diff --git a/.codeclimate.yml b/.codeclimate.yml index 3beadbce4a..6dbf8e0cae 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,26 +1,50 @@ -# Save as .codeclimate.yml (note leading .) in project root directory -languages: - JavaScript: true - PHP: true +--- +engines: + csslint: + enabled: true + duplication: + enabled: true + config: + languages: + - ruby + - javascript + - python + - php + eslint: + enabled: true + fixme: + enabled: true + phpmd: + enabled: true +ratings: + paths: + - "**.css" + - "**.inc" + - "**.js" + - "**.jsx" + - "**.module" + - "**.php" + - "**.py" + - "**.rb" exclude_paths: -- "gulpfile.js" -- "public/packages/maximebf/php-debugbar/debugbar.js" -- "public/packages/maximebf/php-debugbar/widgets.js" -- "public/packages/maximebf/php-debugbar/openhandler.js" -- "public/packages/maximebf/php-debugbar/widgets/sqlqueries/widget.js" -- "public/js/bootstrap3-typeahead.min.js" -- "public/js/bootstrap-sortable.js" -- "public/js/bootstrap-tagsinput.min.js" -- "public/js/bootstrap-tagsinput.min.js.map" -- "public/js/daterangepicker.js" -- "public/js/jquery-2.1.3.min.js" -- "public/js/jquery-2.1.3.min.js.map" -- "public/js/jquery-ui.min.js" -- "public/js/metisMenu.js" -- "public/js/moment.min.js" -- "public/js/sb-admin-2.js" -- "public/bootstrap/*" -- "resources/lang/*" -- "tests/*" -- "database/*" -- "storage/*" +- gulpfile.js +- public/packages/maximebf/php-debugbar/debugbar.js +- public/packages/maximebf/php-debugbar/widgets.js +- public/packages/maximebf/php-debugbar/openhandler.js +- public/packages/maximebf/php-debugbar/widgets/sqlqueries/widget.js +- public/js/bootstrap3-typeahead.min.js +- public/js/bootstrap-sortable.js +- public/js/bootstrap-tagsinput.min.js +- public/js/bootstrap-tagsinput.min.js.map +- public/js/daterangepicker.js +- public/js/jquery-2.1.3.min.js +- public/js/jquery-2.1.3.min.js.map +- public/js/jquery-ui.min.js +- public/js/metisMenu.js +- public/js/moment.min.js +- public/js/sb-admin-2.js +- public/bootstrap/* +- resources/lang/* +- tests/* +- database/* +- storage/* diff --git a/.csslintrc b/.csslintrc new file mode 100644 index 0000000000..aacba956e5 --- /dev/null +++ b/.csslintrc @@ -0,0 +1,2 @@ +--exclude-exts=.min.css +--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..96212a3593 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/*{.,-}min.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..9faa37508e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,213 @@ +ecmaFeatures: + modules: true + jsx: true + +env: + amd: true + browser: true + es6: true + jquery: true + node: true + +# http://eslint.org/docs/rules/ +rules: + # Possible Errors + comma-dangle: [2, never] + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: [2, functions] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 0 + valid-typeof: 2 + + # Best Practices + accessor-pairs: 2 + block-scoped-var: 0 + complexity: [2, 6] + consistent-return: 0 + curly: 0 + default-case: 0 + dot-location: 0 + dot-notation: 0 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 2 + no-caller: 2 + no-case-declarations: 2 + no-div-regex: 2 + no-else-return: 0 + no-empty-label: 2 + no-empty-pattern: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-fallthrough: 2 + no-floating-decimal: 0 + no-implicit-coercion: 0 + no-implied-eval: 2 + no-invalid-this: 0 + no-iterator: 2 + no-labels: 0 + no-lone-blocks: 2 + no-loop-func: 2 + no-magic-number: 0 + no-multi-spaces: 0 + no-multi-str: 0 + no-native-reassign: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-new: 2 + no-octal-escape: 2 + no-octal: 2 + no-proto: 2 + no-redeclare: 2 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 0 + no-throw-literal: 0 + no-unused-expressions: 2 + no-useless-call: 2 + no-useless-concat: 2 + no-void: 2 + no-warning-comments: 0 + no-with: 2 + radix: 2 + vars-on-top: 0 + wrap-iife: 2 + yoda: 0 + + # Strict + strict: 0 + + # Variables + init-declarations: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 0 + no-undef-init: 2 + no-undef: 0 + no-undefined: 0 + no-unused-vars: 0 + no-use-before-define: 0 + + # Node.js and CommonJS + callback-return: 2 + global-require: 2 + handle-callback-err: 2 + no-mixed-requires: 0 + no-new-require: 0 + no-path-concat: 2 + no-process-exit: 2 + no-restricted-modules: 0 + no-sync: 0 + + # Stylistic Issues + array-bracket-spacing: 0 + block-spacing: 0 + brace-style: 0 + camelcase: 0 + comma-spacing: 0 + comma-style: 0 + computed-property-spacing: 0 + consistent-this: 0 + eol-last: 0 + func-names: 0 + func-style: 0 + id-length: 0 + id-match: 0 + indent: 0 + jsx-quotes: 0 + key-spacing: 0 + linebreak-style: 0 + lines-around-comment: 0 + max-depth: 0 + max-len: 0 + max-nested-callbacks: 0 + max-params: 0 + max-statements: [2, 30] + new-cap: 0 + new-parens: 0 + newline-after-var: 0 + no-array-constructor: 0 + no-bitwise: 0 + no-continue: 0 + no-inline-comments: 0 + no-lonely-if: 0 + no-mixed-spaces-and-tabs: 0 + no-multiple-empty-lines: 0 + no-negated-condition: 0 + no-nested-ternary: 0 + no-new-object: 0 + no-plusplus: 0 + no-restricted-syntax: 0 + no-spaced-func: 0 + no-ternary: 0 + no-trailing-spaces: 0 + no-underscore-dangle: 0 + no-unneeded-ternary: 0 + object-curly-spacing: 0 + one-var: 0 + operator-assignment: 0 + operator-linebreak: 0 + padded-blocks: 0 + quote-props: 0 + quotes: 0 + require-jsdoc: 0 + semi-spacing: 0 + semi: 0 + sort-vars: 0 + space-after-keywords: 0 + space-before-blocks: 0 + space-before-function-paren: 0 + space-before-keywords: 0 + space-in-parens: 0 + space-infix-ops: 0 + space-return-throw-case: 0 + space-unary-ops: 0 + spaced-comment: 0 + wrap-regex: 0 + + # ECMAScript 6 + arrow-body-style: 0 + arrow-parens: 0 + arrow-spacing: 0 + constructor-super: 0 + generator-star-spacing: 0 + no-arrow-condition: 0 + no-class-assign: 0 + no-const-assign: 0 + no-dupe-class-members: 0 + no-this-before-super: 0 + no-var: 0 + object-shorthand: 0 + prefer-arrow-callback: 0 + prefer-const: 0 + prefer-reflect: 0 + prefer-spread: 0 + prefer-template: 0 + require-yield: 0 diff --git a/.gitignore b/.gitignore index d58cb9cb4e..24a6fb22e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,6 @@ /vendor /node_modules -Homestead.yaml -Homestead.json .env -_ide_helper.php -_ide_helper_models.php -.phpstorm.meta.php storage/ - -# Eclipse project files -.buildpath -.project -.settings/ - .env.local diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 2231b92414..d244d86e35 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,3 +1,6 @@ # .scrutinizer.yml tools: external_code_coverage: false +filter: + excluded_paths: + - app/Support/Migration/* diff --git a/.travis.yml b/.travis.yml index abfb2812b8..61763a7648 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,15 @@ language: php sudo: false - - php: - - 5.6 - 7 install: + - phpenv config-rm xdebug.ini - composer selfupdate - - composer install --no-dev - - composer update + - rm composer.lock + - composer update --no-scripts + - php artisan clear-compiled + - php artisan optimize - php artisan env - mv -v .env.testing .env - php artisan env diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..7f915a64c2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] +- No unreleased changes yet. + +## [3.8.0] - 2016-03-20 +### Added +- Two factor authentication, thanks to the excellent work of [zjean](https://github.com/zjean). +- A new chart showing your net worth in year and multi-year reports. +- You can now see if your current or future rules actually match any transactions, thanks to the excellent work of @roberthorlings. +- New date fields for transactions. They are not used yet in reports or anything, but they can be filled in. +- New routine to export your data. +- Firefly III will mail the site owner when blocked users try to login, or when blocked domains are used in registrations. + + +### Changed +- Firefly III now requires PHP 7.0 minimum. + + +### Fixed +- HTML fixes, thanks to [roberthorlings](https://github.com/roberthorlings) and [zjean](https://github.com/zjean).. +- A bug fix in the ABN Amro importer, thanks to [roberthorlings](https://github.com/roberthorlings) +- It was not possible to change the opening balance, once it had been set. Thanks to [xnyhps](https://github.com/xnyhps) and [marcoveeneman](https://github.com/marcoveeneman) for spotting this. +- Various other bug fixes. + + + +## [3.4.2] - 2015-05-25 +### Added +- Initial release. + +### Changed +- Initial release. + +### Deprecated +- Initial release. + +### Removed +- Initial release. + +### Fixed +- Initial release. + +### Security +- Initial release. diff --git a/README.md b/README.md index 43bdb2df8d..7c92d457f4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ # Firefly III +[![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php#v7.0.4) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) -[![Total Downloads](https://poser.pugx.org/grumpydictator/firefly-iii/downloads)](https://packagist.org/packages/grumpydictator/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JC5/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master) [![Build Status](https://scrutinizer-ci.com/g/JC5/firefly-iii/badges/build.png?b=master)](https://scrutinizer-ci.com/g/JC5/firefly-iii/build-status/master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/d44c7012-5f50-41ad-add8-8445330e4102/mini.png)](https://insight.sensiolabs.com/projects/d44c7012-5f50-41ad-add8-8445330e4102) [![Code Climate](https://codeclimate.com/github/JC5/firefly-iii/badges/gpa.svg)](https://codeclimate.com/github/JC5/firefly-iii) -[![Project Status](http://stillmaintained.com/JC5/firefly-iii.png?a=b)](http://stillmaintained.com/JC5/firefly-iii) ## About diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index bb99309c05..b15d3c4936 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -1,4 +1,5 @@ line('+------------------------------------------------------------------------------+'); $this->line(''); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 88960c8a36..7ce107879b 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -1,4 +1,6 @@ journal = $journal; diff --git a/app/Events/TransactionJournalUpdated.php b/app/Events/TransactionJournalUpdated.php index 0ea23204e3..edb9a82a12 100644 --- a/app/Events/TransactionJournalUpdated.php +++ b/app/Events/TransactionJournalUpdated.php @@ -1,4 +1,7 @@ -view('errors.FireflyException', ['exception' => $exception, 'debug' => $isDebug], 500); + } + return parent::render($request, $exception); } + /** * Report or log an exception. * @@ -51,6 +64,26 @@ class Handler extends ExceptionHandler */ public function report(Exception $exception) { + + if ($exception instanceof FireflyException || $exception instanceof ErrorException) { + + $user = Auth::check() ? Auth::user() : new User; + + $data = [ + 'class' => get_class($exception), + 'errorMessage' => $exception->getMessage(), + 'time' => date('r'), + 'stackTrace' => $exception->getTraceAsString(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'code' => $exception->getCode(), + ]; + + // create job that will mail. + $job = new MailError($user, env('SITE_OWNER'), Request::ip(), $data); + dispatch($job); + } + parent::report($exception); } } diff --git a/app/Exceptions/NotImplementedException.php b/app/Exceptions/NotImplementedException.php index c1f3ed4500..47c43b9e68 100644 --- a/app/Exceptions/NotImplementedException.php +++ b/app/Exceptions/NotImplementedException.php @@ -1,4 +1,5 @@ repository = app('FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface'); + // make storage: + $this->uploadDisk = Storage::disk('upload'); + $this->exportDisk = Storage::disk('export'); + + parent::__construct($job); + } + + /** + * + */ + public function run() + { + // grab all the users attachments: + $attachments = $this->getAttachments(); + + /** @var Attachment $attachment */ + foreach ($attachments as $attachment) { + $this->exportAttachment($attachment); + } + + // put the explanation string in a file and attach it as well. + $file = $this->job->key . '-Source of all your attachments explained.txt'; + $this->exportDisk->put($file, $this->explanationString); + Log::debug('Also put explanation file "' . $file . '" in the zip.'); + $this->getFiles()->push($file); + } + + /** + * @param Attachment $attachment + */ + private function explain(Attachment $attachment) + { + /** @var TransactionJournal $journal */ + $journal = $attachment->attachable; + $args = [ + 'attachment_name' => $attachment->filename, + 'attachment_id' => $attachment->id, + 'type' => strtolower($journal->transactionType->type), + 'description' => $journal->description, + 'journal_id' => $journal->id, + 'date' => $journal->date->formatLocalized(strval(trans('config.month_and_day'))), + 'amount' => Amount::formatJournal($journal, false), + ]; + $string = trans('firefly.attachment_explanation', $args) . "\n"; + $this->explanationString .= $string; + + } + + /** + * @param Attachment $attachment + * + * @return bool + */ + private function exportAttachment(Attachment $attachment): bool + { + $file = $attachment->fileName(); + Log::debug('Original file is at "' . $file . '".'); + if ($this->uploadDisk->exists($file)) { + try { + $decrypted = Crypt::decrypt($this->uploadDisk->get($file)); + $exportFile = $this->exportFileName($attachment); + $this->exportDisk->put($exportFile, $decrypted); + $this->getFiles()->push($exportFile); + Log::debug('Stored file content in new file "' . $exportFile . '", which will be in the final zip file.'); + + // explain: + $this->explain($attachment); + } catch (DecryptException $e) { + Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage()); + } + + } + + return true; + } + + /** + * Returns the new file name for the export file. + * + * @param $attachment + * + * @return string + */ + private function exportFileName($attachment): string + { + + return sprintf('%s-Attachment nr. %s - %s', $this->job->key, strval($attachment->id), $attachment->filename); + } + + /** + * @return Collection + */ + private function getAttachments(): Collection + { + $attachments = $this->repository->get(); + + Log::debug('Found ' . $attachments->count() . ' attachments.'); + + return $attachments; + } +} diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php new file mode 100644 index 0000000000..c779a1fa02 --- /dev/null +++ b/app/Export/Collector/BasicCollector.php @@ -0,0 +1,57 @@ +files = new Collection; + $this->job = $job; + } + + /** + * @return Collection + */ + public function getFiles() + { + return $this->files; + } + + /** + * @param Collection $files + */ + public function setFiles(Collection $files) + { + $this->files = $files; + } + + +} diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php new file mode 100644 index 0000000000..f220c479ec --- /dev/null +++ b/app/Export/Collector/CollectorInterface.php @@ -0,0 +1,38 @@ +uploadDisk = Storage::disk('upload'); + $this->exportDisk = Storage::disk('export'); + $this->expected = 'csv-upload-' . Auth::user()->id . '-'; + } + + /** + * + */ + public function run() + { + // grab upload directory. + $files = $this->uploadDisk->files(); + Log::debug('Found ' . count($files) . ' files in the upload directory.'); + + foreach ($files as $entry) { + $this->processOldUpload($entry); + } + } + + /** + * @param string $entry + * + * @return string + */ + private function getOriginalUploadDate(string $entry): string + { + // this is an original upload. + $parts = explode('-', str_replace(['.csv.encrypted', $this->expected], '', $entry)); + $originalUpload = intval($parts[1]); + $date = date('Y-m-d \a\t H-i-s', $originalUpload); + + return $date; + } + + /** + * @param string $entry + * + * @return bool + */ + private function isValidFile(string $entry): bool + { + $len = strlen($this->expected); + if (substr($entry, 0, $len) === $this->expected) { + Log::debug($entry . ' is part of this users original uploads.'); + + return true; + } + Log::debug($entry . ' is not part of this users original uploads.'); + + return false; + } + + /** + * @param $entry + */ + private function processOldUpload(string $entry) + { + $content = ''; + + if ($this->isValidFile($entry)) { + try { + $content = Crypt::decrypt($this->uploadDisk->get($entry)); + } catch (DecryptException $e) { + Log::error('Could not decrypt old CSV import file ' . $entry . '. Skipped because ' . $e->getMessage()); + } + } + if (strlen($content) > 0) { + // continue with file: + $date = $this->getOriginalUploadDate($entry); + $file = $this->job->key . '-Old CSV import dated ' . $date . '.csv'; + Log::debug('Will put "' . $file . '" in the zip file.'); + $this->exportDisk->put($file, $content); + $this->getFiles()->push($file); + } + } +} diff --git a/app/Export/ConfigurationFile.php b/app/Export/ConfigurationFile.php new file mode 100644 index 0000000000..c78e6501b7 --- /dev/null +++ b/app/Export/ConfigurationFile.php @@ -0,0 +1,67 @@ +job = $job; + $this->exportDisk = Storage::disk('export'); + } + + /** + * @return bool + */ + public function make() + { + $fields = array_keys(get_class_vars(Entry::class)); + $types = Entry::getTypes(); + + $configuration = [ + 'date-format' => 'Y-m-d', // unfortunately, this is hard-coded. + 'has-headers' => true, + 'map' => [], // we could build a map if necessary for easy re-import. + 'roles' => [], + 'mapped' => [], + 'specifix' => [], + ]; + foreach ($fields as $field) { + $configuration['roles'][] = $types[$field]; + } + $file = $this->job->key . '-configuration.json'; + Log::debug('Created JSON config file.'); + Log::debug('Will put "' . $file . '" in the ZIP file.'); + $this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT)); + + return $file; + } + +} diff --git a/app/Export/Entry.php b/app/Export/Entry.php new file mode 100644 index 0000000000..3580cc6960 --- /dev/null +++ b/app/Export/Entry.php @@ -0,0 +1,460 @@ +setDescription($journal->description); + $entry->setDate($journal->date->format('Y-m-d')); + $entry->setAmount(TransactionJournal::amount($journal)); + + /** @var Budget $budget */ + $budget = $journal->budgets->first(); + if (!is_null($budget)) { + $entry->setBudgetId($budget->id); + $entry->setBudgetName($budget->name); + } + + /** @var Category $category */ + $category = $journal->categories->first(); + if (!is_null($category)) { + $entry->setCategoryId($category->id); + $entry->setCategoryName($category->name); + } + + if (!is_null($journal->bill_id)) { + $entry->setBillId($journal->bill_id); + $entry->setBillName($journal->bill->name); + } + + /** @var Account $sourceAccount */ + $sourceAccount = TransactionJournal::sourceAccount($journal); + $entry->setFromAccountId($sourceAccount->id); + $entry->setFromAccountName($sourceAccount->name); + $entry->setFromAccountIban($sourceAccount->iban); + $entry->setFromAccountType($sourceAccount->accountType->type); + $entry->setFromAccountNumber($sourceAccount->getMeta('accountNumber')); + + + /** @var Account $destination */ + $destination = TransactionJournal::destinationAccount($journal); + $entry->setToAccountId($destination->id); + $entry->setToAccountName($destination->name); + $entry->setToAccountIban($destination->iban); + $entry->setToAccountType($destination->accountType->type); + $entry->setToAccountNumber($destination->getMeta('accountNumber')); + + return $entry; + + } + + /** + * @return array + */ + public static function getTypes(): array + { + // key = field name (see top of class) + // value = field type (see csv.php under 'roles') + return [ + 'amount' => 'amount', + 'date' => 'date-transaction', + 'description' => 'description', + 'billId' => 'bill-id', + 'billName' => 'bill-name', + 'budgetId' => 'budget-id', + 'budgetName' => 'budget-name', + 'categoryId' => 'category-id', + 'categoryName' => 'category-name', + 'fromAccountId' => 'account-id', + 'fromAccountName' => 'account-name', + 'fromAccountIban' => 'account-iban', + 'fromAccountType' => '_ignore', // no, Firefly cannot import what it exports. I know :D + 'toAccountId' => 'opposing-id', + 'toAccountName' => 'opposing-name', + 'toAccountIban' => 'opposing-iban', + 'toAccountType' => '_ignore', + ]; + } + + /** + * @return string + */ + public function getAmount(): string + { + return $this->amount; + } + + /** + * @param string $amount + */ + public function setAmount(string $amount) + { + $this->amount = $amount; + } + + /** + * @return int + */ + public function getBillId() + { + return $this->billId; + } + + /** + * @param int $billId + */ + public function setBillId($billId) + { + $this->billId = $billId; + } + + /** + * @return string + */ + public function getBillName() + { + return $this->billName; + } + + /** + * @param string $billName + */ + public function setBillName($billName) + { + $this->billName = $billName; + } + + /** + * @return int + */ + public function getBudgetId() + { + return $this->budgetId; + } + + /** + * @param int $budgetId + */ + public function setBudgetId($budgetId) + { + $this->budgetId = $budgetId; + } + + /** + * @return string + */ + public function getBudgetName() + { + return $this->budgetName; + } + + /** + * @param string $budgetName + */ + public function setBudgetName($budgetName) + { + $this->budgetName = $budgetName; + } + + /** + * @return int + */ + public function getCategoryId() + { + return $this->categoryId; + } + + /** + * @param int $categoryId + */ + public function setCategoryId($categoryId) + { + $this->categoryId = $categoryId; + } + + /** + * @return string + */ + public function getCategoryName() + { + return $this->categoryName; + } + + /** + * @param string $categoryName + */ + public function setCategoryName($categoryName) + { + $this->categoryName = $categoryName; + } + + /** + * @return string + */ + public function getDate() + { + return $this->date; + } + + /** + * @param string $date + */ + public function setDate(string $date) + { + $this->date = $date; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription(string $description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getFromAccountIban() + { + return $this->fromAccountIban; + } + + /** + * @param string $fromAccountIban + */ + public function setFromAccountIban($fromAccountIban) + { + $this->fromAccountIban = $fromAccountIban; + } + + /** + * @return int + */ + public function getFromAccountId() + { + return $this->fromAccountId; + } + + /** + * @param int $fromAccountId + */ + public function setFromAccountId($fromAccountId) + { + $this->fromAccountId = $fromAccountId; + } + + /** + * @return string + */ + public function getFromAccountName() + { + return $this->fromAccountName; + } + + /** + * @param string $fromAccountName + */ + public function setFromAccountName($fromAccountName) + { + $this->fromAccountName = $fromAccountName; + } + + /** + * @return mixed + */ + public function getFromAccountNumber() + { + return $this->fromAccountNumber; + } + + /** + * @param mixed $fromAccountNumber + */ + public function setFromAccountNumber($fromAccountNumber) + { + $this->fromAccountNumber = $fromAccountNumber; + } + + /** + * @return string + */ + public function getFromAccountType() + { + return $this->fromAccountType; + } + + /** + * @param string $fromAccountType + */ + public function setFromAccountType($fromAccountType) + { + $this->fromAccountType = $fromAccountType; + } + + /** + * @return string + */ + public function getToAccountIban() + { + return $this->toAccountIban; + } + + /** + * @param string $toAccountIban + */ + public function setToAccountIban($toAccountIban) + { + $this->toAccountIban = $toAccountIban; + } + + /** + * @return int + */ + public function getToAccountId() + { + return $this->toAccountId; + } + + /** + * @param int $toAccountId + */ + public function setToAccountId($toAccountId) + { + $this->toAccountId = $toAccountId; + } + + /** + * @return string + */ + public function getToAccountName() + { + return $this->toAccountName; + } + + /** + * @param string $toAccountName + */ + public function setToAccountName($toAccountName) + { + $this->toAccountName = $toAccountName; + } + + /** + * @return mixed + */ + public function getToAccountNumber() + { + return $this->toAccountNumber; + } + + /** + * @param mixed $toAccountNumber + */ + public function setToAccountNumber($toAccountNumber) + { + $this->toAccountNumber = $toAccountNumber; + } + + /** + * @return string + */ + public function getToAccountType() + { + return $this->toAccountType; + } + + /** + * @param string $toAccountType + */ + public function setToAccountType($toAccountType) + { + $this->toAccountType = $toAccountType; + } + + +} diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php new file mode 100644 index 0000000000..04f2002724 --- /dev/null +++ b/app/Export/Exporter/BasicExporter.php @@ -0,0 +1,56 @@ +entries = new Collection; + $this->job = $job; + } + + /** + * @return Collection + */ + public function getEntries() + { + return $this->entries; + } + + /** + * @param Collection $entries + */ + public function setEntries(Collection $entries) + { + $this->entries = $entries; + } + + +} diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php new file mode 100644 index 0000000000..6799ea591c --- /dev/null +++ b/app/Export/Exporter/CsvExporter.php @@ -0,0 +1,81 @@ +fileName; + } + + /** + * + */ + public function run() + { + // create temporary file: + $this->tempFile(); + + // necessary for CSV writer: + $fullPath = storage_path('export') . DIRECTORY_SEPARATOR . $this->fileName; + + // create CSV writer: + $writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w'); + + // all rows: + $rows = []; + + // add header: + $first = $this->getEntries()->first(); + $rows[] = array_keys(get_object_vars($first)); + + // then the rest: + /** @var Entry $entry */ + foreach ($this->getEntries() as $entry) { + $rows[] = array_values(get_object_vars($entry)); + + } + $writer->insertAll($rows); + } + + private function tempFile() + { + $this->fileName = $this->job->key . '-records.csv'; + } +} diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php new file mode 100644 index 0000000000..aed29b2daa --- /dev/null +++ b/app/Export/Exporter/ExporterInterface.php @@ -0,0 +1,43 @@ +settings = $settings; + $this->accounts = $settings['accounts']; + $this->exportFormat = $settings['exportFormat']; + $this->includeAttachments = $settings['includeAttachments']; + $this->includeConfig = $settings['includeConfig']; + $this->includeOldUploads = $settings['includeOldUploads']; + $this->job = $settings['job']; + $this->journals = new Collection; + $this->exportEntries = new Collection; + $this->files = new Collection; + + } + + /** + * + */ + public function collectAttachments() + { + $attachmentCollector = app('FireflyIII\Export\Collector\AttachmentCollector', [$this->job]); + $attachmentCollector->run(); + $this->files = $this->files->merge($attachmentCollector->getFiles()); + } + + /** + * + */ + public function collectJournals() + { + $args = [$this->accounts, Auth::user(), $this->settings['startDate'], $this->settings['endDate']]; + $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); + $this->journals = $journalCollector->collect(); + Log::debug( + 'Collected ' . + $this->journals->count() . ' journals (between ' . + $this->settings['startDate']->format('Y-m-d') . ' and ' . + $this->settings['endDate']->format('Y-m-d') + . ').' + ); + } + + public function collectOldUploads() + { + $uploadCollector = app('FireflyIII\Export\Collector\UploadCollector', [$this->job]); + $uploadCollector->run(); + + $this->files = $this->files->merge($uploadCollector->getFiles()); + } + + /** + * + */ + public function convertJournals() + { + $count = 0; + /** @var TransactionJournal $journal */ + foreach ($this->journals as $journal) { + $this->exportEntries->push(Entry::fromJournal($journal)); + $count++; + } + Log::debug('Converted ' . $count . ' journals to "Entry" objects.'); + } + + public function createConfigFile() + { + $this->configurationMaker = app('FireflyIII\Export\ConfigurationFile', [$this->job]); + $this->files->push($this->configurationMaker->make()); + } + + public function createZipFile() + { + $zip = new ZipArchive; + $file = $this->job->key . '.zip'; + $fullPath = storage_path('export') . '/' . $file; + Log::debug('Will create zip file at ' . $fullPath); + + if ($zip->open($fullPath, ZipArchive::CREATE) !== true) { + throw new FireflyException('Cannot store zip file.'); + } + // for each file in the collection, add it to the zip file. + $disk = Storage::disk('export'); + foreach ($this->getFiles() as $entry) { + // is part of this job? + $zipFileName = str_replace($this->job->key . '-', '', $entry); + $result = $zip->addFromString($zipFileName, $disk->get($entry)); + if (!$result) { + Log::error('Could not add "' . $entry . '" into zip file as "' . $zipFileName . '".'); + } + } + + $zip->close(); + + // delete the files: + foreach ($this->getFiles() as $file) { + Log::debug('Will now delete file "' . $file . '".'); + $disk->delete($file); + } + Log::debug('Done!'); + } + + /** + * + */ + public function exportJournals() + { + $exporterClass = Config::get('firefly.export_formats.' . $this->exportFormat); + $exporter = app($exporterClass, [$this->job]); + Log::debug('Going to export ' . $this->exportEntries->count() . ' export entries into ' . $this->exportFormat . ' format.'); + $exporter->setEntries($this->exportEntries); + $exporter->run(); + $this->files->push($exporter->getFileName()); + Log::debug('Added "' . $exporter->getFileName() . '" to the list of files to include in the zip.'); + } + + /** + * @return Collection + */ + public function getFiles() + { + return $this->files; + } +} diff --git a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php index 0e09e5e077..f4e5ba6938 100644 --- a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php +++ b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php @@ -1,4 +1,5 @@ 1, @@ -30,7 +30,6 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface 'label' => trans('firefly.spent'), 'data' => []]]]; - bcscale(2); $start->subDay(); $ids = $this->getIdsFromCollection($accounts); $startBalances = Steam::balancesById($ids, $start); @@ -69,7 +68,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface * * @return array */ - public function frontpage(Collection $accounts, Carbon $start, Carbon $end) + public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array { // language: $format = (string)trans('config.month_and_day'); @@ -116,7 +115,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface * * @return array */ - public function single(Account $account, Carbon $start, Carbon $end) + public function single(Account $account, Carbon $start, Carbon $end): array { // language: $format = (string)trans('config.month_and_day'); @@ -137,7 +136,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface while ($end >= $current) { $theDate = $current->format('Y-m-d'); - $balance = isset($range[$theDate]) ? $range[$theDate] : $previous; + $balance = $range[$theDate] ?? $previous; $data['labels'][] = $current->formatLocalized($format); $data['datasets'][0]['data'][] = $balance; @@ -153,7 +152,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface * * @return array */ - protected function getIdsFromCollection(Collection $collection) + protected function getIdsFromCollection(Collection $collection): array { $ids = []; foreach ($collection as $entry) { @@ -170,7 +169,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface * * @return string */ - protected function isInArray($array, $entryId) + protected function isInArray($array, $entryId): string { if (isset($array[$entryId])) { return $array[$entryId]; diff --git a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php b/app/Generator/Chart/Bill/BillChartGeneratorInterface.php index 077443ed94..1baa03cc3e 100644 --- a/app/Generator/Chart/Bill/BillChartGeneratorInterface.php +++ b/app/Generator/Chart/Bill/BillChartGeneratorInterface.php @@ -1,4 +1,5 @@ round($unpaid, 2), @@ -38,7 +38,7 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface 'label' => trans('firefly.unpaid'), ], [ - 'value' => round($paid * -1, 2), // paid is negative, must be positive. + 'value' => round(bcmul($paid, '-1'), 2), // paid is negative, must be positive. 'color' => 'rgba(0, 141, 76, 0.7)', 'highlight' => 'rgba(0, 141, 76, 0.9)', 'label' => trans('firefly.paid'), @@ -54,7 +54,7 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface * * @return array */ - public function single(Bill $bill, Collection $entries) + public function single(Bill $bill, Collection $entries): array { $format = (string)trans('config.month'); $data = [ @@ -73,7 +73,7 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface /* * journalAmount has been collected in BillRepository::getJournals */ - $actualAmount[] = round(($entry->journalAmount * -1), 2); + $actualAmount[] = round(TransactionJournal::amountPositive($entry), 2); } $data['datasets'][] = [ diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php index dbe988f585..fe85f83a70 100644 --- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php +++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php @@ -1,4 +1,5 @@ data; @@ -56,7 +56,7 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface * * @return array */ - public function budgetLimit(Collection $entries) + public function budgetLimit(Collection $entries): array { return $this->budget($entries, 'monthAndDay'); } @@ -66,7 +66,7 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface * * @return array */ - public function frontpage(Collection $entries) + public function frontpage(Collection $entries): array { $data = [ 'count' => 0, @@ -84,8 +84,8 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface foreach ($filtered as $entry) { $data['labels'][] = $entry[0]; $left[] = round($entry[1], 2); - $spent[] = round($entry[2] * -1, 2); // spent is coming in negative, must be positive - $overspent[] = round($entry[3] * -1, 2); // same + $spent[] = round(bcmul($entry[2], '-1'), 2); // spent is coming in negative, must be positive + $overspent[] = round(bcmul($entry[3], '-1'), 2); // same } $data['datasets'][] = [ @@ -111,7 +111,7 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface * * @return array */ - public function multiYear(Collection $entries) + public function multiYear(Collection $entries): array { // dataset: $data = [ @@ -146,7 +146,7 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface * * @return array */ - public function year(Collection $budgets, Collection $entries) + public function year(Collection $budgets, Collection $entries): array { // language: $format = (string)trans('config.month'); diff --git a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php index 7357ab3225..c5d0bca4bb 100644 --- a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php +++ b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php @@ -1,4 +1,5 @@ 1, @@ -102,7 +102,7 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface foreach ($entries as $entry) { if ($entry->spent != 0) { $data['labels'][] = $entry->name; - $data['datasets'][0]['data'][] = round(($entry->spent * -1), 2); + $data['datasets'][0]['data'][] = round(bcmul($entry->spent, '-1'), 2); } } @@ -114,7 +114,7 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface * * @return array */ - public function multiYear(Collection $entries) + public function multiYear(Collection $entries): array { // dataset: $data = [ @@ -154,7 +154,7 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface * * @return array */ - public function period(Collection $entries) + public function period(Collection $entries): array { return $this->all($entries); @@ -166,7 +166,7 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface * * @return array */ - public function spentInPeriod(Collection $categories, Collection $entries) + public function spentInPeriod(Collection $categories, Collection $entries): array { // language: diff --git a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php index 1f737573a3..086e208039 100644 --- a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php +++ b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php @@ -1,5 +1,5 @@ date); $sum = bcadd($sum, $entry->sum); diff --git a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php b/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php index a45eace0e6..336bbceb71 100644 --- a/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php +++ b/app/Generator/Chart/PiggyBank/PiggyBankChartGeneratorInterface.php @@ -1,4 +1,5 @@ 2, @@ -52,7 +52,7 @@ class ChartJsReportChartGenerator implements ReportChartGeneratorInterface * * @return array */ - public function multiYearInOutSummarized($income, $expense, $count) + public function multiYearInOutSummarized(string $income, string $expense, int $count): array { $data = [ 'count' => 2, @@ -81,7 +81,33 @@ class ChartJsReportChartGenerator implements ReportChartGeneratorInterface * * @return array */ - public function yearInOut(Collection $entries) + public function netWorth(Collection $entries) : array + { + $format = (string)trans('config.month_and_day'); + $data = [ + 'count' => 1, + 'labels' => [], + 'datasets' => [ + [ + 'label' => trans('firefly.net-worth'), + 'data' => [], + ], + ], + ]; + foreach ($entries as $entry) { + $data['labels'][] = trim($entry['date']->formatLocalized($format)); + $data['datasets'][0]['data'][] = round($entry['net-worth'], 2); + } + + return $data; + } + + /** + * @param Collection $entries + * + * @return array + */ + public function yearInOut(Collection $entries): array { // language: $format = (string)trans('config.month'); @@ -117,7 +143,7 @@ class ChartJsReportChartGenerator implements ReportChartGeneratorInterface * * @return array */ - public function yearInOutSummarized($income, $expense, $count) + public function yearInOutSummarized(string $income, string $expense, int $count): array { $data = [ diff --git a/app/Generator/Chart/Report/ReportChartGeneratorInterface.php b/app/Generator/Chart/Report/ReportChartGeneratorInterface.php index 8b5f753232..c1999137dc 100644 --- a/app/Generator/Chart/Report/ReportChartGeneratorInterface.php +++ b/app/Generator/Chart/Report/ReportChartGeneratorInterface.php @@ -1,4 +1,5 @@ journal; @@ -49,12 +41,11 @@ class ConnectJournalToPiggyBank if (is_null($repetition)) { return true; } - bcscale(2); - $amount = $journal->amount_positive; + $amount = TransactionJournal::amountPositive($journal); // if piggy account matches source account, the amount is positive - if ($piggyBank->account_id == $journal->source_account->id) { - $amount = $amount * -1; + if ($piggyBank->account_id == TransactionJournal::sourceAccount($journal)->id) { + $amount = bcmul($amount, '-1'); } diff --git a/app/Handlers/Events/FireRulesForStore.php b/app/Handlers/Events/FireRulesForStore.php index 0c3a67bc46..38e37bc64a 100644 --- a/app/Handlers/Events/FireRulesForStore.php +++ b/app/Handlers/Events/FireRulesForStore.php @@ -1,4 +1,5 @@ get(['rules.*']); /** @var Rule $rule */ foreach ($rules as $rule) { - Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')'); - $processor = new Processor($rule, $event->journal); - // get some return out of this? - $processor->handle(); + Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')'); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($event->journal); if ($rule->stop_processing) { - break; + return true; } } } + + return true; } } diff --git a/app/Handlers/Events/FireRulesForUpdate.php b/app/Handlers/Events/FireRulesForUpdate.php index e829ee16b8..a645f151ff 100644 --- a/app/Handlers/Events/FireRulesForUpdate.php +++ b/app/Handlers/Events/FireRulesForUpdate.php @@ -1,4 +1,5 @@ id . ' (' . $rule->title . ')'); - $processor = new Processor($rule, $event->journal); - // get some return out of this? - $processor->handle(); + Log::debug('Now handling rule #' . $rule->id . ' (' . $rule->title . ')'); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($event->journal); if ($rule->stop_processing) { break; @@ -70,5 +62,7 @@ class FireRulesForUpdate } } + + return true; } } diff --git a/app/Handlers/Events/ScanForBillsAfterStore.php b/app/Handlers/Events/ScanForBillsAfterStore.php index 81fa3f870f..ffcb3f26ac 100644 --- a/app/Handlers/Events/ScanForBillsAfterStore.php +++ b/app/Handlers/Events/ScanForBillsAfterStore.php @@ -1,4 +1,5 @@ journal; BillScanner::scan($journal); + + return true; } } diff --git a/app/Handlers/Events/ScanForBillsAfterUpdate.php b/app/Handlers/Events/ScanForBillsAfterUpdate.php index c17a79360a..5061b9e315 100644 --- a/app/Handlers/Events/ScanForBillsAfterUpdate.php +++ b/app/Handlers/Events/ScanForBillsAfterUpdate.php @@ -1,4 +1,5 @@ journal; BillScanner::scan($journal); + + return true; } } diff --git a/app/Handlers/Events/UpdateJournalConnection.php b/app/Handlers/Events/UpdateJournalConnection.php index e04b44f088..0a5e5f2804 100644 --- a/app/Handlers/Events/UpdateJournalConnection.php +++ b/app/Handlers/Events/UpdateJournalConnection.php @@ -1,8 +1,11 @@ -journal; @@ -37,7 +31,7 @@ class UpdateJournalConnection /** @var PiggyBankEvent $event */ $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first(); if (is_null($event)) { - return; + return false; } $piggyBank = $event->piggyBank()->first(); $repetition = null; @@ -47,11 +41,10 @@ class UpdateJournalConnection } if (is_null($repetition)) { - return; + return false; } - bcscale(2); - $amount = $journal->amount; + $amount = TransactionJournal::amount($journal); $diff = bcsub($amount, $event->amount); // update current repetition $repetition->currentamount = bcadd($repetition->currentamount, $diff); @@ -60,6 +53,8 @@ class UpdateJournalConnection $event->amount = $amount; $event->save(); + + return true; } } diff --git a/app/Handlers/Events/UserEventListener.php b/app/Handlers/Events/UserEventListener.php new file mode 100644 index 0000000000..6bfdbb75bd --- /dev/null +++ b/app/Handlers/Events/UserEventListener.php @@ -0,0 +1,32 @@ +allowedMimes = Config::get('firefly.allowedMimes'); $this->errors = new MessageBag; $this->messages = new MessageBag; + $this->uploadDisk = Storage::disk('upload'); } /** @@ -44,7 +51,7 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return string */ - public function getAttachmentLocation(Attachment $attachment) + public function getAttachmentLocation(Attachment $attachment): string { $path = storage_path('upload') . DIRECTORY_SEPARATOR . 'at-' . $attachment->id . '.data'; @@ -54,7 +61,7 @@ class AttachmentHelper implements AttachmentHelperInterface /** * @return MessageBag */ - public function getErrors() + public function getErrors(): MessageBag { return $this->errors; } @@ -62,7 +69,7 @@ class AttachmentHelper implements AttachmentHelperInterface /** * @return MessageBag */ - public function getMessages() + public function getMessages(): MessageBag { return $this->messages; } @@ -72,9 +79,17 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return bool */ - public function saveAttachmentsForModel(Model $model) + public function saveAttachmentsForModel(Model $model): bool { - $files = Input::file('attachments'); + $files = null; + try { + if (Input::hasFile('attachments')) { + $files = Input::file('attachments'); + } + } catch (TypeError $e) { + // Log it, do nothing else. + Log::error($e->getMessage()); + } if (is_array($files)) { foreach ($files as $entry) { @@ -97,7 +112,7 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return bool */ - protected function hasFile(UploadedFile $file, Model $model) + protected function hasFile(UploadedFile $file, Model $model): bool { $md5 = md5_file($file->getRealPath()); $name = $file->getClientOriginalName(); @@ -115,16 +130,17 @@ class AttachmentHelper implements AttachmentHelperInterface } /** + * * @param UploadedFile $file * @param Model $model * - * @return bool|Attachment + * @return Attachment */ - protected function processFile(UploadedFile $file, Model $model) + protected function processFile(UploadedFile $file, Model $model): Attachment { $validation = $this->validateUpload($file, $model); if ($validation === false) { - return false; + return new Attachment; } $attachment = new Attachment; // create Attachment object. @@ -137,15 +153,13 @@ class AttachmentHelper implements AttachmentHelperInterface $attachment->uploaded = 0; $attachment->save(); - $path = $file->getRealPath(); // encrypt and move file to storage. - $content = file_get_contents($path); + $fileObject = $file->openFile('r'); + $fileObject->rewind(); + $content = $fileObject->fread($file->getSize()); $encrypted = Crypt::encrypt($content); // store it: - $upload = $this->getAttachmentLocation($attachment); - if (is_writable(dirname($upload))) { - file_put_contents($upload, $encrypted); - } + $this->uploadDisk->put($attachment->fileName(), $encrypted); $attachment->uploaded = 1; // update attachment $attachment->save(); @@ -165,7 +179,7 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return bool */ - protected function validMime(UploadedFile $file) + protected function validMime(UploadedFile $file): bool { $mime = e($file->getMimeType()); $name = e($file->getClientOriginalName()); @@ -185,7 +199,7 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return bool */ - protected function validSize(UploadedFile $file) + protected function validSize(UploadedFile $file): bool { $size = $file->getSize(); $name = e($file->getClientOriginalName()); @@ -205,7 +219,7 @@ class AttachmentHelper implements AttachmentHelperInterface * * @return bool */ - protected function validateUpload(UploadedFile $file, Model $model) + protected function validateUpload(UploadedFile $file, Model $model): bool { if (!$this->validMime($file)) { return false; diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index bcc36a642f..51e7c84628 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -1,5 +1,5 @@ accounts = new Collection; + } + + /** + * @return Collection + */ + public function getAccounts(): Collection { return $this->accounts; } /** - * @param \Illuminate\Support\Collection $accounts + * @param Collection $accounts */ - public function setAccounts($accounts) + public function setAccounts(Collection $accounts) { $this->accounts = $accounts; } /** - * @return float + * @return string */ - public function getDifference() + public function getDifference(): string { return $this->difference; } /** - * @param float $difference + * @param string $difference */ - public function setDifference($difference) + public function setDifference(string $difference) { $this->difference = $difference; } /** - * @return float + * @return string */ - public function getEnd() + public function getEnd(): string { return $this->end; } /** - * @param float $end + * @param string $end */ - public function setEnd($end) + public function setEnd(string $end) { $this->end = $end; } /** - * @return float + * @return string */ - public function getStart() + public function getStart(): string { return $this->start; } /** - * @param float $start + * @param string $start */ - public function setStart($start) + public function setStart(string $start) { $this->start = $start; } diff --git a/app/Helpers/Collection/Balance.php b/app/Helpers/Collection/Balance.php index 48d8433c2f..41db832ef7 100644 --- a/app/Helpers/Collection/Balance.php +++ b/app/Helpers/Collection/Balance.php @@ -1,5 +1,5 @@ balanceHeader; + return $this->balanceHeader ?? new BalanceHeader; } /** * @param BalanceHeader $balanceHeader */ - public function setBalanceHeader($balanceHeader) + public function setBalanceHeader(BalanceHeader $balanceHeader) { $this->balanceHeader = $balanceHeader; } /** - * @return \Illuminate\Support\Collection + * @return Collection */ - public function getBalanceLines() + public function getBalanceLines(): Collection { return $this->balanceLines; } diff --git a/app/Helpers/Collection/BalanceEntry.php b/app/Helpers/Collection/BalanceEntry.php index 7749226b82..baa2f3d17c 100644 --- a/app/Helpers/Collection/BalanceEntry.php +++ b/app/Helpers/Collection/BalanceEntry.php @@ -1,5 +1,5 @@ account; } @@ -33,39 +33,39 @@ class BalanceEntry /** * @param AccountModel $account */ - public function setAccount($account) + public function setAccount(AccountModel $account) { $this->account = $account; } /** - * @return float + * @return string */ - public function getLeft() + public function getLeft(): string { return $this->left; } /** - * @param float $left + * @param string $left */ - public function setLeft($left) + public function setLeft(string $left) { $this->left = $left; } /** - * @return float + * @return string */ - public function getSpent() + public function getSpent(): string { return $this->spent; } /** - * @param float $spent + * @param string $spent */ - public function setSpent($spent) + public function setSpent(string $spent) { $this->spent = $spent; } diff --git a/app/Helpers/Collection/BalanceHeader.php b/app/Helpers/Collection/BalanceHeader.php index 5dbe86f5f6..60ed221f7b 100644 --- a/app/Helpers/Collection/BalanceHeader.php +++ b/app/Helpers/Collection/BalanceHeader.php @@ -1,5 +1,5 @@ accounts; } diff --git a/app/Helpers/Collection/BalanceLine.php b/app/Helpers/Collection/BalanceLine.php index 0800010c42..e6157f6c06 100644 --- a/app/Helpers/Collection/BalanceLine.php +++ b/app/Helpers/Collection/BalanceLine.php @@ -1,5 +1,5 @@ balanceEntries = new Collection; + } /** @@ -46,7 +48,7 @@ class BalanceLine /** * @return Collection */ - public function getBalanceEntries() + public function getBalanceEntries(): Collection { return $this->balanceEntries; } @@ -54,7 +56,7 @@ class BalanceLine /** * @param Collection $balanceEntries */ - public function setBalanceEntries($balanceEntries) + public function setBalanceEntries(Collection $balanceEntries) { $this->balanceEntries = $balanceEntries; } @@ -62,15 +64,15 @@ class BalanceLine /** * @return BudgetModel */ - public function getBudget() + public function getBudget(): BudgetModel { - return $this->budget; + return $this->budget ?? new BudgetModel; } /** * @param BudgetModel $budget */ - public function setBudget($budget) + public function setBudget(BudgetModel $budget) { $this->budget = $budget; } @@ -78,7 +80,7 @@ class BalanceLine /** * @return int */ - public function getRole() + public function getRole(): int { return $this->role; } @@ -86,7 +88,7 @@ class BalanceLine /** * @param int $role */ - public function setRole($role) + public function setRole(int $role) { $this->role = $role; } @@ -94,9 +96,9 @@ class BalanceLine /** * @return string */ - public function getTitle() + public function getTitle(): string { - if ($this->getBudget() instanceof BudgetModel) { + if ($this->getBudget() instanceof BudgetModel && !is_null($this->getBudget()->id)) { return $this->getBudget()->name; } if ($this->getRole() == self::ROLE_DEFAULTROLE) { @@ -118,14 +120,14 @@ class BalanceLine * on the given budget/repetition. If you subtract all those amounts from the budget/repetition's * total amount, this is returned: * - * @return float + * @return string */ - public function leftOfRepetition() + public function leftOfRepetition(): string { - $start = isset($this->budget->amount) ? $this->budget->amount : 0; + $start = $this->budget->amount ?? '0'; /** @var BalanceEntry $balanceEntry */ foreach ($this->getBalanceEntries() as $balanceEntry) { - $start += $balanceEntry->getSpent(); + $start = bcadd($balanceEntry->getSpent(), $start); } return $start; diff --git a/app/Helpers/Collection/Bill.php b/app/Helpers/Collection/Bill.php index 550f8f1aeb..f5b58b36ec 100644 --- a/app/Helpers/Collection/Bill.php +++ b/app/Helpers/Collection/Bill.php @@ -1,5 +1,5 @@ bills->sortBy( function (BillLine $bill) { diff --git a/app/Helpers/Collection/BillLine.php b/app/Helpers/Collection/BillLine.php index 5091747577..8c3141170e 100644 --- a/app/Helpers/Collection/BillLine.php +++ b/app/Helpers/Collection/BillLine.php @@ -1,5 +1,5 @@ amount; + return $this->amount ?? '0'; } /** * @param string $amount */ - public function setAmount($amount) + public function setAmount(string $amount) { $this->amount = $amount; } @@ -46,7 +49,7 @@ class BillLine /** * @return BillModel */ - public function getBill() + public function getBill(): BillModel { return $this->bill; } @@ -54,7 +57,7 @@ class BillLine /** * @param BillModel $bill */ - public function setBill($bill) + public function setBill(BillModel $bill) { $this->bill = $bill; } @@ -62,7 +65,7 @@ class BillLine /** * @return string */ - public function getMax() + public function getMax(): string { return $this->max; } @@ -70,7 +73,7 @@ class BillLine /** * @param string $max */ - public function setMax($max) + public function setMax(string $max) { $this->max = $max; } @@ -78,7 +81,7 @@ class BillLine /** * @return string */ - public function getMin() + public function getMin(): string { return $this->min; } @@ -86,23 +89,39 @@ class BillLine /** * @param string $min */ - public function setMin($min) + public function setMin(string $min) { $this->min = $min; } + /** + * @return int + */ + public function getTransactionJournalId(): int + { + return $this->transactionJournalId ?? 0; + } + + /** + * @param int $transactionJournalId + */ + public function setTransactionJournalId(int $transactionJournalId) + { + $this->transactionJournalId = $transactionJournalId; + } + /** * @return boolean */ - public function isActive() + public function isActive(): bool { return $this->active; } /** - * @param boolean $active + * @param bool $active */ - public function setActive($active) + public function setActive(bool $active) { $this->active = $active; } @@ -110,15 +129,15 @@ class BillLine /** * @return boolean */ - public function isHit() + public function isHit(): bool { return $this->hit; } /** - * @param boolean $hit + * @param bool $hit */ - public function setHit($hit) + public function setHit(bool $hit) { $this->hit = $hit; } diff --git a/app/Helpers/Collection/Budget.php b/app/Helpers/Collection/Budget.php index 53548291de..92d864eb06 100644 --- a/app/Helpers/Collection/Budget.php +++ b/app/Helpers/Collection/Budget.php @@ -1,5 +1,5 @@ budgeted = bcadd($this->budgeted, $add); } /** - * @param float $add + * @param string $add */ - public function addLeft($add) + public function addLeft(string $add) { - $add = strval(round($add, 2)); - bcscale(2); + $add = strval(round($add, 2)); $this->left = bcadd($this->left, $add); } /** - * @param float $add + * @param string $add */ - public function addOverspent($add) + public function addOverspent(string $add) { - $add = strval(round($add, 2)); - bcscale(2); + $add = strval(round($add, 2)); $this->overspent = bcadd($this->overspent, $add); } /** - * @param float $add + * @param string $add */ - public function addSpent($add) + public function addSpent(string $add) { - $add = strval(round($add, 2)); - bcscale(2); + $add = strval(round($add, 2)); $this->spent = bcadd($this->spent, $add); } /** * @return \Illuminate\Support\Collection */ - public function getBudgetLines() + public function getBudgetLines(): Collection { return $this->budgetLines; } @@ -91,7 +87,7 @@ class Budget /** * @return string */ - public function getBudgeted() + public function getBudgeted(): string { return $this->budgeted; } @@ -99,7 +95,7 @@ class Budget /** * @param string $budgeted */ - public function setBudgeted($budgeted) + public function setBudgeted(string $budgeted) { $this->budgeted = $budgeted; } @@ -107,7 +103,7 @@ class Budget /** * @return string */ - public function getLeft() + public function getLeft(): string { return $this->left; } @@ -115,7 +111,7 @@ class Budget /** * @param string $left */ - public function setLeft($left) + public function setLeft(string $left) { $this->left = $left; } @@ -123,7 +119,7 @@ class Budget /** * @return string */ - public function getOverspent() + public function getOverspent(): string { return $this->overspent; } @@ -131,7 +127,7 @@ class Budget /** * @param string $overspent */ - public function setOverspent($overspent) + public function setOverspent(string $overspent) { $this->overspent = strval(round($overspent, 2)); } @@ -139,7 +135,7 @@ class Budget /** * @return string */ - public function getSpent() + public function getSpent(): string { return $this->spent; } @@ -147,7 +143,7 @@ class Budget /** * @param string $spent */ - public function setSpent($spent) + public function setSpent(string $spent) { $this->spent = strval(round($spent, 2)); } diff --git a/app/Helpers/Collection/BudgetLine.php b/app/Helpers/Collection/BudgetLine.php index f26950f9f9..ecda6ac7ca 100644 --- a/app/Helpers/Collection/BudgetLine.php +++ b/app/Helpers/Collection/BudgetLine.php @@ -1,5 +1,5 @@ budget; + return $this->budget ?? new BudgetModel; } /** * @param BudgetModel $budget */ - public function setBudget($budget) + public function setBudget(BudgetModel $budget) { $this->budget = $budget; } /** - * @return float + * @return string */ - public function getBudgeted() + public function getBudgeted(): string { return $this->budgeted; } /** - * @param float $budgeted + * @param string $budgeted */ - public function setBudgeted($budgeted) + public function setBudgeted(string $budgeted) { $this->budgeted = $budgeted; } /** - * @return float + * @return string */ - public function getLeft() + public function getLeft(): string { return $this->left; } /** - * @param float $left + * @param string $left */ - public function setLeft($left) + public function setLeft(string $left) { $this->left = $left; } /** - * @return float + * @return string */ - public function getOverspent() + public function getOverspent(): string { return $this->overspent; } /** - * @param float $overspent + * @param string $overspent */ - public function setOverspent($overspent) + public function setOverspent(string $overspent) { $this->overspent = $overspent; } @@ -95,31 +95,31 @@ class BudgetLine /** * @return LimitRepetition */ - public function getRepetition() + public function getRepetition(): LimitRepetition { - return $this->repetition; + return $this->repetition ?? new LimitRepetition; } /** * @param LimitRepetition $repetition */ - public function setRepetition($repetition) + public function setRepetition(LimitRepetition $repetition) { $this->repetition = $repetition; } /** - * @return float + * @return string */ - public function getSpent() + public function getSpent(): string { return $this->spent; } /** - * @param float $spent + * @param string $spent */ - public function setSpent($spent) + public function setSpent(string $spent) { $this->spent = $spent; } diff --git a/app/Helpers/Collection/Category.php b/app/Helpers/Collection/Category.php index 6e243a7631..b8196fe05a 100644 --- a/app/Helpers/Collection/Category.php +++ b/app/Helpers/Collection/Category.php @@ -1,5 +1,5 @@ total = bcadd($this->total, $add); } /** * @return Collection */ - public function getCategories() + public function getCategories(): Collection { $set = $this->categories->sortBy( function (CategoryModel $category) { @@ -69,7 +68,7 @@ class Category /** * @return string */ - public function getTotal() + public function getTotal(): string { return strval(round($this->total, 2)); } diff --git a/app/Helpers/Collection/Expense.php b/app/Helpers/Collection/Expense.php index ce489c5118..b241f04f9f 100644 --- a/app/Helpers/Collection/Expense.php +++ b/app/Helpers/Collection/Expense.php @@ -1,5 +1,5 @@ account_id; $amount = strval(round($entry->journalAmount, 2)); @@ -58,11 +57,10 @@ class Expense } /** - * @param $add + * @param string $add */ - public function addToTotal($add) + public function addToTotal(string $add) { - bcscale(2); $add = strval(round($add, 2)); @@ -80,7 +78,7 @@ class Expense /** * @return Collection */ - public function getExpenses() + public function getExpenses(): Collection { $set = $this->expenses->sortBy( function (stdClass $object) { @@ -94,7 +92,7 @@ class Expense /** * @return string */ - public function getTotal() + public function getTotal(): string { return strval(round($this->total, 2)); } diff --git a/app/Helpers/Collection/Income.php b/app/Helpers/Collection/Income.php index 6a66de8fef..17017e5129 100644 --- a/app/Helpers/Collection/Income.php +++ b/app/Helpers/Collection/Income.php @@ -1,5 +1,5 @@ id = $accountId; $this->incomes->put($accountId, $newObject); } else { - bcscale(2); $existing = $this->incomes->get($accountId); $existing->amount = bcadd($existing->amount, $entry->journalAmount); $existing->count++; @@ -54,19 +53,18 @@ class Income } /** - * @param $add + * @param string $add */ - public function addToTotal($add) + public function addToTotal(string $add) { - $add = strval(round($add, 2)); - bcscale(2); + $add = strval(round($add, 2)); $this->total = bcadd($this->total, $add); } /** * @return Collection */ - public function getIncomes() + public function getIncomes(): Collection { $set = $this->incomes->sortByDesc( function (stdClass $object) { @@ -80,7 +78,7 @@ class Income /** * @return string */ - public function getTotal() + public function getTotal(): string { return strval(round($this->total, 2)); } diff --git a/app/Helpers/Csv/Converter/AccountId.php b/app/Helpers/Csv/Converter/AccountId.php index c489fa36a4..00912809bd 100644 --- a/app/Helpers/Csv/Converter/AccountId.php +++ b/app/Helpers/Csv/Converter/AccountId.php @@ -1,4 +1,5 @@ mapped[$this->index][$this->value])) { @@ -30,6 +31,9 @@ class AccountId extends BasicConverter implements ConverterInterface if (!is_null($account)) { Log::debug('Found ' . $account->accountType->type . ' named "******" with ID: ' . $this->value . ' (not mapped) '); + } else { + // new account to prevent TypeErrors. + $account = new Account; } } diff --git a/app/Helpers/Csv/Converter/Amount.php b/app/Helpers/Csv/Converter/Amount.php index 7ec25ce17e..6aade561ab 100644 --- a/app/Helpers/Csv/Converter/Amount.php +++ b/app/Helpers/Csv/Converter/Amount.php @@ -1,5 +1,5 @@ value)) { - return $this->value; + return strval($this->value); } - return 0; + return '0'; } } diff --git a/app/Helpers/Csv/Converter/AmountComma.php b/app/Helpers/Csv/Converter/AmountComma.php index 5fed5587b9..37b4d95841 100644 --- a/app/Helpers/Csv/Converter/AmountComma.php +++ b/app/Helpers/Csv/Converter/AmountComma.php @@ -1,5 +1,5 @@ value); + $value = str_replace(',', '.', strval($this->value)); if (is_numeric($value)) { - return floatval($value); + return strval($value); } - return 0; + return '0'; } } diff --git a/app/Helpers/Csv/Converter/AssetAccountIban.php b/app/Helpers/Csv/Converter/AssetAccountIban.php index 311e529a47..d506b2d052 100644 --- a/app/Helpers/Csv/Converter/AssetAccountIban.php +++ b/app/Helpers/Csv/Converter/AssetAccountIban.php @@ -1,10 +1,10 @@ mapped[$this->index][$this->value])) { $account = Auth::user()->accounts()->find($this->mapped[$this->index][$this->value]); @@ -27,32 +28,41 @@ class AssetAccountIban extends BasicConverter implements ConverterInterface } if (strlen($this->value) > 0) { // find or create new account: - $account = $this->findAccount(); - $accountType = AccountType::where('type', 'Asset account')->first(); + $account = $this->findAccount(); - if (is_null($account)) { + if (is_null($account->id)) { // create it if doesn't exist. - $account = Account::firstOrCreateEncrypted( - [ - 'name' => $this->value, - 'iban' => $this->value, - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'active' => 1, - ] - ); + + $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + + ]; + + $account = $repository->store($accountData); } return $account; } - return null; + return new Account; } /** - * @return Account|null + * @return Account */ - protected function findAccount() + protected function findAccount(): Account { $set = Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->get(['accounts.*']); /** @var Account $entry */ @@ -63,6 +73,6 @@ class AssetAccountIban extends BasicConverter implements ConverterInterface } } - return null; + return new Account; } } diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php index 87804875af..407e54fc6d 100644 --- a/app/Helpers/Csv/Converter/AssetAccountName.php +++ b/app/Helpers/Csv/Converter/AssetAccountName.php @@ -1,10 +1,10 @@ first(); - $set = Auth::user()->accounts()->accountTypeIn(['Asset account', 'Default account'])->get(); + $set = Auth::user()->accounts()->accountTypeIn(['Asset account', 'Default account'])->get(); /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name == $this->value) { @@ -36,15 +35,25 @@ class AssetAccountName extends BasicConverter implements ConverterInterface } // create it if doesnt exist. - $account = Account::firstOrCreateEncrypted( - [ - 'name' => $this->value, - 'iban' => '', - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'active' => 1, - ] - ); + + $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + + ]; + + $account = $repository->store($accountData); return $account; } diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php new file mode 100644 index 0000000000..803fc9763f --- /dev/null +++ b/app/Helpers/Csv/Converter/AssetAccountNumber.php @@ -0,0 +1,89 @@ +mapped[$this->index][$this->value])) { + $account = Auth::user()->accounts()->find($this->mapped[$this->index][$this->value]); + + return $account; + } + // if not, search for it (or create it): + $value = $this->value ?? ''; + if (strlen($value) > 0) { + // find or create new account: + $account = $this->findAccount(); + + if (is_null($account->id)) { + // create it if doesn't exist. + $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + + + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + + ]; + + $account = $repository->store($accountData); + } + + return $account; + } + + return null; + } + + /** + * @return Account + */ + protected function findAccount(): Account + { + $set = Auth::user()->accounts()->with(['accountmeta'])->accountTypeIn(['Default account', 'Asset account'])->get(['accounts.*']); + /** @var Account $entry */ + foreach ($set as $entry) { + $accountNumber = $entry->getMeta('accountNumber'); + if ($accountNumber == $this->value) { + + return $entry; + } + } + + return new Account; + } +} diff --git a/app/Helpers/Csv/Converter/BasicConverter.php b/app/Helpers/Csv/Converter/BasicConverter.php index 2c7bff6f2a..b1a262cae8 100644 --- a/app/Helpers/Csv/Converter/BasicConverter.php +++ b/app/Helpers/Csv/Converter/BasicConverter.php @@ -1,5 +1,5 @@ mapped[$this->index][$this->value])) { - $budget = Auth::user()->budgets()->find($this->mapped[$this->index][$this->value]); + $budget = Auth::user()->budgets()->find($this->mapped[$this->index][$this->value]); // see issue #180 } else { - $budget = Budget::firstOrCreateEncrypted( - [ - 'name' => $this->value, - 'user_id' => Auth::user()->id, - 'active' => true, - ] - ); + $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $budget = $repository->store(['name' => $this->value, 'user' => Auth::user()->id]); } return $budget; diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php index c5dcf760d4..f205f9edfe 100644 --- a/app/Helpers/Csv/Converter/CategoryId.php +++ b/app/Helpers/Csv/Converter/CategoryId.php @@ -1,4 +1,5 @@ mapped[$this->index][$this->value])) { $category = Auth::user()->categories()->find($this->mapped[$this->index][$this->value]); } else { - $category = Category::firstOrCreateEncrypted( + $category = Category::firstOrCreateEncrypted( // See issue #180 [ 'name' => $this->value, 'user_id' => Auth::user()->id, diff --git a/app/Helpers/Csv/Converter/ConverterInterface.php b/app/Helpers/Csv/Converter/ConverterInterface.php index 1eb2e37db9..643d17b6c1 100644 --- a/app/Helpers/Csv/Converter/ConverterInterface.php +++ b/app/Helpers/Csv/Converter/ConverterInterface.php @@ -1,5 +1,5 @@ value); } catch (InvalidArgumentException $e) { diff --git a/app/Helpers/Csv/Converter/Description.php b/app/Helpers/Csv/Converter/Description.php index bb5626e251..98cfd1b4bf 100644 --- a/app/Helpers/Csv/Converter/Description.php +++ b/app/Helpers/Csv/Converter/Description.php @@ -1,5 +1,5 @@ data['description'] . ' ' . $this->value); + $description = $this->data['description'] ?? ''; + + return trim($description . ' ' . $this->value); } } diff --git a/app/Helpers/Csv/Converter/INGDebetCredit.php b/app/Helpers/Csv/Converter/INGDebetCredit.php new file mode 100644 index 0000000000..e41aee16ac --- /dev/null +++ b/app/Helpers/Csv/Converter/INGDebetCredit.php @@ -0,0 +1,34 @@ +value === 'Af') { + return -1; + } + + return 1; + } +} diff --git a/app/Helpers/Csv/Converter/Ignore.php b/app/Helpers/Csv/Converter/Ignore.php index a9dff94088..30cf747a86 100644 --- a/app/Helpers/Csv/Converter/Ignore.php +++ b/app/Helpers/Csv/Converter/Ignore.php @@ -1,5 +1,5 @@ value); foreach ($strings as $string) { - $tag = Tag::firstOrCreateEncrypted( + $tag = Tag::firstOrCreateEncrypted( // See issue #180 [ 'tag' => $string, 'tagMode' => 'nothing', diff --git a/app/Helpers/Csv/Converter/TagsSpace.php b/app/Helpers/Csv/Converter/TagsSpace.php index 323f6005cc..c0e7b5c4f8 100644 --- a/app/Helpers/Csv/Converter/TagsSpace.php +++ b/app/Helpers/Csv/Converter/TagsSpace.php @@ -1,5 +1,5 @@ value); foreach ($strings as $string) { - $tag = Tag::firstOrCreateEncrypted( + $tag = Tag::firstOrCreateEncrypted( // See issue #180 [ 'tag' => $string, 'tagMode' => 'nothing', diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php index 32e1ca4477..b45edd5211 100644 --- a/app/Helpers/Csv/Data.php +++ b/app/Helpers/Csv/Data.php @@ -1,9 +1,11 @@ csvFileContent; + return $this->csvFileContent ?? ''; } /** * * @param string $csvFileContent */ - public function setCsvFileContent($csvFileContent) + public function setCsvFileContent(string $csvFileContent) { $this->csvFileContent = $csvFileContent; } @@ -82,7 +84,7 @@ class Data * * @param string $csvFileLocation */ - public function setCsvFileLocation($csvFileLocation) + public function setCsvFileLocation(string $csvFileLocation) { Session::put('csv-file', $csvFileLocation); $this->csvFileLocation = $csvFileLocation; @@ -99,9 +101,9 @@ class Data /** * - * @param mixed $dateFormat + * @param string $dateFormat */ - public function setDateFormat($dateFormat) + public function setDateFormat(string $dateFormat) { Session::put('csv-date-format', $dateFormat); $this->dateFormat = $dateFormat; @@ -120,7 +122,7 @@ class Data * * @param string $delimiter */ - public function setDelimiter($delimiter) + public function setDelimiter(string $delimiter) { Session::put('csv-delimiter', $delimiter); $this->delimiter = $delimiter; @@ -170,7 +172,7 @@ class Data */ public function getReader() { - if (strlen($this->csvFileContent) === 0) { + if (!is_null($this->csvFileContent) && strlen($this->csvFileContent) === 0) { $this->loadCsvFile(); } @@ -233,7 +235,7 @@ class Data * * @param bool $hasHeaders */ - public function setHasHeaders($hasHeaders) + public function setHasHeaders(bool $hasHeaders) { Session::put('csv-has-headers', $hasHeaders); $this->hasHeaders = $hasHeaders; @@ -243,7 +245,7 @@ class Data * * @param int $importAccount */ - public function setImportAccount($importAccount) + public function setImportAccount(int $importAccount) { Session::put('csv-import-account', $importAccount); $this->importAccount = $importAccount; @@ -252,7 +254,8 @@ class Data protected function loadCsvFile() { $file = $this->getCsvFileLocation(); - $content = file_get_contents($file); + $disk = Storage::disk('upload'); + $content = $disk->get($file); $contentDecrypted = Crypt::decrypt($content); $this->setCsvFileContent($contentDecrypted); } @@ -260,63 +263,63 @@ class Data protected function sessionCsvFileLocation() { if (Session::has('csv-file')) { - $this->csvFileLocation = (string)Session::get('csv-file'); + $this->csvFileLocation = (string)session('csv-file'); } } protected function sessionDateFormat() { if (Session::has('csv-date-format')) { - $this->dateFormat = (string)Session::get('csv-date-format'); + $this->dateFormat = (string)session('csv-date-format'); } } protected function sessionDelimiter() { if (Session::has('csv-delimiter')) { - $this->delimiter = Session::get('csv-delimiter'); + $this->delimiter = session('csv-delimiter'); } } protected function sessionHasHeaders() { if (Session::has('csv-has-headers')) { - $this->hasHeaders = (bool)Session::get('csv-has-headers'); + $this->hasHeaders = (bool)session('csv-has-headers'); } } protected function sessionImportAccount() { if (Session::has('csv-import-account')) { - $this->importAccount = intval(Session::get('csv-import-account')); + $this->importAccount = intval(session('csv-import-account')); } } protected function sessionMap() { if (Session::has('csv-map')) { - $this->map = (array)Session::get('csv-map'); + $this->map = (array)session('csv-map'); } } protected function sessionMapped() { if (Session::has('csv-mapped')) { - $this->mapped = (array)Session::get('csv-mapped'); + $this->mapped = (array)session('csv-mapped'); } } protected function sessionRoles() { if (Session::has('csv-roles')) { - $this->roles = (array)Session::get('csv-roles'); + $this->roles = (array)session('csv-roles'); } } protected function sessionSpecifix() { if (Session::has('csv-specifix')) { - $this->specifix = (array)Session::get('csv-specifix'); + $this->specifix = (array)session('csv-specifix'); } } } diff --git a/app/Helpers/Csv/Importer.php b/app/Helpers/Csv/Importer.php index 0386f71819..ac1bcdffb4 100644 --- a/app/Helpers/Csv/Importer.php +++ b/app/Helpers/Csv/Importer.php @@ -1,5 +1,5 @@ data = $data; } @@ -147,7 +147,6 @@ class Importer */ protected function createTransactionJournal() { - bcscale(2); $date = $this->importData['date']; if (is_null($this->importData['date'])) { $date = $this->importData['date-rent']; @@ -169,7 +168,7 @@ class Importer // second transaction $accountId = $this->importData['opposing-account-object']->id; // create second transaction: - $amount = bcmul($this->importData['amount'], -1); + $amount = bcmul($this->importData['amount'], '-1'); $transaction = Transaction::create(['transaction_journal_id' => $journal->id, 'account_id' => $accountId, 'amount' => $amount]); $errors = $transaction->getErrors()->merge($errors); } @@ -187,7 +186,7 @@ class Importer // some debug info: $journalId = $journal->id; - $type = $journal->getTransactionType(); + $type = $journal->transaction_type_type ?? $journal->transactionType->type; /** @var Account $asset */ $asset = $this->importData['asset-account-object']; /** @var Account $opposing */ @@ -195,7 +194,7 @@ class Importer Log::info('Created journal #' . $journalId . ' of type ' . $type . '!'); Log::info('Asset account #' . $asset->id . ' lost/gained: ' . $this->importData['amount']); - Log::info($opposing->accountType->type . ' #' . $opposing->id . ' lost/gained: ' . bcmul($this->importData['amount'], -1)); + Log::info($opposing->accountType->type . ' #' . $opposing->id . ' lost/gained: ' . bcmul($this->importData['amount'], '-1')); return $journal; } @@ -218,17 +217,17 @@ class Importer } /** - * @param $row + * @param array $row * * @throws FireflyException * @return string|bool */ - protected function importRow($row) + protected function importRow(array $row) { $data = $this->getFiller(); // These fields are necessary to create a new transaction journal. Some are optional foreach ($row as $index => $value) { - $role = isset($this->roles[$index]) ? $this->roles[$index] : '_ignore'; + $role = $this->roles[$index] ?? '_ignore'; $class = Config::get('csv.roles.' . $role . '.converter'); $field = Config::get('csv.roles.' . $role . '.field'); @@ -266,7 +265,7 @@ class Importer * * @return bool */ - protected function parseRow($index) + protected function parseRow(int $index) { return (($this->data->hasHeaders() && $index >= 1) || !$this->data->hasHeaders()); } @@ -296,7 +295,8 @@ class Importer foreach ($set as $className) { /** @var PostProcessorInterface $postProcessor */ $postProcessor = app('FireflyIII\Helpers\Csv\PostProcessing\\' . $className); - $postProcessor->setData($this->importData); + $array = $this->importData ?? []; + $postProcessor->setData($array); Log::debug('Now post-process processor named ' . $className . ':'); $this->importData = $postProcessor->process(); } @@ -343,7 +343,9 @@ class Importer */ protected function validateData() { - if (is_null($this->importData['date']) && is_null($this->importData['date-rent'])) { + $date = $this->importData['date'] ?? null; + $rentDate = $this->importData['date-rent'] ?? null; + if (is_null($date) && is_null($rentDate)) { return 'No date value for this row.'; } if (is_null($this->importData['opposing-account-object'])) { @@ -368,8 +370,8 @@ class Importer /** @var Rule $rule */ foreach ($group->rules as $rule) { - $processor = new Processor($rule, $journal); - $processor->handle(); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { break; } diff --git a/app/Helpers/Csv/Mapper/AnyAccount.php b/app/Helpers/Csv/Mapper/AnyAccount.php index dcfaefeec5..573e025f04 100644 --- a/app/Helpers/Csv/Mapper/AnyAccount.php +++ b/app/Helpers/Csv/Mapper/AnyAccount.php @@ -1,5 +1,5 @@ name; - if (strlen($account->iban) > 0) { + $iban = $account->iban ?? ''; + if (strlen($iban) > 0) { $name .= ' (' . $account->iban . ')'; } $list[$account->id] = $name; diff --git a/app/Helpers/Csv/Mapper/Bill.php b/app/Helpers/Csv/Mapper/Bill.php index f94c7ccd7e..d0f9cb8c3d 100644 --- a/app/Helpers/Csv/Mapper/Bill.php +++ b/app/Helpers/Csv/Mapper/Bill.php @@ -1,5 +1,5 @@ data['amount'] = bcmul($this->data['amount'], $this->data['amount-modifier']); + $amount = $this->data['amount'] ?? '0'; + $modifier = strval($this->data['amount-modifier']); + $this->data['amount'] = bcmul($amount, $modifier); return $this->data; } diff --git a/app/Helpers/Csv/PostProcessing/AssetAccount.php b/app/Helpers/Csv/PostProcessing/AssetAccount.php index a3a91f2de4..7ee7986b76 100644 --- a/app/Helpers/Csv/PostProcessing/AssetAccount.php +++ b/app/Helpers/Csv/PostProcessing/AssetAccount.php @@ -1,8 +1,9 @@ checkIbanString(); if (!is_null($result)) { return $result; } + // no object still? maybe we can find the account by name. $result = $this->checkNameString(); if (!is_null($result)) { return $result; } + // still nothing? Perhaps the account number can lead us to an account: + $result = $this->checkAccountNumberString(); + if (!is_null($result)) { + return $result; + } return null; } @@ -51,17 +59,18 @@ class AssetAccount implements PostProcessorInterface } /** - * @return array + * @return array|null */ - protected function checkIdNameObject() + protected function checkAccountNumberString() { - if ($this->data['asset-account-id'] instanceof Account) { // first priority. try to find the account based on ID, if any - $this->data['asset-account-object'] = $this->data['asset-account-id']; + $accountNumber = $this->data['asset-account-number'] ?? null; + if ($accountNumber instanceof Account) { // fourth: try to find account based on name, if any. + $this->data['asset-account-object'] = $accountNumber; return $this->data; } - if ($this->data['asset-account-iban'] instanceof Account) { // second: try to find the account based on IBAN, if any. - $this->data['asset-account-object'] = $this->data['asset-account-iban']; + if (is_string($accountNumber)) { // it's an actual account number + $this->data['asset-account-object'] = $this->parseAccountNumberString(); return $this->data; } @@ -74,8 +83,9 @@ class AssetAccount implements PostProcessorInterface */ protected function checkIbanString() { + $iban = $this->data['asset-account-iban'] ?? ''; $rules = ['iban' => 'iban']; - $check = ['iban' => $this->data['asset-account-iban']]; + $check = ['iban' => $iban]; $validator = Validator::make($check, $rules); if (!$validator->fails()) { $this->data['asset-account-object'] = $this->parseIbanString(); @@ -87,21 +97,52 @@ class AssetAccount implements PostProcessorInterface } /** - * @return Account|null + * @return array */ - protected function parseIbanString() + protected function checkIdNameObject() { - // create by name and/or iban. - $accounts = Auth::user()->accounts()->get(); - foreach ($accounts as $entry) { - if ($entry->iban == $this->data['asset-account-iban']) { + $accountId = $this->data['asset-account-id'] ?? null; + $accountIban = $this->data['asset-account-iban'] ?? null; + $accountNumber = $this->data['asset-account-number'] ?? null; + if ($accountId instanceof Account) { // first priority. try to find the account based on ID, if any + $this->data['asset-account-object'] = $accountId; - return $entry; - } + return $this->data; } - $account = $this->createAccount(); + if ($accountIban instanceof Account) { // second: try to find the account based on IBAN, if any. + $this->data['asset-account-object'] = $accountIban; - return $account; + return $this->data; + } + + if ($accountNumber instanceof Account) { // second: try to find the account based on account number, if any. + $this->data['asset-account-object'] = $accountNumber; + + return $this->data; + } + + + return null; + } + + /** + * @return array|null + */ + protected function checkNameString() + { + $accountName = $this->data['asset-account-name'] ?? null; + if ($accountName instanceof Account) { // third: try to find account based on name, if any. + $this->data['asset-account-object'] = $accountName; + + return $this->data; + } + if (is_string($accountName)) { + $this->data['asset-account-object'] = $this->parseNameString(); + + return $this->data; + } + + return null; } /** @@ -110,16 +151,17 @@ class AssetAccount implements PostProcessorInterface protected function createAccount() { $accountType = $this->getAccountType(); + $name = $this->data['asset-account-name'] ?? ''; + $iban = $this->data['asset-account-iban'] ?? ''; - // create if not exists: - $name = is_string($this->data['asset-account-name']) && strlen($this->data['asset-account-name']) > 0 ? $this->data['asset-account-name'] - : $this->data['asset-account-iban']; + // create if not exists: // See issue #180 + $name = strlen($name) > 0 ? $name : $iban; $account = Account::firstOrCreateEncrypted( [ 'user_id' => Auth::user()->id, 'account_type_id' => $accountType->id, 'name' => $name, - 'iban' => $this->data['asset-account-iban'], + 'iban' => $iban, 'active' => true, ] ); @@ -137,22 +179,22 @@ class AssetAccount implements PostProcessorInterface } /** - * @return array|null + * @return Account|null */ - protected function checkNameString() + protected function parseIbanString() { - if ($this->data['asset-account-name'] instanceof Account) { // third: try to find account based on name, if any. - $this->data['asset-account-object'] = $this->data['asset-account-name']; + // create by name and/or iban. + $iban = $this->data['asset-account-iban'] ?? ''; + $accounts = Auth::user()->accounts()->get(); + foreach ($accounts as $entry) { + if ($iban !== '' && $entry->iban === $iban) { - return $this->data; + return $entry; + } } - if (is_string($this->data['asset-account-name'])) { - $this->data['asset-account-object'] = $this->parseNameString(); + $account = $this->createAccount(); - return $this->data; - } - - return null; + return $account; } /** @@ -170,6 +212,7 @@ class AssetAccount implements PostProcessorInterface } } // create if not exists: + // See issue #180 $account = Account::firstOrCreateEncrypted( [ 'user_id' => Auth::user()->id, @@ -182,4 +225,43 @@ class AssetAccount implements PostProcessorInterface return $account; } + + /** + * @return Account|null + */ + private function parseAccountNumberString() + { + $accountNumber = $this->data['asset-account-number'] ?? ''; + $accountType = $this->getAccountType(); + $accounts = Auth::user()->accounts()->with(['accountmeta'])->where('account_type_id', $accountType->id)->get(); + /** @var Account $entry */ + foreach ($accounts as $entry) { + $metaFieldValue = $entry->getMeta('accountNumber'); + if ($metaFieldValue === $accountNumber && $metaFieldValue !== '') { + Log::debug('Found an asset account with this account number (#' . $entry->id . ')'); + + return $entry; + } + } + // create new if not exists and return that one: + /** @var \FireflyIII\Repositories\Account\AccountRepositoryInterface $repository */ + $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountData = [ + 'name' => $accountNumber, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $accountNumber, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + ]; + $account = $repository->store($accountData); + + return $account; + } } diff --git a/app/Helpers/Csv/PostProcessing/Bill.php b/app/Helpers/Csv/PostProcessing/Bill.php index 22a1993edd..de81b8b3c4 100644 --- a/app/Helpers/Csv/PostProcessing/Bill.php +++ b/app/Helpers/Csv/PostProcessing/Bill.php @@ -1,5 +1,5 @@ data['description'] = trim($this->data['description']); + $description = $this->data['description'] ?? ''; + $this->data['description'] = trim($description); if (strlen($this->data['description']) == 0) { $this->data['description'] = trans('firefly.csv_empty_description'); } diff --git a/app/Helpers/Csv/PostProcessing/OpposingAccount.php b/app/Helpers/Csv/PostProcessing/OpposingAccount.php index 2a0e3f6e2b..0bc038bba3 100644 --- a/app/Helpers/Csv/PostProcessing/OpposingAccount.php +++ b/app/Helpers/Csv/PostProcessing/OpposingAccount.php @@ -1,5 +1,5 @@ data['opposing-account-name']) && strlen($this->data['opposing-account-name']) > 0 ? $this->data['opposing-account-name'] : $this->data['opposing-account-iban']; - $account = Account::firstOrCreateEncrypted( + $account = Account::firstOrCreateEncrypted( // See issue #180 [ 'user_id' => Auth::user()->id, 'account_type_id' => $accountType->id, @@ -195,7 +195,7 @@ class OpposingAccount implements PostProcessorInterface } } // create if not exists: - $account = Account::firstOrCreateEncrypted( + $account = Account::firstOrCreateEncrypted( // See issue #180 [ 'user_id' => Auth::user()->id, 'account_type_id' => $accountType->id, diff --git a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php index 88c0b60559..a6a793c3d5 100644 --- a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php +++ b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php @@ -1,5 +1,5 @@ data = $data; } @@ -57,7 +57,7 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface /** * @param array $row */ - public function setRow($row) + public function setRow(array $row) { $this->row = $row; } @@ -95,7 +95,12 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface // description and opposing account will be the same. $this->data['opposing-account-name'] = $matches[4]; - $this->data['description'] = $matches[4]; + + if ($matches[1] == 'GEA') { + $this->data['description'] = 'GEA ' . $matches[4]; + } else { + $this->data['description'] = $matches[4]; + } return true; } @@ -114,20 +119,28 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface if (preg_match('/^SEPA(.{28})/', $this->data['description'], $matches)) { Log::debug('AbnAmroSpecifix: Description is structured as SEPA plain description.'); + $type = $matches[1]; + $reference = ''; + $name = ''; + $newDescription = ''; + // SEPA plain descriptions contain several key-value pairs, split by a colon - preg_match_all('/([A-Za-z]+(?=:\s)):\s([A-Za-z 0-9._#-]+(?=\s))/', $this->data['description'], $matches, PREG_SET_ORDER); + preg_match_all('/([A-Za-z]+(?=:\s)):\s([A-Za-z 0-9._#-]+(?=\s|$))/', $this->data['description'], $matches, PREG_SET_ORDER); if (is_array($matches)) { foreach ($matches as $match) { $key = $match[1]; $value = trim($match[2]); - + Log::debug('SEPA: ' . $key . ' - ' . $value); switch (strtoupper($key)) { case 'OMSCHRIJVING': - $this->data['description'] = $value; + $newDescription = $value; break; case 'NAAM': - $this->data['opposing-account-name'] = $value; + $this->data['opposing-account-name'] = $name = $value; + break; + case 'KENMERK': + $reference = $value; break; case 'IBAN': $this->data['opposing-account-iban'] = $value; @@ -138,6 +151,14 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface } } + // Set a new description for the current transaction. If none was given + // set the description to type, name and reference + if ($newDescription) { + $this->data['description'] = $newDescription; + } else { + $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); + } + return true; } @@ -155,6 +176,13 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface if (preg_match_all('!\/([A-Z]{3,4})\/([^/]*)!', $this->data['description'], $matches, PREG_SET_ORDER)) { Log::debug('AbnAmroSpecifix: Description is structured as TRTP format.'); + $type = ''; + $name = ''; + $reference = ''; + $newDescription = ''; + + // Search for properties specified in the TRTP format. If no description + // is provided, use the type, name and reference as new description if (is_array($matches)) { foreach ($matches as $match) { $key = $match[1]; @@ -162,18 +190,32 @@ class AbnAmroDescription extends Specifix implements SpecifixInterface switch (strtoupper($key)) { case 'NAME': - $this->data['opposing-account-name'] = $value; + $this->data['opposing-account-name'] = $name = $value; break; case 'REMI': - $this->data['description'] = $value; + $newDescription = $value; break; case 'IBAN': $this->data['opposing-account-iban'] = $value; break; + case 'EREF': + $reference = $value; + break; + case 'TRTP': + $type = $value; + break; default: // Ignore the rest } } + + // Set a new description for the current transaction. If none was given + // set the description to type, name and reference + if ($newDescription) { + $this->data['description'] = $newDescription; + } else { + $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); + } } return true; diff --git a/app/Helpers/Csv/Specifix/Dummy.php b/app/Helpers/Csv/Specifix/Dummy.php index 915e87bd37..2e4263a4d5 100644 --- a/app/Helpers/Csv/Specifix/Dummy.php +++ b/app/Helpers/Csv/Specifix/Dummy.php @@ -1,5 +1,5 @@ data = $data; } @@ -43,7 +43,7 @@ class Dummy extends Specifix implements SpecifixInterface /** * @param array $row */ - public function setRow($row) + public function setRow(array $row) { $this->row = $row; } diff --git a/app/Helpers/Csv/Specifix/RabobankDescription.php b/app/Helpers/Csv/Specifix/RabobankDescription.php index a8fced085a..249fc00cfe 100644 --- a/app/Helpers/Csv/Specifix/RabobankDescription.php +++ b/app/Helpers/Csv/Specifix/RabobankDescription.php @@ -1,5 +1,5 @@ data = $data; } @@ -48,7 +48,7 @@ class RabobankDescription extends Specifix implements SpecifixInterface /** * @param array $row */ - public function setRow($row) + public function setRow(array $row) { $this->row = $row; } diff --git a/app/Helpers/Csv/Specifix/Specifix.php b/app/Helpers/Csv/Specifix/Specifix.php index 2c2a191811..fa51625e8e 100644 --- a/app/Helpers/Csv/Specifix/Specifix.php +++ b/app/Helpers/Csv/Specifix/Specifix.php @@ -1,4 +1,5 @@ processorType = $processorType; diff --git a/app/Helpers/Csv/Specifix/SpecifixInterface.php b/app/Helpers/Csv/Specifix/SpecifixInterface.php index 34e9a3e833..b6534fbe51 100644 --- a/app/Helpers/Csv/Specifix/SpecifixInterface.php +++ b/app/Helpers/Csv/Specifix/SpecifixInterface.php @@ -1,4 +1,5 @@ getMap(); $options[$index] = $set; @@ -150,20 +153,21 @@ class Wizard implements WizardInterface } /** - * @param $path + * @param string $path * * @return string */ - public function storeCsvFile($path) + public function storeCsvFile(string $path) { $time = str_replace(' ', '-', microtime()); $fileName = 'csv-upload-' . Auth::user()->id . '-' . $time . '.csv.encrypted'; - $fullPath = storage_path('upload') . DIRECTORY_SEPARATOR . $fileName; - $content = file_get_contents($path); + $disk = Storage::disk('upload'); + $file = new SplFileObject($path, 'r'); + $content = $file->fread($file->getSize()); $contentEncrypted = Crypt::encrypt($content); - file_put_contents($fullPath, $contentEncrypted); + $disk->put($fileName, $contentEncrypted); - return $fullPath; + return $fileName; } @@ -188,7 +192,7 @@ class Wizard implements WizardInterface * * @return bool */ - protected function useRow($hasHeaders, $index) + protected function useRow(bool $hasHeaders, int $index) { return ($hasHeaders && $index > 1) || !$hasHeaders; } diff --git a/app/Helpers/Csv/WizardInterface.php b/app/Helpers/Csv/WizardInterface.php index 6257a85694..815a432a43 100644 --- a/app/Helpers/Csv/WizardInterface.php +++ b/app/Helpers/Csv/WizardInterface.php @@ -1,4 +1,5 @@ startOfFiscalYear($date); + if ($this->useCustomFiscalYear === true) { + // add 1 year and sub 1 day + $endDate->addYear(); + $endDate->subDay(); + } else { + $endDate->endOfYear(); + } + + + return $endDate; + } + /** * @param Carbon $date * @@ -52,27 +74,7 @@ class FiscalHelper implements FiscalHelperInterface } else { $startDate->startOfYear(); } + return $startDate; } - - /** - * @param Carbon $date - * - * @return Carbon date object - */ - public function endOfFiscalYear(Carbon $date) - { - // get start of fiscal year for passed date - $endDate = $this->startOfFiscalYear($date); - if ($this->useCustomFiscalYear === true) { - // add 1 year and sub 1 day - $endDate->addYear(); - $endDate->subDay(); - } else { - $endDate->endOfYear(); - } - - - return $endDate; - } } diff --git a/app/Helpers/FiscalHelperInterface.php b/app/Helpers/FiscalHelperInterface.php index f4ec6fba1a..b812fb9324 100644 --- a/app/Helpers/FiscalHelperInterface.php +++ b/app/Helpers/FiscalHelperInterface.php @@ -1,4 +1,5 @@ '

There is no help for this route!

', + 'text' => '

' . strval(trans('firefly.route_has_no_help')) . '

', 'title' => $title, ]; - try { - $content['text'] = file_get_contents($uri); - } catch (ErrorException $e) { - Log::error(trim($e->getMessage())); + + Log::debug('Going to get from Github: ' . $uri); + + $result = Requests::get($uri); + + Log::debug('Status code was ' . $result->status_code . '.'); + + if ($result->status_code === 200) { + $content['text'] = $result->body; } + + if (strlen(trim($content['text'])) == 0) { - $content['text'] = '

There is no help for this route.

'; + Log::debug('No actual help text for this route (even though a page was found).'); + $content['text'] = '

' . strval(trans('firefly.route_has_no_help')) . '

'; } $converter = new CommonMarkConverter(); $content['text'] = $converter->convertToHtml($content['text']); @@ -62,11 +70,11 @@ class Help implements HelpInterface /** * @codeCoverageIgnore * - * @param $route + * @param string $route * * @return bool */ - public function hasRoute($route) + public function hasRoute(string $route):bool { return Route::has($route); } @@ -74,11 +82,11 @@ class Help implements HelpInterface /** * @codeCoverageIgnore * - * @param $route + * @param string $route * * @return bool */ - public function inCache($route) + public function inCache(string $route):bool { return Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text'); } @@ -86,12 +94,12 @@ class Help implements HelpInterface /** * @codeCoverageIgnore * - * @param $route - * @param array $content + * @param string $route + * @param array $content * * @internal param $title */ - public function putInCache($route, array $content) + public function putInCache(string $route, array $content) { Cache::put('help.' . $route . '.text', $content['text'], 10080); // a week. Cache::put('help.' . $route . '.title', $content['title'], 10080); diff --git a/app/Helpers/Help/HelpInterface.php b/app/Helpers/Help/HelpInterface.php index 3807c2c1ff..5f2c07b215 100644 --- a/app/Helpers/Help/HelpInterface.php +++ b/app/Helpers/Help/HelpInterface.php @@ -1,5 +1,5 @@ subDay(); - bcscale(2); // get balances for start. $startSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') @@ -53,7 +53,20 @@ class AccountReportHelper implements AccountReportHelperInterface ->whereNull('transactions.deleted_at') ->where('transaction_journals.date', '<=', $yesterday->format('Y-m-d')) ->groupBy('accounts.id') - ->get(['accounts.id', DB::Raw('SUM(`transactions`.`amount`) as `balance`')]); + ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]); + + // a special consideration for accounts that did exist on this exact day. + // we also grab the balance from today just in case, to see if that changes things. + // it's a fall back for users who (rightly so) start keeping score at the first of + // the month and find the first report lacking / broken. + $backupSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->whereIn('accounts.id', $ids) + ->whereNull('transaction_journals.deleted_at') + ->whereNull('transactions.deleted_at') + ->where('transaction_journals.date', '<=', $start->format('Y-m-d')) + ->groupBy('accounts.id') + ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]); // and end: $endSet = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') @@ -63,24 +76,38 @@ class AccountReportHelper implements AccountReportHelperInterface ->whereNull('transactions.deleted_at') ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) ->groupBy('accounts.id') - ->get(['accounts.id', DB::Raw('SUM(`transactions`.`amount`) as `balance`')]); + ->get(['accounts.id', DB::raw('SUM(`transactions`.`amount`) as `balance`')]); $accounts->each( - function (Account $account) use ($startSet, $endSet) { + function (Account $account) use ($startSet, $endSet, $backupSet) { /** * The balance for today always incorporates transactions * made on today. So to get todays "start" balance, we sub one * day. */ // - $currentStart = $startSet->filter( + $account->startBalance = '0'; + $account->endBalance = '0'; + $currentStart = $startSet->filter( function (Account $entry) use ($account) { return $account->id == $entry->id; } ); + // grab entry from current backup as well: + $currentBackup = $backupSet->filter( + function (Account $entry) use ($account) { + return $account->id == $entry->id; + } + ); + + if ($currentStart->first()) { $account->startBalance = $currentStart->first()->balance; + } else { + if (is_null($currentStart->first()) && !is_null($currentBackup->first())) { + $account->startBalance = $currentBackup->first()->balance; + } } $currentEnd = $endSet->filter( diff --git a/app/Helpers/Report/AccountReportHelperInterface.php b/app/Helpers/Report/AccountReportHelperInterface.php index e52dea4498..bf9f650633 100644 --- a/app/Helpers/Report/AccountReportHelperInterface.php +++ b/app/Helpers/Report/AccountReportHelperInterface.php @@ -1,4 +1,5 @@ addBalanceLine($this->createEmptyBalanceLine($accounts, $spentData)); $balance->addBalanceLine($this->createTagsBalanceLine($accounts, $start, $end)); $balance->addBalanceLine($this->createDifferenceBalanceLine($accounts, $spentData, $start, $end)); - $balance->setBalanceHeader($header); return $balance; @@ -108,7 +108,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $model->account_id == $account->id && $model->budget_id == $budget->id; } ); - $spent = 0; + $spent = '0'; if (!is_null($entry->first())) { $spent = $entry->first()->spent; } @@ -142,7 +142,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $model->account_id == $account->id && is_null($model->budget_id); } ); - $spent = 0; + $spent = '0'; if (!is_null($entry->first())) { $spent = $entry->first()->spent; } @@ -151,11 +151,10 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $tag->account_id == $account->id; } ); - $left = 0; + $left = '0'; if (!is_null($leftEntry->first())) { $left = $leftEntry->first()->sum; } - bcscale(2); $diffValue = bcadd($spent, $left); // difference: @@ -185,7 +184,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $model->account_id == $account->id && is_null($model->budget_id); } ); - $spent = 0; + $spent = '0'; if (!is_null($entry->first())) { $spent = $entry->first()->spent; } @@ -221,11 +220,10 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $tag->account_id == $account->id; } ); - $left = 0; + $left = '0'; if (!is_null($leftEntry->first())) { $left = $leftEntry->first()->sum; } - bcscale(2); // balanced by tags $tagEntry = new BalanceEntry; diff --git a/app/Helpers/Report/BalanceReportHelperInterface.php b/app/Helpers/Report/BalanceReportHelperInterface.php index fc0629b08e..4ef5d725b6 100644 --- a/app/Helpers/Report/BalanceReportHelperInterface.php +++ b/app/Helpers/Report/BalanceReportHelperInterface.php @@ -1,4 +1,5 @@ getBudgets(); $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end); $allTotalSpent = $repository->spentAllPerDayForAccounts($accounts, $start, $end); - bcscale(2); foreach ($set as $budget) { @@ -48,16 +48,19 @@ class BudgetReportHelper implements BudgetReportHelperInterface return $rep->budget_id == $budget->id; } ); - $totalSpent = isset($allTotalSpent[$budget->id]) ? $allTotalSpent[$budget->id] : []; + $totalSpent = $allTotalSpent[$budget->id] ?? []; // no repetition(s) for this budget: if ($repetitions->count() == 0) { - $spent = array_sum($totalSpent); - $budgetLine = new BudgetLine; - $budgetLine->setBudget($budget); - $budgetLine->setOverspent($spent); - $object->addOverspent($spent); - $object->addBudgetLine($budgetLine); + + $spent = array_sum($totalSpent); + if ($spent > 0) { + $budgetLine = new BudgetLine; + $budgetLine->setBudget($budget); + $budgetLine->setOverspent($spent); + $object->addOverspent($spent); + $object->addBudgetLine($budgetLine); + } continue; } @@ -73,7 +76,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface // 200 en -200 is 0, vergeleken met 0 === 0 // 200 en -300 is -100, vergeleken met 0 === -1 - $left = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : 0; + $left = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0'; $spent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0'; $overspent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? '0' : bcadd($expenses, $repetition->amount); @@ -93,7 +96,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface } // stuff outside of budgets: - $noBudget = $repository->getWithoutBudgetSum($start, $end); + $noBudget = $repository->getWithoutBudgetSum($accounts, $start, $end); $budgetLine = new BudgetLine; $budgetLine->setOverspent($noBudget); $budgetLine->setSpent($noBudget); @@ -115,7 +118,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface */ protected function getSumOfRange(Carbon $start, Carbon $end, array $array) { - bcscale(2); $sum = '0'; $currentStart = clone $start; // to not mess with the original one $currentEnd = clone $end; // to not mess with the original one diff --git a/app/Helpers/Report/BudgetReportHelperInterface.php b/app/Helpers/Report/BudgetReportHelperInterface.php index 63b3a5a19f..69d479c3b0 100644 --- a/app/Helpers/Report/BudgetReportHelperInterface.php +++ b/app/Helpers/Report/BudgetReportHelperInterface.php @@ -1,4 +1,5 @@ setMax($bill->amount_max); // is hit in period? - bcscale(2); $entry = $journals->filter( function (TransactionJournal $journal) use ($bill) { - return $journal->bill_id == $bill->id; + return $journal->bill_id === $bill->id; } ); - if (!is_null($entry->first())) { - $billLine->setAmount($entry->first()->journalAmount); + $first = $entry->first(); + if (!is_null($first)) { + $billLine->setTransactionJournalId($first->id); + $billLine->setAmount($first->journalAmount); $billLine->setHit(true); } else { $billLine->setHit(false); } - - $collection->addBill($billLine); + if (!(!$billLine->isHit() && !$billLine->isActive())) { + $collection->addBill($billLine); + } } @@ -130,7 +134,7 @@ class ReportHelper implements ReportHelperInterface * * @return Expense */ - public function getExpenseReport($start, $end, Collection $accounts) + public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts) { $object = new Expense; $set = $this->query->expense($accounts, $start, $end); @@ -152,7 +156,7 @@ class ReportHelper implements ReportHelperInterface * * @return Income */ - public function getIncomeReport($start, $end, Collection $accounts) + public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts) { $object = new Income; $set = $this->query->income($accounts, $start, $end); @@ -213,6 +217,64 @@ class ReportHelper implements ReportHelperInterface return $months; } + /** + * Returns an array of tags and their comparitive size with amounts bla bla. + * + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function tagReport(Carbon $start, Carbon $end, Collection $accounts): array + { + $ids = $accounts->pluck('id')->toArray(); + $set = Tag:: + distinct() + ->leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id') + ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->whereIn('transactions.account_id', $ids)->get( + ['tags.id', 'tags.tag', 'transaction_journals.id as journal_id', 'transactions.amount'] + ); + $collection = []; + if ($set->count() === 0) { + return $collection; + } + foreach ($set as $entry) { + // less than zero? multiply to be above zero. + $amount = $entry->amount; + if (bccomp($amount, '0', 2) === -1) { + $amount = bcmul($amount, '-1'); + } + $id = intval($entry->id); + + if (!isset($collection[$id])) { + $collection[$id] = [ + 'id' => $id, + 'tag' => $entry->tag, + 'amount' => $amount, + ]; + } else { + $collection[$id]['amount'] = bcadd($collection[$id]['amount'], $amount); + } + } + + // cleanup collection (match "fonts") + $max = strval(max(array_column($collection, 'amount'))); + foreach ($collection as $id => $entry) { + $size = bcdiv($entry['amount'], $max, 4); + if (bccomp($size, '0.25') === -1) { + $size = '0.5'; + } + $collection[$id]['fontsize'] = $size; + } + + return $collection; + } + /** * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. @@ -225,7 +287,6 @@ class ReportHelper implements ReportHelperInterface */ protected function getSumOfRange(Carbon $start, Carbon $end, array $array) { - bcscale(2); $sum = '0'; $currentStart = clone $start; // to not mess with the original one $currentEnd = clone $end; // to not mess with the original one diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php index 7a1833a230..e45fd3b56f 100644 --- a/app/Helpers/Report/ReportHelperInterface.php +++ b/app/Helpers/Report/ReportHelperInterface.php @@ -1,4 +1,5 @@ groupBy('dateFormatted') ->get( [ - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), - DB::Raw('SUM(`t_to`.`amount`) AS `sum`'), + DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), + DB::raw('SUM(`t_to`.`amount`) AS `sum`'), ] ); $array = []; @@ -86,7 +87,7 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('accounts', 't_to.account_id', '=', 'accounts.id') - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]) + ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) ->before($end) ->after($start) ->whereIn('t_from.account_id', $ids) @@ -121,7 +122,7 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('accounts', 't_from.account_id', '=', 'accounts.id') - ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]) + ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->before($end) ->after($start) ->whereIn('t_to.account_id', $ids) @@ -163,8 +164,8 @@ class ReportQuery implements ReportQueryInterface ->groupBy('dateFormatted') ->get( [ - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), - DB::Raw('SUM(`t_from`.`amount`) AS `sum`'), + DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), + DB::raw('SUM(`t_from`.`amount`) AS `sum`'), ] ); $array = []; diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php index 0656ec2115..db1efc6d5a 100644 --- a/app/Helpers/Report/ReportQueryInterface.php +++ b/app/Helpers/Report/ReportQueryInterface.php @@ -1,4 +1,5 @@ accountType->type); $subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]); - $accountList = Expandedform::makeSelectList($repository->getAccounts([$account->accountType->type]), true); + $accountList = ExpandedForm::makeSelectList($repository->getAccounts([$account->accountType->type]), true); unset($accountList[$account->id]); // put previous url in session @@ -87,14 +87,14 @@ class AccountController extends Controller $type = $account->accountType->type; $typeName = Config::get('firefly.shortNamesByFullName.' . $type); $name = $account->name; - $moveTo = Auth::user()->accounts()->find(intval(Input::get('move_account_before_delete'))); + $moveTo = $repository->find(intval(Input::get('move_account_before_delete'))); $repository->destroy($account, $moveTo); Session::flash('success', trans('firefly.' . $typeName . '_deleted', ['name' => $name])); Preferences::mark(); - return redirect(Session::get('accounts.delete.url')); + return redirect(session('accounts.delete.url')); } /** @@ -112,7 +112,7 @@ class AccountController extends Controller $openingBalance = $repository->openingBalanceTransaction($account); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('accounts.edit.fromUpdate') !== true) { + if (session('accounts.edit.fromUpdate') !== true) { Session::put('accounts.edit.url', URL::previous()); } Session::forget('accounts.edit.fromUpdate'); @@ -122,16 +122,17 @@ class AccountController extends Controller // the opening balance is tricky: $openingBalanceAmount = null; - if ($openingBalance) { + if ($openingBalance->id) { $transaction = $repository->getFirstTransaction($openingBalance, $account); $openingBalanceAmount = $transaction->amount; } $preFilled = [ + 'accountNumber' => $account->getMeta('accountNumber'), 'accountRole' => $account->getMeta('accountRole'), 'ccType' => $account->getMeta('ccType'), 'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'), - 'openingBalanceDate' => $openingBalance ? $openingBalance->date->format('Y-m-d') : null, + 'openingBalanceDate' => $openingBalance->id ? $openingBalance->date->format('Y-m-d') : null, 'openingBalance' => $openingBalanceAmount, 'virtualBalance' => round($account->virtual_balance, 2), ]; @@ -148,14 +149,18 @@ class AccountController extends Controller * * @return \Illuminate\View\View */ - public function index(ARI $repository, $what) + public function index(ARI $repository, string $what) { + $what = $what ?? 'asset'; + $subTitle = trans('firefly.' . $what . '_accounts'); $subTitleIcon = Config::get('firefly.subIconsByIdentifier.' . $what); $types = Config::get('firefly.accountTypesByIdentifier.' . $what); $accounts = $repository->getAccounts($types); - $start = clone Session::get('start', Carbon::now()->startOfMonth()); - $end = clone Session::get('end', Carbon::now()->endOfMonth()); + /** @var Carbon $start */ + $start = clone session('start', Carbon::now()->startOfMonth()); + /** @var Carbon $end */ + $end = clone session('end', Carbon::now()->endOfMonth()); $start->subDay(); $ids = $accounts->pluck('id')->toArray(); @@ -209,6 +214,7 @@ class AccountController extends Controller 'active' => true, 'user' => Auth::user()->id, 'iban' => $request->input('iban'), + 'accountNumber' => $request->input('accountNumber'), 'accountRole' => $request->input('accountRole'), 'openingBalance' => round($request->input('openingBalance'), 2), 'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')), @@ -221,6 +227,13 @@ class AccountController extends Controller Session::flash('success', 'New account "' . $account->name . '" stored!'); Preferences::mark(); + // update preferences if necessary: + $frontPage = Preferences::get('frontPageAccounts', [])->data; + if (count($frontPage) > 0) { + $frontPage[] = $account->id; + Preferences::set('frontPageAccounts', $frontPage); + } + if (intval(Input::get('create_another')) === 1) { // set value so create routine will not overwrite URL: Session::put('accounts.create.fromStore', true); @@ -229,7 +242,7 @@ class AccountController extends Controller } // redirect to previous URL. - return redirect(Session::get('accounts.create.url')); + return redirect(session('accounts.create.url')); } /** @@ -247,6 +260,7 @@ class AccountController extends Controller 'active' => $request->input('active'), 'user' => Auth::user()->id, 'iban' => $request->input('iban'), + 'accountNumber' => $request->input('accountNumber'), 'accountRole' => $request->input('accountRole'), 'virtualBalance' => round($request->input('virtualBalance'), 2), 'openingBalance' => round($request->input('openingBalance'), 2), @@ -268,24 +282,24 @@ class AccountController extends Controller } // redirect to previous URL. - return redirect(Session::get('accounts.edit.url')); + return redirect(session('accounts.edit.url')); } /** * @param array $array - * @param $entryId + * @param int $entryId * * @return null|mixed */ - protected function isInArray(array $array, $entryId) + protected function isInArray(array $array, int $entryId) { if (isset($array[$entryId])) { return $array[$entryId]; } - return null; + return ''; } } diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index 0e818f3f20..c380af181f 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -1,10 +1,11 @@ $name])); Preferences::mark(); - return redirect(Session::get('attachments.delete.url')); + return redirect(session('attachments.delete.url')); } /** - * @param Attachment $attachment - * @param AttachmentHelperInterface $helper + * @param Attachment $attachment + * + * @throws FireflyException * - * @return string */ - public function download(Attachment $attachment, AttachmentHelperInterface $helper) + public function download(Attachment $attachment) { + // create a disk. + $disk = Storage::disk('upload'); + $file = $attachment->fileName(); - $file = $helper->getAttachmentLocation($attachment); - if (file_exists($file)) { + if ($disk->exists($file)) { $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); - return response(Crypt::decrypt(file_get_contents($file)), 200) + return response(Crypt::decrypt($disk->get($file)), 200) ->header('Content-Description', 'File Transfer') ->header('Content-Type', 'application/octet-stream') ->header('Content-Disposition', 'attachment; filename=' . $quoted) @@ -92,11 +96,10 @@ class AttachmentController extends Controller ->header('Expires', '0') ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->header('Pragma', 'public') - ->header('Content-Length', $attachment->size); + ->header('Content-Length', $disk->size($file)); - } else { - abort(404); } + throw new FireflyException('Could not find the indicated attachment. The file is no longer there.'); } /** @@ -110,7 +113,7 @@ class AttachmentController extends Controller $subTitle = trans('firefly.edit_attachment', ['name' => $attachment->filename]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('attachments.edit.fromUpdate') !== true) { + if (session('attachments.edit.fromUpdate') !== true) { Session::put('attachments.edit.url', URL::previous()); } Session::forget('attachments.edit.fromUpdate'); @@ -167,7 +170,7 @@ class AttachmentController extends Controller } // redirect to previous URL. - return redirect(Session::get('attachments.edit.url')); + return redirect(session('attachments.edit.url')); } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 79c24d30c2..0d82af7b2c 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -1,10 +1,12 @@ getGuard())->attempt($credentials, $request->has('remember'))) { - return $this->handleUserWasAuthenticated($request, $throttles); } // check if user is blocked: - $message = ''; + $errorMessage = ''; /** @var User $foundUser */ $foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first(); if (!is_null($foundUser)) { // if it exists, show message: - $code = $foundUser->blocked_code; - - if (strlen($code) == 0) { - $code = 'general_blocked'; - } - $message = trans('firefly.' . $code . '_error', ['email' => $credentials['email']]); + $code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked'; + $errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']])); + $this->reportBlockedUserLoginAttempt($foundUser, $code, $request->ip()); } if ($throttles) { $this->incrementLoginAttempts($request); } - return $this->sendFailedLoginResponse($request, $message); + return $this->sendFailedLoginResponse($request, $errorMessage); } /** * Handle a registration request for the application. * + * @param UserRepositoryInterface $repository * @param \Illuminate\Http\Request $request * * @return \Illuminate\Http\Response + * @throws FireflyException + * @throws \Illuminate\Foundation\Validation\ValidationException */ - public function register(Request $request) + public function register(UserRepositoryInterface $repository, Request $request) { $validator = $this->validator($request->all()); @@ -112,6 +114,9 @@ class AuthController extends Controller // is user email domain blocked? if ($this->isBlockedDomain($data['email'])) { $validator->getMessageBag()->add('email', (string)trans('validation.invalid_domain')); + + $this->reportBlockedDomainRegistrationAttempt($data['email'], $request->ip()); + $this->throwValidationException( $request, $validator ); @@ -132,7 +137,7 @@ class AuthController extends Controller $message->to($email, $email)->subject('Welcome to Firefly III! '); } ); - } catch (\Swift_TransportException $e) { + } catch (Swift_TransportException $e) { Log::error($e->getMessage()); } @@ -142,19 +147,13 @@ class AuthController extends Controller Session::flash('gaEventAction', 'new-registration'); // first user ever? - if (User::count() == 1) { - $admin = Role::where('name', 'owner')->first(); - Auth::user()->attachRole($admin); + if ($repository->count() == 1) { + $repository->attachRole(Auth::user(), 'owner'); } - return redirect($this->redirectPath()); } - // @codeCoverageIgnoreStart - abort(500, 'Not a user!'); - - - return redirect($this->redirectPath()); + throw new FireflyException('The authenticated user object is invalid.'); } /** @@ -206,11 +205,11 @@ class AuthController extends Controller /** * Get the failed login message. * - * @param $message + * @param string $message * * @return string */ - protected function getFailedLoginMessage($message) + protected function getFailedLoginMessage(string $message) { if (strlen($message) > 0) { return $message; @@ -222,11 +221,11 @@ class AuthController extends Controller } /** - * @param $email + * @param string $email * * @return bool */ - protected function isBlockedDomain($email) + protected function isBlockedDomain(string $email) { $parts = explode('@', $email); $blocked = $this->getBlockedDomains(); @@ -242,12 +241,11 @@ class AuthController extends Controller * Get the failed login response instance. * * @param \Illuminate\Http\Request $request - * - * @param $message + * @param string $message * * @return \Illuminate\Http\Response */ - protected function sendFailedLoginResponse(Request $request, $message) + protected function sendFailedLoginResponse(Request $request, string $message) { return redirect()->back() ->withInput($request->only($this->loginUsername(), 'remember')) @@ -274,4 +272,62 @@ class AuthController extends Controller ] ); } + + /** + * Send a message home about a blocked domain and the address attempted to register. + * + * @param string $registrationMail + * @param string $ipAddress + */ + private function reportBlockedDomainRegistrationAttempt(string $registrationMail, string $ipAddress) + { + try { + $email = env('SITE_OWNER', false); + $parts = explode('@', $registrationMail); + $domain = $parts[1]; + $fields = [ + 'email_address' => $registrationMail, + 'blocked_domain' => $domain, + 'ip' => $ipAddress, + ]; + + Mail::send( + ['emails.blocked-registration-html', 'emails.blocked-registration'], $fields, function (Message $message) use ($email, $domain) { + $message->to($email, $email)->subject('Blocked a registration attempt with domain ' . $domain . '.'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + } + + /** + * Send a message home about the blocked attempt to login. + * Perhaps in a later stage, simply log these messages. + * + * @param User $user + * @param string $code + * @param string $ipAddress + */ + private function reportBlockedUserLoginAttempt(User $user, string $code, string $ipAddress) + { + + try { + $email = env('SITE_OWNER', false); + $fields = [ + 'user_id' => $user->id, + 'user_address' => $user->email, + 'code' => $code, + 'ip' => $ipAddress, + ]; + + Mail::send( + ['emails.blocked-login-html', 'emails.blocked-login'], $fields, function (Message $message) use ($email, $user) { + $message->to($email, $email)->subject('Blocked a login attempt from ' . trim($user->email) . '.'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + } } diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index 855cfb5924..e341c46372 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -1,4 +1,5 @@ data; + + if (strlen($secret) === 0) { + throw new FireflyException('Your two factor authentication secret is empty, which it should be at this point. Please check the log files.'); + } + Session::flash('two-factor-secret', $secret); + + return view('auth.two-factor', compact('user')); + } + + /** + * @return mixed + * @throws FireflyException + */ + public function lostTwoFactor() + { + $user = Auth::user(); + $siteOwner = env('SITE_OWNER', ''); + + Log::info( + 'To reset the two factor authentication for user #' . $user->id . + ' (' . $user->email . '), simply open the "preferences" table and delete the entries with the names "twoFactorAuthEnabled" and' . + ' "twoFactorAuthSecret" for user_id ' . $user->id . '. That will take care of it.' + ); + + return view('auth.lost-two-factor', compact('user', 'siteOwner')); + } + + /** + * @param TokenFormRequest $request + * + * @return mixed + */ + public function postIndex(TokenFormRequest $request) + { + Session::put('twofactor-authenticated', true); + Session::put('twofactor-authenticated-date', new Carbon); + + return redirect(route('home')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index ca59f0d256..e74da3c093 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -20,7 +20,7 @@ class BillController extends Controller { /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -39,7 +39,7 @@ class BillController extends Controller // put previous url in session if not redirect from store (not "create another"). - if (Session::get('bills.create.fromStore') !== true) { + if (session('bills.create.fromStore') !== true) { Session::put('bills.create.url', URL::previous()); } Session::forget('bills.create.fromStore'); @@ -78,7 +78,7 @@ class BillController extends Controller Session::flash('success', 'The bill was deleted.'); Preferences::mark(); - return redirect(Session::get('bills.delete.url')); + return redirect(session('bills.delete.url')); } /** @@ -92,7 +92,7 @@ class BillController extends Controller $subTitle = trans('firefly.edit_bill', ['name' => $bill->name]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('bills.edit.fromUpdate') !== true) { + if (session('bills.edit.fromUpdate') !== true) { Session::put('bills.edit.url', URL::previous()); } Session::forget('bills.edit.fromUpdate'); @@ -184,7 +184,7 @@ class BillController extends Controller } // redirect to previous URL. - return redirect(Session::get('bills.create.url')); + return redirect(session('bills.create.url')); } @@ -211,7 +211,7 @@ class BillController extends Controller } // redirect to previous URL. - return redirect(Session::get('bills.edit.url')); + return redirect(session('bills.edit.url')); } diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index e86d95bb46..6424acf265 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -3,6 +3,7 @@ use Amount; use Auth; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; @@ -26,7 +27,7 @@ class BudgetController extends Controller { /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -44,8 +45,9 @@ class BudgetController extends Controller */ public function amount(BudgetRepositoryInterface $repository, Budget $budget) { - $amount = intval(Input::get('amount')); - $date = Session::get('start', Carbon::now()->startOfMonth()); + $amount = intval(Input::get('amount')); + /** @var Carbon $date */ + $date = session('start', Carbon::now()->startOfMonth()); $limitRepetition = $repository->updateLimitAmount($budget, $date, $amount); if ($amount == 0) { $limitRepetition = null; @@ -62,7 +64,7 @@ class BudgetController extends Controller public function create() { // put previous url in session if not redirect from store (not "create another"). - if (Session::get('budgets.create.fromStore') !== true) { + if (session('budgets.create.fromStore') !== true) { Session::put('budgets.create.url', URL::previous()); } Session::forget('budgets.create.fromStore'); @@ -107,7 +109,7 @@ class BudgetController extends Controller Preferences::mark(); - return redirect(Session::get('budgets.delete.url')); + return redirect(session('budgets.delete.url')); } /** @@ -120,7 +122,7 @@ class BudgetController extends Controller $subTitle = trans('firefly.edit_budget', ['name' => $budget->name]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('budgets.edit.fromUpdate') !== true) { + if (session('budgets.edit.fromUpdate') !== true) { Session::put('budgets.edit.url', URL::previous()); } Session::forget('budgets.edit.fromUpdate'); @@ -140,19 +142,20 @@ class BudgetController extends Controller */ public function index(BudgetRepositoryInterface $repository, ARI $accountRepository) { - $budgets = $repository->getActiveBudgets(); - $inactive = $repository->getInactiveBudgets(); - $spent = '0'; - $budgeted = '0'; - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod(Session::get('start', new Carbon), $range); + $budgets = $repository->getActiveBudgets(); + $inactive = $repository->getInactiveBudgets(); + $spent = '0'; + $budgeted = '0'; + $range = Preferences::get('viewRange', '1M')->data; + /** @var Carbon $date */ + $date = session('start', new Carbon); + $start = Navigation::startOfPeriod($date, $range); $end = Navigation::endOfPeriod($start, $range); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); $budgetIncomeTotal = Preferences::get($key, 1000)->data; $period = Navigation::periodShow($start, $range); $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); - bcscale(2); /** * Do some cleanup: */ @@ -186,9 +189,11 @@ class BudgetController extends Controller */ public function noBudget(BudgetRepositoryInterface $repository) { - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod(Session::get('start', new Carbon), $range); - $end = Navigation::endOfPeriod($start, $range); + /** @var Carbon $start */ + $start = session('start', Carbon::now()->startOfMonth()); + /** @var Carbon $end */ + $end = session('end', Carbon::now()->endOfMonth()); + $list = $repository->getWithoutBudget($start, $end); $subTitle = trans( 'firefly.without_budget_between', @@ -204,7 +209,9 @@ class BudgetController extends Controller public function postUpdateIncome() { $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod(Session::get('start', new Carbon), $range); + /** @var Carbon $date */ + $date = session('start', new Carbon); + $start = Navigation::startOfPeriod($date, $range); $end = Navigation::endOfPeriod($start, $range); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); @@ -217,19 +224,18 @@ class BudgetController extends Controller /** * @param BudgetRepositoryInterface $repository * @param Budget $budget - * @param LimitRepetition $repetition + * @param LimitRepetition|null $repetition * - * @return \Illuminate\View\View + * @return View + * @throws FireflyException */ public function show(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition = null) { if (!is_null($repetition->id) && $repetition->budgetLimit->budget->id != $budget->id) { - $message = 'Invalid selection.'; - - return view('error', compact('message')); + throw new FireflyException('This budget limit is not part of this budget.'); } - $journals = $repository->getJournals($budget, $repetition); + $journals = $repository->getJournals($budget, $repetition, 50); if (is_null($repetition->id)) { $start = $repository->firstActivity($budget); @@ -282,7 +288,7 @@ class BudgetController extends Controller } // redirect to previous URL. - return redirect(Session::get('budgets.create.url')); + return redirect(session('budgets.create.url')); } @@ -313,7 +319,7 @@ class BudgetController extends Controller } // redirect to previous URL. - return redirect(Session::get('budgets.edit.url')); + return redirect(session('budgets.edit.url')); } @@ -322,8 +328,11 @@ class BudgetController extends Controller */ public function updateIncome() { - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod(Session::get('start', new Carbon), $range); + $range = Preferences::get('viewRange', '1M')->data; + + /** @var Carbon $date */ + $date = session('start', new Carbon); + $start = Navigation::startOfPeriod($date, $range); $end = Navigation::endOfPeriod($start, $range); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); $amount = Preferences::get($key, 1000); diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 1b59519808..f7239b898b 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -25,7 +25,7 @@ class CategoryController extends Controller { /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -40,7 +40,7 @@ class CategoryController extends Controller public function create() { // put previous url in session if not redirect from store (not "create another"). - if (Session::get('categories.create.fromStore') !== true) { + if (session('categories.create.fromStore') !== true) { Session::put('categories.create.url', URL::previous()); } Session::forget('categories.create.fromStore'); @@ -83,7 +83,7 @@ class CategoryController extends Controller Session::flash('success', 'The category "' . e($name) . '" was deleted.'); Preferences::mark(); - return redirect(Session::get('categories.delete.url')); + return redirect(session('categories.delete.url')); } /** @@ -96,7 +96,7 @@ class CategoryController extends Controller $subTitle = trans('firefly.edit_category', ['name' => $category->name]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('categories.edit.fromUpdate') !== true) { + if (session('categories.edit.fromUpdate') !== true) { Session::put('categories.edit.url', URL::previous()); } Session::forget('categories.edit.fromUpdate'); @@ -133,8 +133,10 @@ class CategoryController extends Controller */ public function noCategory(CRI $repository) { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->startOfMonth()); + /** @var Carbon $start */ + $start = session('start', Carbon::now()->startOfMonth()); + /** @var Carbon $end */ + $end = session('end', Carbon::now()->startOfMonth()); $list = $repository->listNoCategory($start, $end); $subTitle = trans( 'firefly.without_category_between', @@ -213,7 +215,7 @@ class CategoryController extends Controller * * @return \Illuminate\View\View */ - public function showWithDate(SCRI $repository, Category $category, $date) + public function showWithDate(SCRI $repository, Category $category, string $date) { $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; @@ -284,7 +286,7 @@ class CategoryController extends Controller } // redirect to previous URL. - return redirect(Session::get('categories.edit.url')); + return redirect(session('categories.edit.url')); } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 1110abaa27..4f0c5d365a 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -1,4 +1,5 @@ startOfMonth()); - $end = clone Session::get('end', Carbon::now()->endOfMonth()); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); $accounts = $repository->getAccounts(['Expense account', 'Beneficiary account']); // chart properties for cache: @@ -106,8 +106,8 @@ class AccountController extends Controller public function frontpage(ARI $repository) { $frontPage = Preferences::get('frontPageAccounts', []); - $start = clone Session::get('start', Carbon::now()->startOfMonth()); - $end = clone Session::get('end', Carbon::now()->endOfMonth()); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); $accounts = $repository->getFrontpageAccounts($frontPage); // chart properties for cache: @@ -138,8 +138,8 @@ class AccountController extends Controller { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); // chart properties for cache: $cache = new CacheProperties(); diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index d1e58998c5..aba1965f05 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -1,4 +1,5 @@ startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. $creditCardDue = $repository->getCreditCardBill($start, $end); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 848a13179b..18a61759d2 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -1,4 +1,5 @@ generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface'); } + /** + * @param BudgetRepositoryInterface $repository + * @param Budget $budget + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function budget(BudgetRepositoryInterface $repository, Budget $budget) + { + + // dates and times + $first = $repository->getFirstBudgetLimitDate($budget); + $range = Preferences::get('viewRange', '1M')->data; + $last = session('end', new Carbon); + + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($first); + $cache->addProperty($last); + $cache->addProperty('budget'); + if ($cache->has()) { + + return Response::json($cache->get()); // @codeCoverageIgnore + } + + $final = clone $last; + $final->addYears(2); + $last = Navigation::endOfX($last, $range, $final); + $entries = new Collection; + // get all expenses: + $spentArray = $repository->spentPerDay($budget, $first, $last); + + while ($first < $last) { + + // periodspecific dates: + $currentStart = Navigation::startOfPeriod($first, $range); + $currentEnd = Navigation::endOfPeriod($first, $range); + $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); + $entry = [$first, ($spent * -1)]; + + $entries->push($entry); + $first = Navigation::addPeriod($first, $range, 0); + } + + $data = $this->generator->budget($entries); + $cache->store($data); + + return Response::json($data); + } + + /** + * Shows the amount left in a specific budget limit. + * + * @param BudgetRepositoryInterface $repository + * @param Budget $budget + * @param LimitRepetition $repetition + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) + { + $start = clone $repetition->startdate; + $end = $repetition->enddate; + + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('budget'); + $cache->addProperty('limit'); + $cache->addProperty($budget->id); + $cache->addProperty($repetition->id); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + $set = $repository->getExpensesPerDay($budget, $start, $end); + $entries = new Collection; + $amount = $repetition->amount; + + // get sum (har har)! + while ($start <= $end) { + $formatted = $start->format('Y-m-d'); + $filtered = $set->filter( + function (Budget $obj) use ($formatted) { + return $obj->date == $formatted; + } + ); + $sum = is_null($filtered->first()) ? '0' : $filtered->first()->dailyAmount; + + /* + * Sum of expenses on this day: + */ + $amount = round(bcadd(strval($amount), $sum), 2); + $entries->push([clone $start, $amount]); + $start->addDay(); + } + + $data = $this->generator->budgetLimit($entries); + $cache->store($data); + + return Response::json($data); + + } + + /** + * Shows a budget list with spent/left/overspent. + * + * @param BudgetRepositoryInterface $repository + * + * @param ARI $accountRepository + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository) + { + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); + + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('budget'); + $cache->addProperty('all'); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); + $allEntries = new Collection; + $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + + + /** @var Budget $budget */ + foreach ($budgets as $budget) { + // we already have amount, startdate and enddate. + // if this "is" a limit repetition (as opposed to a budget without one entirely) + // depends on whether startdate and enddate are null. + $name = $budget->name; + if (is_null($budget->startdate) && is_null($budget->enddate)) { + $currentStart = clone $start; + $currentEnd = clone $end; + $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + $amount = '0'; + $left = '0'; + $spent = $expenses; + $overspent = '0'; + } else { + $currentStart = clone $budget->startdate; + $currentEnd = clone $budget->enddate; + $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + $amount = $budget->amount; + // smaller than 1 means spent MORE than budget allows. + $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? '0' : bcadd($budget->amount, $expenses); + $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses; + $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : '0'; + } + + $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); + } + + $noBudgetExpenses = $repository->getWithoutBudgetSum($accounts, $start, $end); + $allEntries->push([trans('firefly.noBudget'), '0', '0', $noBudgetExpenses, '0', '0']); + $data = $this->generator->frontpage($allEntries); + $cache->store($data); + + return Response::json($data); + } + /** * * @param BudgetRepositoryInterface $repository @@ -49,7 +218,7 @@ class BudgetController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function multiYear(BudgetRepositoryInterface $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) + public function multiYear(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) { // chart properties for cache: $cache = new CacheProperties(); @@ -87,21 +256,24 @@ class BudgetController extends Controller $currentEnd = clone $currentStart; $currentEnd->endOfYear(); - // save to array: + // basic information: $year = $currentStart->year; - $entry['name'] = $budget->name; + $entry['name'] = $budget->name ?? (string)trans('firefly.noBudget'); $spent = 0; - $budgeted = 0; - if (isset($set[$id]['entries'][$year])) { - $spent = $set[$id]['entries'][$year] * -1; - } - - if (isset($budgetedArray[$id][$year])) { - $budgeted = round($budgetedArray[$id][$year], 2); + // this might be a good moment to collect no budget stuff. + if (is_null($budget->id)) { + // get without budget sum in range: + $spent = $repository->getWithoutBudgetSum($accounts, $currentStart, $currentEnd) * -1; + } else { + if (isset($set[$id]['entries'][$year])) { + $spent = $set[$id]['entries'][$year] * -1; + } } + $budgeted = $budgetedArray[$id][$year] ?? '0'; $entry['spent'][$year] = $spent; - $entry['budgeted'][$year] = $budgeted; + $entry['budgeted'][$year] = round($budgeted, 2); + // jump to next year. $currentStart = clone $currentEnd; @@ -117,181 +289,6 @@ class BudgetController extends Controller } - /** - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function budget(BudgetRepositoryInterface $repository, Budget $budget) - { - - // dates and times - $first = $repository->getFirstBudgetLimitDate($budget); - $range = Preferences::get('viewRange', '1M')->data; - $last = Session::get('end', new Carbon); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($first); - $cache->addProperty($last); - $cache->addProperty('budget'); - if ($cache->has()) { - - return Response::json($cache->get()); // @codeCoverageIgnore - } - - $final = clone $last; - $final->addYears(2); - $last = Navigation::endOfX($last, $range, $final); - $entries = new Collection; - // get all expenses: - $set = $repository->getExpensesPerMonth($budget, $first, $last); - - while ($first < $last) { - $monthFormatted = $first->format('Y-m'); - - $filtered = $set->filter( - function (Budget $obj) use ($monthFormatted) { - return $obj->dateFormatted == $monthFormatted; - } - ); - $spent = is_null($filtered->first()) ? '0' : $filtered->first()->monthlyAmount; - - $entries->push([$first, round(($spent * -1), 2)]); - - $first = Navigation::addPeriod($first, $range, 0); - } - - $data = $this->generator->budget($entries); - $cache->store($data); - - return Response::json($data); - } - - /** - * Shows the amount left in a specific budget limit. - * - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * @param LimitRepetition $repetition - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) - { - $start = clone $repetition->startdate; - $end = $repetition->enddate; - bcscale(2); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('budget'); - $cache->addProperty('limit'); - $cache->addProperty($budget->id); - $cache->addProperty($repetition->id); - if ($cache->has()) { - return Response::json($cache->get()); // @codeCoverageIgnore - } - - $set = $repository->getExpensesPerDay($budget, $start, $end); - $entries = new Collection; - $amount = $repetition->amount; - - // get sum (har har)! - while ($start <= $end) { - $formatted = $start->format('Y-m-d'); - $filtered = $set->filter( - function (Budget $obj) use ($formatted) { - return $obj->date == $formatted; - } - ); - $sum = is_null($filtered->first()) ? '0' : $filtered->first()->dailyAmount; - - /* - * Sum of expenses on this day: - */ - $amount = round(bcadd($amount, $sum), 2); - $entries->push([clone $start, $amount]); - $start->addDay(); - } - - $data = $this->generator->budgetLimit($entries); - $cache->store($data); - - return Response::json($data); - - } - - /** - * Shows a budget list with spent/left/overspent. - * - * @param BudgetRepositoryInterface $repository - * - * @param ARI $accountRepository - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository) - { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('budget'); - $cache->addProperty('all'); - if ($cache->has()) { - return Response::json($cache->get()); // @codeCoverageIgnore - } - - $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); - $allEntries = new Collection; - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); - - - bcscale(2); - - /** @var Budget $budget */ - foreach ($budgets as $budget) { - // we already have amount, startdate and enddate. - // if this "is" a limit repetition (as opposed to a budget without one entirely) - // depends on whether startdate and enddate are null. - $name = $budget->name; - if (is_null($budget->startdate) && is_null($budget->enddate)) { - $currentStart = clone $start; - $currentEnd = clone $end; - $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - $amount = 0; - $left = 0; - $spent = $expenses; - $overspent = 0; - } else { - $currentStart = clone $budget->startdate; - $currentEnd = clone $budget->enddate; - $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - $amount = $budget->amount; - // smaller than 1 means spent MORE than budget allows. - $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? 0 : bcadd($budget->amount, $expenses); - $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? ($amount * -1) : $expenses; - $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : 0; - } - - $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); - } - - $noBudgetExpenses = $repository->getWithoutBudgetSum($start, $end); - $allEntries->push([trans('firefly.noBudget'), 0, 0, $noBudgetExpenses, 0, 0]); - $data = $this->generator->frontpage($allEntries); - $cache->store($data); - - return Response::json($data); - } - /** * * @param BudgetRepositoryInterface $repository @@ -302,7 +299,7 @@ class BudgetController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function year(BudgetRepositoryInterface $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // chart properties for cache: $cache = new CacheProperties(); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 95c1985206..b0e5eaf873 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -1,4 +1,5 @@ startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); $data = $this->makePeriodChart($repository, $category, $start, $end); return Response::json($data); @@ -115,7 +120,7 @@ class CategoryController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function earnedInPeriod(CRI $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function earnedInPeriod(CRI $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { $cache = new CacheProperties; // chart properties for cache: $cache->addProperty($start); @@ -134,32 +139,8 @@ class CategoryController extends Controller return $category->name; } ); - $entries = new Collection; - - while ($start < $end) { // filter the set: - $row = [clone $start]; - $currentSet = $set->filter( // get possibly relevant entries from the big $set - function (Category $category) use ($start) { - return $category->dateFormatted == $start->format('Y-m'); - } - ); - /** @var Category $category */ - foreach ($categories as $category) { // check for each category if its in the current set. - $entry = $currentSet->filter( // if its in there, use the value. - function (Category $cat) use ($category) { - return ($cat->id == $category->id); - } - )->first(); - if (!is_null($entry)) { - $row[] = round($entry->earned, 2); - } else { - $row[] = 0; - } - } - $entries->push($row); - $start->addMonth(); - } - $data = $this->generator->earnedInPeriod($categories, $entries); + $entries = $this->filterCollection($start, $end, $set, $categories); + $data = $this->generator->earnedInPeriod($categories, $entries); $cache->store($data); return $data; @@ -171,13 +152,15 @@ class CategoryController extends Controller * * @param CRI $repository * + * @param ARI $accountRepository + * * @return \Symfony\Component\HttpFoundation\Response */ - public function frontpage(CRI $repository) + public function frontpage(CRI $repository, ARI $accountRepository) { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); // chart properties for cache: $cache = new CacheProperties; @@ -190,8 +173,9 @@ class CategoryController extends Controller } // get data for categories (and "no category"): - $set = $repository->spentForAccountsPerMonth(new Collection, $start, $end); - $outside = $repository->sumSpentNoCategory(new Collection, $start, $end); + $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); + $outside = $repository->sumSpentNoCategory($accounts, $start, $end); // this is a "fake" entry for the "no category" entry. $entry = new stdClass(); @@ -216,7 +200,7 @@ class CategoryController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function multiYear($reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) + public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) { /** @var CRI $repository */ $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); @@ -340,42 +324,81 @@ class CategoryController extends Controller return Response::json($cache->get()); // @codeCoverageIgnore } + $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); $categories = $set->unique('id')->sortBy( function (Category $category) { return $category->name; } ); - $entries = new Collection; + $entries = $this->filterCollection($start, $end, $set, $categories); + $entries = $this->invertSelection($entries); + $data = $this->generator->spentInPeriod($categories, $entries); + $cache->store($data); + + return $data; + } + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $set + * @param Collection $categories + * + * @return Collection + */ + private function filterCollection(Carbon $start, Carbon $end, Collection $set, Collection $categories): Collection + { + $entries = new Collection; while ($start < $end) { // filter the set: $row = [clone $start]; - $currentSet = $set->filter(// get possibly relevant entries from the big $set + $currentSet = $set->filter( // get possibly relevant entries from the big $set function (Category $category) use ($start) { return $category->dateFormatted == $start->format('Y-m'); } ); /** @var Category $category */ - foreach ($categories as $category) {// check for each category if its in the current set. - $entry = $currentSet->filter(// if its in there, use the value. + foreach ($categories as $category) { // check for each category if its in the current set. + $entry = $currentSet->filter( // if its in there, use the value. function (Category $cat) use ($category) { return ($cat->id == $category->id); } )->first(); if (!is_null($entry)) { - $row[] = round(($entry->spent * -1), 2); + $row[] = $entry->earned ? round($entry->earned, 2) : round($entry->spent, 2); } else { $row[] = 0; } } - $entries->push($row); $start->addMonth(); } - $data = $this->generator->spentInPeriod($categories, $entries); - $cache->store($data); - return $data; + return $entries; + } + + /** + * Not the most elegant solution but it works. + * + * @param Collection $entries + * + * @return Collection + */ + private function invertSelection(Collection $entries): Collection + { + $result = new Collection; + foreach ($entries as $entry) { + $new = [$entry[0]]; + $count = count($entry); + for ($i = 1; $i < $count; $i++) { + $new[$i] = ($entry[$i] * -1); + } + $result->push($new); + } + + return $result; + } /** @@ -395,7 +418,7 @@ class CategoryController extends Controller $cache->addProperty($category->id); $cache->addProperty('specific-period'); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + return $cache->get(); // } $entries = new Collection; @@ -406,8 +429,8 @@ class CategoryController extends Controller while ($start <= $end) { $str = $start->format('Y-m-d'); - $spent = isset($spentArray[$str]) ? $spentArray[$str] : 0; - $earned = isset($earnedArray[$str]) ? $earnedArray[$str] : 0; + $spent = $spentArray[$str] ?? '0'; + $earned = $earnedArray[$str] ?? '0'; $date = Navigation::periodShow($start, '1D'); $entries->push([clone $start, $date, $spent, $earned]); $start->addDay(); @@ -419,5 +442,4 @@ class CategoryController extends Controller return $data; } - } diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php index b21153bfa9..bf2d91a445 100644 --- a/app/Http/Controllers/Chart/PiggyBankController.php +++ b/app/Http/Controllers/Chart/PiggyBankController.php @@ -1,4 +1,5 @@ generator = app('FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface'); } + /** + * This chart, by default, is shown on the multi-year and year report pages, + * which means that giving it a 2 week "period" should be enough granularity. + * + * @param string $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return \Illuminate\Http\JsonResponse + */ + public function netWorth(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + // chart properties for cache: + $cache = new CacheProperties; + $cache->addProperty('netWorth'); + $cache->addProperty($start); + $cache->addProperty($reportType); + $cache->addProperty($accounts); + $cache->addProperty($end); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + $ids = $accounts->pluck('id')->toArray(); + $current = clone $start; + $entries = new Collection; + while ($current < $end) { + $balances = Steam::balancesById($ids, $current); + $sum = $this->arraySum($balances); + $entries->push( + [ + 'date' => clone $current, + 'net-worth' => $sum, + ] + ); + + $current->addDays(7); + } + $data = $this->generator->netWorth($entries); + + $cache->store($data); + + return Response::json($data); + } + /** * Summarizes all income and expenses, per month, for a given year. @@ -45,7 +92,7 @@ class ReportController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function yearInOut(ReportQueryInterface $query, $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function yearInOut(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // chart properties for cache: $cache = new CacheProperties; @@ -88,7 +135,7 @@ class ReportController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function yearInOutSummarized(ReportQueryInterface $query, $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function yearInOutSummarized(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // chart properties for cache: @@ -117,64 +164,6 @@ class ReportController extends Controller return Response::json($data); } - /** - * @param array $earned - * @param array $spent - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - protected function singleYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end) - { - $income = '0'; - $expense = '0'; - $count = 0; - while ($start < $end) { - $date = $start->format('Y-m'); - $currentIncome = isset($earned[$date]) ? $earned[$date] : 0; - $currentExpense = isset($spent[$date]) ? ($spent[$date] * -1) : 0; - $income = bcadd($income, $currentIncome); - $expense = bcadd($expense, $currentExpense); - - $count++; - $start->addMonth(); - } - - $data = $this->generator->yearInOutSummarized($income, $expense, $count); - - return $data; - } - - /** - * @param array $earned - * @param array $spent - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - protected function multiYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end) - { - $income = '0'; - $expense = '0'; - $count = 0; - while ($start < $end) { - - $currentIncome = $this->pluckFromArray($start->year, $earned); - $currentExpense = $this->pluckFromArray($start->year, $spent) * -1; - $income = bcadd($income, $currentIncome); - $expense = bcadd($expense, $currentExpense); - - $count++; - $start->addYear(); - } - - $data = $this->generator->multiYearInOutSummarized($income, $expense, $count); - - return $data; - } - /** * @param array $earned * @param array $spent @@ -208,22 +197,23 @@ class ReportController extends Controller * * @return array */ - protected function singleYearInOut(array $earned, array $spent, Carbon $start, Carbon $end) + protected function multiYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end) { - // per month? simply use each month. - - $entries = new Collection; + $income = '0'; + $expense = '0'; + $count = 0; while ($start < $end) { - // total income and total expenses: - $date = $start->format('Y-m'); - $incomeSum = isset($earned[$date]) ? $earned[$date] : 0; - $expenseSum = isset($spent[$date]) ? ($spent[$date] * -1) : 0; - $entries->push([clone $start, $incomeSum, $expenseSum]); - $start->addMonth(); + $currentIncome = $this->pluckFromArray($start->year, $earned); + $currentExpense = bcmul($this->pluckFromArray($start->year, $spent), '-1'); + $income = bcadd($income, $currentIncome); + $expense = bcadd($expense, $currentExpense); + + $count++; + $start->addYear(); } - $data = $this->generator->yearInOut($entries); + $data = $this->generator->multiYearInOutSummarized($income, $expense, $count); return $data; } @@ -236,7 +226,6 @@ class ReportController extends Controller */ protected function pluckFromArray($year, array $set) { - bcscale(2); $sum = '0'; foreach ($set as $date => $amount) { if (substr($date, 0, 4) == $year) { @@ -247,4 +236,76 @@ class ReportController extends Controller return $sum; } + + /** + * @param array $earned + * @param array $spent + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + protected function singleYearInOut(array $earned, array $spent, Carbon $start, Carbon $end) + { + // per month? simply use each month. + + $entries = new Collection; + while ($start < $end) { + // total income and total expenses: + $date = $start->format('Y-m'); + $incomeSum = $earned[$date] ?? 0; + $expenseSum = isset($spent[$date]) ? ($spent[$date] * -1) : 0; + + $entries->push([clone $start, $incomeSum, $expenseSum]); + $start->addMonth(); + } + + $data = $this->generator->yearInOut($entries); + + return $data; + } + + /** + * @param array $earned + * @param array $spent + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + protected function singleYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end) + { + $income = '0'; + $expense = '0'; + $count = 0; + while ($start < $end) { + $date = $start->format('Y-m'); + $currentIncome = $earned[$date] ?? '0'; + $currentExpense = isset($spent[$date]) ? bcmul($spent[$date], '-1') : '0'; + $income = bcadd($income, $currentIncome); + $expense = bcadd($expense, $currentExpense); + + $count++; + $start->addMonth(); + } + + $data = $this->generator->yearInOutSummarized($income, $expense, $count); + + return $data; + } + + /** + * @param $array + * + * @return string + */ + private function arraySum($array) : string + { + $sum = '0'; + foreach ($array as $entry) { + $sum = bcadd($sum, $entry); + } + + return $sum; + } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 08328d8884..b3070f64bf 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -1,4 +1,5 @@ data; $this->monthFormat = (string)trans('config.month'); $this->monthAndDayFormat = (string)trans('config.month_and_day'); + $this->dateTimeFormat = (string)trans('config.date_time'); App::setLocale($lang); Carbon::setLocale(substr($lang, 0, 2)); @@ -60,6 +64,7 @@ class Controller extends BaseController ]; View::share('monthFormat', $this->monthFormat); View::share('monthAndDayFormat', $this->monthAndDayFormat); + View::share('dateTimeFormat', $this->dateTimeFormat); View::share('language', $lang); View::share('localeconv', $localeconv); } @@ -77,7 +82,6 @@ class Controller extends BaseController */ protected function getSumOfRange(Carbon $start, Carbon $end, array $array) { - bcscale(2); $sum = '0'; $currentStart = clone $start; // to not mess with the original one $currentEnd = clone $end; // to not mess with the original one diff --git a/app/Http/Controllers/CsvController.php b/app/Http/Controllers/CsvController.php index 75fa433460..b5a7966f32 100644 --- a/app/Http/Controllers/CsvController.php +++ b/app/Http/Controllers/CsvController.php @@ -1,4 +1,5 @@ Session::get('csv-date-format'), - 'has-headers' => Session::get('csv-has-headers'), + 'date-format' => session('csv-date-format'), + 'has-headers' => session('csv-has-headers'), ]; if (Session::has('csv-map')) { - $data['map'] = Session::get('csv-map'); + $data['map'] = session('csv-map'); } if (Session::has('csv-roles')) { - $data['roles'] = Session::get('csv-roles'); + $data['roles'] = session('csv-roles'); } if (Session::has('csv-mapped')) { - $data['mapped'] = Session::get('csv-mapped'); + $data['mapped'] = session('csv-mapped'); } if (Session::has('csv-specifix')) { - $data['specifix'] = Session::get('csv-specifix'); + $data['specifix'] = session('csv-specifix'); } $result = json_encode($data, JSON_PRETTY_PRINT); $name = sprintf('"%s"', addcslashes('csv-configuration-' . date('Y-m-d') . '.json', '"\\')); - RequestFacade::header('Content-disposition: attachment; filename=' . $name); - RequestFacade::header('Content-Type: application/json'); - RequestFacade::header('Content-Description: File Transfer'); - RequestFacade::header('Connection: Keep-Alive'); - RequestFacade::header('Expires: 0'); - RequestFacade::header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - RequestFacade::header('Pragma: public'); - RequestFacade::header('Content-Length: ' . strlen($result)); - - return $result; + return response($result, 200) + ->header('Content-disposition', 'attachment; filename=' . $name) + ->header('Content-Type', 'application/json') + ->header('Content-Description', 'File Transfer') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', strlen($result)); } /** @@ -218,8 +217,10 @@ class CsvController extends Controller } // process given roles and mapping: - $roles = $this->wizard->processSelectedRoles(Input::get('role')); - $maps = $this->wizard->processSelectedMapping($roles, Input::get('map')); + $inputMap = Input::get('map') ?? []; + $inputRoles = Input::get('role') ?? []; + $roles = $this->wizard->processSelectedRoles($inputRoles); + $maps = $this->wizard->processSelectedMapping($roles, $inputMap); Session::put('csv-map', $maps); Session::put('csv-roles', $roles); @@ -277,13 +278,7 @@ class CsvController extends Controller * field id => field identifier. * ] */ - try { - $options = $this->wizard->showOptions($this->data->getMap()); - } catch (FireflyException $e) { - Log::error($e->getMessage()); - - return view('error', ['message' => $e->getMessage()]); - } + $options = $this->wizard->showOptions($this->data->getMap()); // After these values are prepped, read the actual CSV file $reader = $this->data->getReader(); @@ -318,15 +313,10 @@ class CsvController extends Controller } Log::debug('Created importer'); - $importer = new Importer; + /** @var Importer $importer */ + $importer = app('FireflyIII\Helpers\Csv\Importer'); $importer->setData($this->data); - try { - $importer->run(); - } catch (FireflyException $e) { - Log::error('Catch error: ' . $e->getMessage()); - - return view('error', ['message' => $e->getMessage()]); - } + $importer->run(); Log::debug('Done importing!'); $rows = $importer->getRows(); @@ -408,7 +398,7 @@ class CsvController extends Controller return redirect(route('csv.index')); } - $fullPath = $this->wizard->storeCsvFile($request->file('csv')->getRealPath()); + $path = $this->wizard->storeCsvFile($request->file('csv')->getRealPath()); $settings = []; $settings['date-format'] = Input::get('date_format'); $settings['has-headers'] = intval(Input::get('has_headers')) === 1; @@ -427,14 +417,16 @@ class CsvController extends Controller $settings['roles'] = []; if ($request->hasFile('csv_config')) { // Process config file if present. - $data = file_get_contents($request->file('csv_config')->getRealPath()); + + $size = $request->file('csv_config')->getSize(); + $data = $request->file('csv_config')->openFile()->fread($size); $json = json_decode($data, true); if (is_array($json)) { $settings = array_merge($settings, $json); } } - $this->data->setCsvFileLocation($fullPath); + $this->data->setCsvFileLocation($path); $this->data->setDateFormat($settings['date-format']); $this->data->setHasHeaders($settings['has-headers']); $this->data->setMap($settings['map']); diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index d62ba35cb1..80280c94ca 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -22,7 +22,7 @@ class CurrencyController extends Controller /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -40,7 +40,7 @@ class CurrencyController extends Controller $subTitle = trans('firefly.create_currency'); // put previous url in session if not redirect from store (not "create another"). - if (Session::get('currency.create.fromStore') !== true) { + if (session('currency.create.fromStore') !== true) { Session::put('currency.create.url', URL::previous()); } Session::forget('currency.create.fromStore'); @@ -114,7 +114,7 @@ class CurrencyController extends Controller $currency->delete(); } - return redirect(Session::get('currency.delete.url')); + return redirect(session('currency.delete.url')); } /** @@ -129,7 +129,7 @@ class CurrencyController extends Controller $currency->symbol = htmlentities($currency->symbol); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('currency.edit.fromUpdate') !== true) { + if (session('currency.edit.fromUpdate') !== true) { Session::put('currency.edit.url', URL::previous()); } Session::forget('currency.edit.fromUpdate'); @@ -183,7 +183,7 @@ class CurrencyController extends Controller } // redirect to previous URL. - return redirect(Session::get('currency.create.url')); + return redirect(session('currency.create.url')); } @@ -212,7 +212,7 @@ class CurrencyController extends Controller } // redirect to previous URL. - return redirect(Session::get('currency.edit.url')); + return redirect(session('currency.edit.url')); } diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php new file mode 100644 index 0000000000..feb72426b6 --- /dev/null +++ b/app/Http/Controllers/ExportController.php @@ -0,0 +1,201 @@ +key . '.zip'; + $date = date('Y-m-d \a\t H-i-s'); + $name = 'Export job on ' . $date . '.zip'; + $quoted = sprintf('"%s"', addcslashes($name, '"\\')); + + if (!$disk->exists($file)) { + throw new FireflyException('Against all expectations, zip file "' . $file . '" does not exist.'); + } + + + $job->change('export_downloaded'); + Log::debug('Will send user file "' . $file . '".'); + + return response($disk->get($file), 200) + ->header('Content-Description', 'File Transfer') + ->header('Content-Type', 'application/octet-stream') + ->header('Content-Disposition', 'attachment; filename=' . $quoted) + ->header('Content-Transfer-Encoding', 'binary') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', $disk->size($file)); + + } + + /** + * @param ExportJob $job + * + * @return \Illuminate\Http\JsonResponse + */ + public function getStatus(ExportJob $job) + { + return Response::json(['status' => trans('firefly.' . $job->status)]); + } + + /** + * @param ARI $repository + * + * @param EJRI $jobs + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function index(ARI $repository, EJRI $jobs) + { + // create new export job. + $job = $jobs->create(); + // delete old ones. + $jobs->cleanup(); + + // does the user have shared accounts? + $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accountList = ExpandedForm::makeSelectList($accounts); + $checked = array_keys($accountList); + $formats = array_keys(Config::get('firefly.export_formats')); + $defaultFormat = Preferences::get('export_format', Config::get('firefly.default_export_format'))->data; + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + + return view('export.index', compact('job', 'checked', 'accountList', 'formats', 'defaultFormat', 'first', 'today')); + + } + + /** + * @param ExportFormRequest $request + * @param ARI $repository + * + * @param EJRI $jobs + * + * @return string + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function postIndex(ExportFormRequest $request, ARI $repository, EJRI $jobs) + { + set_time_limit(0); + $job = $jobs->findByKey($request->get('job')); + $settings = [ + 'accounts' => $repository->get($request->get('accounts')), + 'startDate' => new Carbon($request->get('export_start_range')), + 'endDate' => new Carbon($request->get('export_end_range')), + 'exportFormat' => $request->get('exportFormat'), + 'includeAttachments' => intval($request->get('include_attachments')) === 1, + 'includeConfig' => intval($request->get('include_config')) === 1, + 'includeOldUploads' => intval($request->get('include_old_uploads')) === 1, + 'job' => $job, + ]; + + $job->change('export_status_make_exporter'); + $processor = new Processor($settings); + + /* + * Collect journals: + */ + $job->change('export_status_collecting_journals'); + $processor->collectJournals(); + $job->change('export_status_collected_journals'); + /* + * Transform to exportable entries: + */ + $job->change('export_status_converting_to_export_format'); + $processor->convertJournals(); + $job->change('export_status_converted_to_export_format'); + /* + * Transform to (temporary) file: + */ + $job->change('export_status_creating_journal_file'); + $processor->exportJournals(); + $job->change('export_status_created_journal_file'); + /* + * Collect attachments, if applicable. + */ + if ($settings['includeAttachments']) { + $job->change('export_status_collecting_attachments'); + $processor->collectAttachments(); + $job->change('export_status_collected_attachments'); + } + + /* + * Collect old uploads + */ + if ($settings['includeOldUploads']) { + $job->change('export_status_collecting_old_uploads'); + $processor->collectOldUploads(); + $job->change('export_status_collected_old_uploads'); + } + + /* + * Generate / collect config file. + */ + if ($settings['includeConfig']) { + $job->change('export_status_creating_config_file'); + $processor->createConfigFile(); + $job->change('export_status_created_config_file'); + } + + /* + * Create ZIP file: + */ + $job->change('export_status_creating_zip_file'); + $processor->createZipFile(); + $job->change('export_status_created_zip_file'); + + $job->change('export_status_finished'); + + return Response::json('ok'); + } +} diff --git a/app/Http/Controllers/HelpController.php b/app/Http/Controllers/HelpController.php index 667aef28f3..67e177a53d 100644 --- a/app/Http/Controllers/HelpController.php +++ b/app/Http/Controllers/HelpController.php @@ -2,6 +2,7 @@ use FireflyIII\Helpers\Help\HelpInterface; use Log; +use Preferences; use Response; /** @@ -25,7 +26,7 @@ class HelpController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function show(HelpInterface $help, $route) + public function show(HelpInterface $help, string $route) { $content = [ 'text' => '

There is no help for this route!

', @@ -46,7 +47,8 @@ class HelpController extends Controller return Response::json($content); } - $content = $help->getFromGithub($route); + $language = Preferences::get('language', env('DEFAULT_LANGUAGE', 'en_US'))->data; + $content = $help->getFromGithub($language, $route); $help->putInCache($route, $content); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 1d638813da..47b2f482d6 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -3,14 +3,17 @@ use Artisan; use Carbon\Carbon; use Config; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Input; -use Log; use Preferences; +use Route; use Session; use Steam; + /** * Class HomeController * @@ -28,6 +31,7 @@ class HomeController extends Controller public function dateRange() { + $start = new Carbon(Input::get('start')); $end = new Carbon(Input::get('end')); @@ -42,16 +46,26 @@ class HomeController extends Controller } /** - * @return \Illuminate\Http\RedirectResponse + * @throws FireflyException */ - public function flush() + public function displayError() + { + throw new FireflyException('A very simple test error.'); + } + + /** + * @param TagRepositoryInterface $repository + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function flush(TagRepositoryInterface $repository) { Preferences::mark(); // get all tags. // update all counts: - $tags = Tag::get(); + $tags = $repository->get(); /** @var Tag $tag */ foreach ($tags as $tag) { @@ -76,22 +90,22 @@ class HomeController extends Controller */ public function index(ARI $repository) { - Log::debug('You are at index.'); $types = Config::get('firefly.accountTypesByIdentifier.asset'); $count = $repository->countAccounts($types); - bcscale(2); if ($count == 0) { return redirect(route('new-user.index')); } - $title = 'Firefly'; - $subTitle = trans('firefly.welcomeBack'); - $mainTitleIcon = 'fa-fire'; - $transactions = []; - $frontPage = Preferences::get('frontPageAccounts', []); - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $title = 'Firefly'; + $subTitle = trans('firefly.welcomeBack'); + $mainTitleIcon = 'fa-fire'; + $transactions = []; + $frontPage = Preferences::get('frontPageAccounts', []); + /** @var Carbon $start */ + $start = session('start', Carbon::now()->startOfMonth()); + /** @var Carbon $end */ + $end = session('end', Carbon::now()->endOfMonth()); $showTour = Preferences::get('tour', true)->data; $accounts = $repository->getFrontpageAccounts($frontPage); $savings = $repository->getSavingsAccounts(); @@ -105,11 +119,11 @@ class HomeController extends Controller $sum = $repository->sumOfEverything(); - if ($sum != 0) { + if (bccomp($sum, '0') !== 0) { Session::flash( 'error', 'Your transactions are unbalanced. This means a' . ' withdrawal, deposit or transfer was not stored properly. ' - . 'Please check your accounts and transactions for errors.' + . 'Please check your accounts and transactions for errors (' . $sum . ').' ); } @@ -126,4 +140,52 @@ class HomeController extends Controller ); } + /** + * Display a list of named routes. Excludes some that cannot be "shown". This method + * is used to generate help files (down the road). + */ + public function routes() + { + // these routes are not relevant for the help pages: + $ignore = [ + 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', + 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', + 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', + 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', + ]; + $routes = Route::getRoutes(); + /** @var \Illuminate\Routing\Route $route */ + foreach ($routes as $route) { + + $name = $route->getName(); + $methods = $route->getMethods(); + + if (!is_null($name) && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) { + foreach (array_keys(Config::get('firefly.languages')) as $lang) { + echo 'touch ' . $lang . '/' . $name . '.md
'; + } + + } + } + + return '
'; + } + + + /** + * @param array $array + * @param string $needle + * + * @return bool + */ + private function startsWithAny(array $array, string $needle): bool + { + foreach ($array as $entry) { + if ((substr($needle, 0, strlen($entry)) === $entry)) { + return true; + } + } + + return false; + } } diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 682299f4d3..229879cf1a 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -3,6 +3,7 @@ use Amount; use Carbon\Carbon; use Config; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Report\ReportQueryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -13,7 +14,6 @@ use FireflyIII\Support\CacheProperties; use Input; use Preferences; use Response; -use Session; /** * Class JsonController @@ -24,8 +24,6 @@ class JsonController extends Controller { /** * JsonController constructor. - * - * @codeCoverageIgnore */ public function __construct() { @@ -56,9 +54,8 @@ class JsonController extends Controller */ public function boxBillsPaid(BillRepositoryInterface $repository) { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); - bcscale(2); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); /* * Since both this method and the chart use the exact same data, we can suffice @@ -69,7 +66,7 @@ class JsonController extends Controller if ($creditCardDue >= 0) { $amount = bcadd($amount, $creditCardDue); } - $amount = $amount * -1; + $amount = bcmul($amount, '-1'); $data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; @@ -83,9 +80,8 @@ class JsonController extends Controller */ public function boxBillsUnpaid(BillRepositoryInterface $repository) { - bcscale(2); - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. $creditCardDue = $repository->getCreditCardBill($start, $end); @@ -109,8 +105,8 @@ class JsonController extends Controller */ public function boxIn(ReportQueryInterface $reportQuery, ARI $accountRepository) { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); // works for json too! $cache = new CacheProperties; @@ -138,8 +134,8 @@ class JsonController extends Controller */ public function boxOut(ReportQueryInterface $reportQuery, ARI $accountRepository) { - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); @@ -250,7 +246,7 @@ class JsonController extends Controller { $pref = Preferences::get('tour', true); if (!$pref) { - abort(404); + throw new FireflyException('Cannot find preference for tour. Exit.'); } $headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end']; $steps = []; diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php index 58a68a45fa..5560401813 100644 --- a/app/Http/Controllers/NewUserController.php +++ b/app/Http/Controllers/NewUserController.php @@ -4,7 +4,6 @@ use Auth; use Carbon\Carbon; use Config; use FireflyIII\Http\Requests\NewUserFormRequest; -use FireflyIII\Models\AccountMeta; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use Preferences; use Session; @@ -107,8 +106,8 @@ class NewUserController extends Controller $creditCard = $repository->store($creditAccount); // store meta for CC: - AccountMeta::create(['name' => 'ccType', 'data' => 'monthlyFull', 'account_id' => $creditCard->id,]); - AccountMeta::create(['name' => 'ccMonthlyPaymentDate', 'data' => Carbon::now()->year . '-01-01', 'account_id' => $creditCard->id,]); + $repository->storeMeta($creditCard, 'ccType', 'monthlyFull'); + $repository->storeMeta($creditCard, 'ccMonthlyPaymentDate', Carbon::now()->year . '-01-01'); } Session::flash('success', 'New account(s) created!'); diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 56ea5322ab..0a9c50e40a 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -28,7 +28,7 @@ class PiggyBankController extends Controller { /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -47,8 +47,8 @@ class PiggyBankController extends Controller */ public function add(ARI $repository, PiggyBank $piggyBank) { - bcscale(2); - $date = Session::get('end', Carbon::now()->endOfMonth()); + /** @var Carbon $date */ + $date = session('end', Carbon::now()->endOfMonth()); $leftOnAccount = $repository->leftOnAccount($piggyBank->account, $date); $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); @@ -71,7 +71,7 @@ class PiggyBankController extends Controller $subTitleIcon = 'fa-plus'; // put previous url in session if not redirect from store (not "create another"). - if (Session::get('piggy-banks.create.fromStore') !== true) { + if (session('piggy-banks.create.fromStore') !== true) { Session::put('piggy-banks.create.url', URL::previous()); } Session::forget('piggy-banks.create.fromStore'); @@ -112,7 +112,7 @@ class PiggyBankController extends Controller Preferences::mark(); $repository->destroy($piggyBank); - return redirect(Session::get('piggy-banks.delete.url')); + return redirect(session('piggy-banks.delete.url')); } /** @@ -148,7 +148,7 @@ class PiggyBankController extends Controller Session::flash('gaEventAction', 'edit'); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('piggy-banks.edit.fromUpdate') !== true) { + if (session('piggy-banks.edit.fromUpdate') !== true) { Session::put('piggy-banks.edit.url', URL::previous()); } Session::forget('piggy-banks.edit.fromUpdate'); @@ -166,8 +166,8 @@ class PiggyBankController extends Controller { /** @var Collection $piggyBanks */ $piggyBanks = $piggyRepository->getPiggyBanks(); - $end = Session::get('end', Carbon::now()->endOfMonth()); - bcscale(2); + /** @var Carbon $end */ + $end = session('end', Carbon::now()->endOfMonth()); $accounts = []; /** @var PiggyBank $piggyBank */ @@ -185,8 +185,8 @@ class PiggyBankController extends Controller 'name' => $account->name, 'balance' => Steam::balance($account, $end, true), 'leftForPiggyBanks' => $repository->leftOnAccount($account, $end), - 'sumOfSaved' => $piggyBank->savedSoFar, - 'sumOfTargets' => round($piggyBank->targetamount, 2), + 'sumOfSaved' => strval($piggyBank->savedSoFar), + 'sumOfTargets' => strval(round($piggyBank->targetamount, 2)), 'leftToSave' => $piggyBank->leftToSave, ]; } else { @@ -226,9 +226,9 @@ class PiggyBankController extends Controller */ public function postAdd(PiggyBankRepositoryInterface $repository, ARI $accounts, PiggyBank $piggyBank) { - bcscale(2); - $amount = round(Input::get('amount'), 2); - $date = Session::get('end', Carbon::now()->endOfMonth()); + $amount = round(Input::get('amount'), 2); + /** @var Carbon $date */ + $date = session('end', Carbon::now()->endOfMonth()); $leftOnAccount = $accounts->leftOnAccount($piggyBank->account, $date); $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); @@ -261,7 +261,6 @@ class PiggyBankController extends Controller public function postRemove(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { $amount = round(Input::get('amount'), 2); - bcscale(2); $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; @@ -271,7 +270,7 @@ class PiggyBankController extends Controller $repetition->save(); // create event - $repository->createEvent($piggyBank, $amount * -1); + $repository->createEvent($piggyBank, bcmul($amount, '-1')); Session::flash('success', 'Removed ' . Amount::format($amount, false) . ' from "' . e($piggyBank->name) . '".'); Preferences::mark(); @@ -341,7 +340,7 @@ class PiggyBankController extends Controller // redirect to previous URL. - return redirect(Session::get('piggy-banks.create.url')); + return redirect(session('piggy-banks.create.url')); } /** @@ -376,7 +375,7 @@ class PiggyBankController extends Controller // redirect to previous URL. - return redirect(Session::get('piggy-banks.edit.url')); + return redirect(session('piggy-banks.edit.url')); } diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 5ef8f38630..482af8f2fb 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -1,8 +1,11 @@ getDomain(); + $secret = $google2fa->generateSecretKey(16, Auth::user()->id); + Session::flash('two-factor-secret', $secret); + $image = $google2fa->getQRCodeInline('Firefly III at ' . $domain, null, $secret, 150); + + + return view('preferences.code', compact('image')); + } + + /** + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function deleteCode() + { + Preferences::delete('twoFactorAuthEnabled'); + Preferences::delete('twoFactorAuthSecret'); + Session::flash('success', strval(trans('firefly.pref_two_factor_auth_disabled'))); + Session::flash('info', strval(trans('firefly.pref_two_factor_auth_remove_it'))); + + return redirect(route('preferences')); + } + /** * @param ARI $repository * @@ -32,25 +64,46 @@ class PreferencesController extends Controller */ public function index(ARI $repository) { - $accounts = $repository->getAccounts(['Default account', 'Asset account']); - $viewRangePref = Preferences::get('viewRange', '1M'); - $viewRange = $viewRangePref->data; - $frontPageAccounts = Preferences::get('frontPageAccounts', []); - $budgetMax = Preferences::get('budgetMaximum', 1000); - $language = Preferences::get('language', env('DEFAULT_LANGUAGE', 'en_US'))->data; - $budgetMaximum = $budgetMax->data; - $customFiscalYear = Preferences::get('customFiscalYear', 0)->data; - $fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data; - $fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr; - - $showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', 'false') == 'true'; + $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $viewRangePref = Preferences::get('viewRange', '1M'); + $viewRange = $viewRangePref->data; + $frontPageAccounts = Preferences::get('frontPageAccounts', []); + $budgetMax = Preferences::get('budgetMaximum', 1000); + $language = Preferences::get('language', env('DEFAULT_LANGUAGE', 'en_US'))->data; + $budgetMaximum = $budgetMax->data; + $customFiscalYear = Preferences::get('customFiscalYear', 0)->data; + $fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data; + $fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr; + $twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; + $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); + $showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true; return view( 'preferences.index', - compact('budgetMaximum', 'language', 'accounts', 'frontPageAccounts', 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'showIncomplete') + compact( + 'budgetMaximum', 'language', 'accounts', 'frontPageAccounts', + 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'twoFactorAuthEnabled', + 'hasTwoFactorAuthSecret', 'showIncomplete' + ) ); } + /** + * @param TokenFormRequest $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function postCode(TokenFormRequest $request) + { + Preferences::set('twoFactorAuthEnabled', 1); + Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret')); + + Session::flash('success', 'Preferences saved!'); + Preferences::mark(); + + return redirect(route('preferences')); + } + /** * @return \Illuminate\Http\RedirectResponse */ @@ -78,10 +131,19 @@ class PreferencesController extends Controller // custom fiscal year $customFiscalYear = (int)Input::get('customFiscalYear'); + $fiscalYearStart = date('m-d', strtotime(Input::get('fiscalYearStart'))); Preferences::set('customFiscalYear', $customFiscalYear); - $fiscalYearStart = date('m-d', strtotime(Input::get('fiscalYearStart'))); Preferences::set('fiscalYearStart', $fiscalYearStart); + // two factor auth + $twoFactorAuthEnabled = intval(Input::get('twoFactorAuthEnabled')); + $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); + + // If we already have a secret, just set the two factor auth enabled to 1, and let the user continue with the existing secret. + if ($hasTwoFactorAuthSecret) { + Preferences::set('twoFactorAuthEnabled', $twoFactorAuthEnabled); + } + // language: $lang = Input::get('language'); if (in_array($lang, array_keys(Config::get('firefly.languages')))) { @@ -92,7 +154,24 @@ class PreferencesController extends Controller Session::flash('success', 'Preferences saved!'); Preferences::mark(); + // if we don't have a valid secret yet, redirect to the code page. + // AND USER HAS ACTUALLY ENABLED 2FA + if (!$hasTwoFactorAuthSecret && $twoFactorAuthEnabled === 1) { + return redirect(route('preferences.code')); + } + return redirect(route('preferences')); } + /** + * @return string + */ + private function getDomain() : string + { + $url = url()->to('/'); + $parts = parse_url($url); + + return $parts['host']; + } + } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 89c82545b6..a6509cd9f2 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -122,7 +122,7 @@ class ProfileController extends Controller * * @return string|bool */ - protected function validatePassword($old, $new1) + protected function validatePassword(string $old, string $new1) { if ($new1 == $old) { return trans('firefly.should_change'); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index c917fef16c..b416c7fa63 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -1,10 +1,15 @@ accountHelper->getAccountReport($start, $end, $accounts); - $incomes = $this->helper->getIncomeReport($start, $end, $accounts); - $expenses = $this->helper->getExpenseReport($start, $end, $accounts); - $budgets = $this->budgetHelper->getBudgetReport($start, $end, $accounts); - $categories = $this->helper->getCategoryReport($start, $end, $accounts); - $balance = $this->balanceHelper->getBalanceReport($start, $end, $accounts); - $bills = $this->helper->getBillReport($start, $end, $accounts); - - // and some id's, joined: - $accountIds = join(',', $accounts->pluck('id')->toArray()); - - // continue! - return view( - 'reports.default.month', - compact( - 'start', 'end', 'reportType', - 'accountReport', - 'incomes', 'incomeTopLength', - 'expenses', 'expenseTopLength', - 'budgets', 'balance', - 'categories', - 'bills', - 'accountIds', 'reportType' - ) - ); - } - - /** - * @param $reportType - * @param $start - * @param $end - * @param $accounts - * - * @return View - */ - public function defaultMultiYear($reportType, $start, $end, $accounts) - { - - $incomeTopLength = 8; - $expenseTopLength = 8; - // list of users stuff: - $budgets = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface')->getActiveBudgets(); - $categories = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface')->listCategories(); - $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); - $incomes = $this->helper->getIncomeReport($start, $end, $accounts); - $expenses = $this->helper->getExpenseReport($start, $end, $accounts); - - // and some id's, joined: - $accountIds = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $accountIds[] = $account->id; - } - $accountIds = join(',', $accountIds); - - return view( - 'reports.default.multi-year', - compact( - 'budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'reportType', 'accountReport', 'incomes', 'expenses', - 'incomeTopLength', 'expenseTopLength' - ) - ); - } - - /** - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return View - */ - public function defaultYear($reportType, Carbon $start, Carbon $end, Collection $accounts) - { - $incomeTopLength = 8; - $expenseTopLength = 8; - - $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); - $incomes = $this->helper->getIncomeReport($start, $end, $accounts); - $expenses = $this->helper->getExpenseReport($start, $end, $accounts); - - Session::flash('gaEventCategory', 'report'); - Session::flash('gaEventAction', 'year'); - Session::flash('gaEventLabel', $start->format('Y')); - - // and some id's, joined: - $accountIds = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $accountIds[] = $account->id; - } - $accountIds = join(',', $accountIds); - - return view( - 'reports.default.year', - compact( - 'start', 'accountReport', 'incomes', 'reportType', 'accountIds', 'end', - 'expenses', 'incomeTopLength', 'expenseTopLength' - ) - ); - } - /** * @param ARI $repository * @@ -167,7 +59,8 @@ class ReportController extends Controller */ public function index(ARI $repository) { - $start = Session::get('first'); + /** @var Carbon $start */ + $start = clone session('first'); $months = $this->helper->listOfMonths($start); $customFiscalYear = Preferences::get('customFiscalYear', 0)->data; @@ -182,31 +75,34 @@ class ReportController extends Controller $accountList = join(',', $accountIds); - return view('reports.index', compact('months', 'accounts', 'start', 'accountList','customFiscalYear')); + return view('reports.index', compact('months', 'accounts', 'start', 'accountList', 'customFiscalYear')); } /** - * @param $reportType + * @param string $reportType * @param Carbon $start * @param Carbon $end * @param Collection $accounts * * @return View + * @throws FireflyException */ - public function report($reportType, Carbon $start, Carbon $end, Collection $accounts) + public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // throw an error if necessary. if ($end < $start) { - return view('error')->with('message', 'End date cannot be before start date, silly!'); + throw new FireflyException('End date cannot be before start date, silly!'); } // lower threshold - if ($start < Session::get('first')) { - $start = Session::get('first'); + if ($start < session('first')) { + Log::debug('Start is ' . $start . ' but sessionfirst is ' . session('first')); + $start = session('first'); } switch ($reportType) { default: + throw new FireflyException('Unfortunately, reports of the type "' . e($reportType) . '" are not yet available. '); case 'default': View::share( @@ -230,10 +126,144 @@ class ReportController extends Controller } return $this->defaultMonth($reportType, $start, $end, $accounts); + case 'audit': + + View::share( + 'subTitle', trans( + 'firefly.report_audit', + [ + 'start' => $start->formatLocalized($this->monthFormat), + 'end' => $end->formatLocalized($this->monthFormat), + ] + ) + ); + View::share('subTitleIcon', 'fa-calendar'); + + throw new FireflyException('Unfortunately, reports of the type "' . e($reportType) . '" are not yet available. '); + break; } } + /** + * @param $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return View + */ + private function defaultMonth(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + $incomeTopLength = 8; + $expenseTopLength = 8; + + // get report stuff! + $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); + $incomes = $this->helper->getIncomeReport($start, $end, $accounts); + $expenses = $this->helper->getExpenseReport($start, $end, $accounts); + $budgets = $this->budgetHelper->getBudgetReport($start, $end, $accounts); + $categories = $this->helper->getCategoryReport($start, $end, $accounts); + $balance = $this->balanceHelper->getBalanceReport($start, $end, $accounts); + $bills = $this->helper->getBillReport($start, $end, $accounts); + $tags = $this->helper->tagReport($start, $end, $accounts); + + // and some id's, joined: + $accountIds = join(',', $accounts->pluck('id')->toArray()); + + // continue! + return view( + 'reports.default.month', + compact( + 'start', 'end', 'reportType', + 'accountReport', 'tags', + 'incomes', 'incomeTopLength', + 'expenses', 'expenseTopLength', + 'budgets', 'balance', + 'categories', + 'bills', + 'accountIds', 'reportType' + ) + ); + } + + /** + * @param $reportType + * @param $start + * @param $end + * @param $accounts + * + * @return View + */ + private function defaultMultiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + + $incomeTopLength = 8; + $expenseTopLength = 8; + // list of users stuff: + $budgets = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface')->getActiveBudgets(); + $categories = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface')->listCategories(); + $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); + $incomes = $this->helper->getIncomeReport($start, $end, $accounts); + $expenses = $this->helper->getExpenseReport($start, $end, $accounts); + $tags = $this->helper->tagReport($start, $end, $accounts); + + // and some id's, joined: + $accountIds = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + $accountIds = join(',', $accountIds); + + return view( + 'reports.default.multi-year', + compact( + 'budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'reportType', 'accountReport', 'incomes', 'expenses', + 'incomeTopLength', 'expenseTopLength', 'tags' + ) + ); + } + + /** + * @param $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return View + */ + private function defaultYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + $incomeTopLength = 8; + $expenseTopLength = 8; + + $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); + $incomes = $this->helper->getIncomeReport($start, $end, $accounts); + $expenses = $this->helper->getExpenseReport($start, $end, $accounts); + $tags = $this->helper->tagReport($start, $end, $accounts); + + Session::flash('gaEventCategory', 'report'); + Session::flash('gaEventAction', 'year'); + Session::flash('gaEventLabel', $start->format('Y')); + + // and some id's, joined: + $accountIds = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + $accountIds = join(',', $accountIds); + + return view( + 'reports.default.year', + compact( + 'start', 'accountReport', 'incomes', 'reportType', 'accountIds', 'end', + 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags' + ) + ); + } + } diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index e3a23e4c9b..005c849bdc 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -1,4 +1,5 @@ $ruleGroup->title]); // put previous url in session if not redirect from store (not "create another"). - if (Session::get('rules.rule.create.fromStore') !== true) { + if (session('rules.rule.create.fromStore') !== true) { Session::put('rules.rule.create.url', URL::previous()); } Session::forget('rules.rule.create.fromStore'); @@ -85,6 +90,8 @@ class RuleController extends Controller } /** + * Delete a given rule. + * * @param Rule $rule * * @return View @@ -103,6 +110,8 @@ class RuleController extends Controller } /** + * Actually destroy the given rule. + * * @param Rule $rule * @param RuleRepositoryInterface $repository * @@ -118,7 +127,7 @@ class RuleController extends Controller Preferences::mark(); - return redirect(Session::get('rules.rule.delete.url')); + return redirect(session('rules.rule.delete.url')); } /** @@ -136,11 +145,12 @@ class RuleController extends Controller } /** - * @param Rule $rule + * @param RuleRepositoryInterface $repository + * @param Rule $rule * * @return View */ - public function edit(Rule $rule) + public function edit(RuleRepositoryInterface $repository, Rule $rule) { // has old input? if (Input::old()) { @@ -156,11 +166,11 @@ class RuleController extends Controller } // get rule trigger for update / store-journal: - $primaryTrigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first()->trigger_value; + $primaryTrigger = $repository->getPrimaryTrigger($rule); $subTitle = trans('firefly.edit_rule', ['title' => $rule->title]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('rules.rule.edit.fromUpdate') !== true) { + if (session('rules.rule.edit.fromUpdate') !== true) { Session::put('rules.rule.edit.url', URL::previous()); } Session::forget('rules.rule.edit.fromUpdate'); @@ -171,32 +181,15 @@ class RuleController extends Controller } /** + * @param RuleGroupRepositoryInterface $repository + * * @return View */ - public function index() + public function index(RuleGroupRepositoryInterface $repository) { $this->createDefaultRuleGroup(); $this->createDefaultRule(); - - $ruleGroups = Auth::user() - ->ruleGroups() - ->orderBy('active', 'DESC') - ->orderBy('order', 'ASC') - ->with( - [ - 'rules' => function (HasMany $query) { - $query->orderBy('active', 'DESC'); - $query->orderBy('order', 'ASC'); - - }, - 'rules.ruleTriggers' => function (HasMany $query) { - $query->orderBy('order', 'ASC'); - }, - 'rules.ruleActions' => function (HasMany $query) { - $query->orderBy('order', 'ASC'); - }, - ] - )->get(); + $ruleGroups = $repository->getRuleGroupsWithRules(Auth::user()); return view('rules.index', compact('ruleGroups')); } @@ -270,14 +263,61 @@ class RuleController extends Controller // set value so create routine will not overwrite URL: Session::put('rules.rule.create.fromStore', true); - return redirect(route('rules.rule.create', [$request->input('what')]))->withInput(); + return redirect(route('rules.rule.create', [$ruleGroup]))->withInput(); } // redirect to previous URL. - return redirect(Session::get('rules.rule.create.url')); + return redirect(session('rules.rule.create.url')); } + /** + * This method allows the user to test a certain set of rule triggers. The rule triggers are passed along + * using the URL parameters (GET), and are usually put there using a Javascript thing. + * + * This method will parse and validate those rules and create a "TransactionMatcher" which will attempt + * to find transaction journals matching the users input. A maximum range of transactions to try (range) and + * a maximum number of transactions to return (limit) are set as well. + * + * @param TestRuleFormRequest $request + * + * @return \Illuminate\View\View + */ + public function testTriggers(TestRuleFormRequest $request) + { + // build trigger array from response + $triggers = $this->getValidTriggerList($request); + + if (count($triggers) == 0) { + return Response::json(['html' => '', 'warning' => trans('firefly.warning_no_valid_triggers')]); + } + + $limit = Config::get('firefly.test-triggers.limit'); + $range = Config::get('firefly.test-triggers.range'); + + /** @var TransactionMatcher $matcher */ + $matcher = app('FireflyIII\Rules\TransactionMatcher'); + $matcher->setLimit($limit); + $matcher->setRange($range); + $matcher->setTriggers($triggers); + $matchingTransactions = $matcher->findMatchingTransactions(); + + // Warn the user if only a subset of transactions is returned + $warning = ''; + if (count($matchingTransactions) == $limit) { + $warning = trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); + } else { + if (count($matchingTransactions) == 0) { + $warning = trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); + } + } + + // Return json response + $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + + return Response::json(['html' => $view, 'warning' => $warning]); + } + /** * @param RuleRepositoryInterface $repository * @param Rule $rule @@ -329,7 +369,7 @@ class RuleController extends Controller } // redirect to previous URL. - return redirect(Session::get('rules.rule.edit.url')); + return redirect(session('rules.rule.edit.url')); } private function createDefaultRule() @@ -374,7 +414,7 @@ class RuleController extends Controller if ($repository->count() === 0) { $data = [ - 'user_id' => Auth::user()->id, + 'user_id' => Auth::user()->id, 'title' => trans('firefly.default_rule_group_name'), 'description' => trans('firefly.default_rule_group_description'), ]; @@ -495,5 +535,30 @@ class RuleController extends Controller return $triggers; } + /** + * @param TestRuleFormRequest $request + * + * @return array + */ + private function getValidTriggerList(TestRuleFormRequest $request): array + { + + $triggers = []; + $data = [ + 'rule-triggers' => $request->get('rule-trigger'), + 'rule-trigger-values' => $request->get('rule-trigger-value'), + 'rule-trigger-stop' => $request->get('rule-trigger-stop'), + ]; + foreach ($data['rule-triggers'] as $index => $triggerType) { + $triggers[] = [ + 'type' => $triggerType, + 'value' => $data['rule-trigger-values'][$index], + 'stopProcessing' => intval($data['rule-trigger-stop'][$index]) === 1 ? true : false, + ]; + } + + return $triggers; + } + } diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 1dbc65ca8d..d34885acc2 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -1,11 +1,16 @@ $ruleGroup->title]); - $ruleGroupList = Expandedform::makeSelectList($repository->get(), true); + $ruleGroupList = ExpandedForm::makeSelectList($repository->get(), true); unset($ruleGroupList[$ruleGroup->id]); // put previous url in session @@ -90,7 +95,7 @@ class RuleGroupController extends Controller Preferences::mark(); - return redirect(Session::get('rules.rule-group.delete.url')); + return redirect(session('rules.rule-group.delete.url')); } /** @@ -117,7 +122,7 @@ class RuleGroupController extends Controller $subTitle = trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('rules.rule-group.edit.fromUpdate') !== true) { + if (session('rules.rule-group.edit.fromUpdate') !== true) { Session::put('rules.rule-group.edit.url', URL::previous()); } Session::forget('rules.rule-group.edit.fromUpdate'); @@ -128,6 +133,61 @@ class RuleGroupController extends Controller } + /** + * Execute the given rulegroup on a set of existing transactions + * + * @param SelectTransactionsRequest $request + * @param AccountRepositoryInterface $repository + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Http\RedirectResponse + */ + public function execute(SelectTransactionsRequest $request, AccountRepositoryInterface $repository, RuleGroup $ruleGroup) + { + // Get parameters specified by the user + $accounts = $repository->get($request->get('accounts')); + $startDate = new Carbon($request->get('start_date')); + $endDate = new Carbon($request->get('end_date')); + + // Create a job to do the work asynchronously + $job = new ExecuteRuleGroupOnExistingTransactions($ruleGroup); + + // Apply parameters to the job + $job->setUser(Auth::user()); + $job->setAccounts($accounts); + $job->setStartDate($startDate); + $job->setEndDate($endDate); + + // Dispatch a new job to execute it in a queue + $this->dispatch($job); + + // Tell the user that the job is queued + Session::flash('success', trans('firefly.executed_group_on_existing_transactions', ['title' => $ruleGroup->title])); + + return redirect()->route('rules.index'); + } + + /** + * Shows a form for the user to select a range of transactions to execute this rulegroup for + * + * @param AccountRepositoryInterface $repository + * @param RuleGroup $ruleGroup + * + * @return View + */ + public function selectTransactions(AccountRepositoryInterface $repository, RuleGroup $ruleGroup) + { + // does the user have shared accounts? + $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accountList = ExpandedForm::makeSelectList($accounts); + $checkedAccounts = array_keys($accountList); + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + $subTitle = (string)trans('firefly.execute_on_existing_transactions'); + + return view('rules.rule-group.select-transactions', compact('checkedAccounts', 'accountList', 'first', 'today', 'ruleGroup', 'subTitle')); + } + /** * @param RuleGroupFormRequest $request * @param RuleGroupRepositoryInterface $repository @@ -155,7 +215,7 @@ class RuleGroupController extends Controller } // redirect to previous URL. - return redirect(Session::get('rules.rule-group.create.url')); + return redirect(session('rules.rule-group.create.url')); } /** @@ -200,8 +260,7 @@ class RuleGroupController extends Controller } // redirect to previous URL. - return redirect(Session::get('rules.rule-group.edit.url')); + return redirect(session('rules.rule-group.edit.url')); } - } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index e9793687f3..b634a85379 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -28,13 +28,15 @@ class SearchController extends Controller public function index(SearchInterface $searcher) { - $subTitle = null; - $rawQuery = null; - $result = []; + $subTitle = null; + $query = null; + $result = []; + $title = trans('firefly.search'); + $mainTitleIcon = 'fa-search'; if (!is_null(Input::get('q')) && strlen(Input::get('q')) > 0) { - $rawQuery = trim(Input::get('q')); - $words = explode(' ', $rawQuery); - $subTitle = trans('firefly.search_results_for', ['query' => $rawQuery]); + $query = trim(Input::get('q')); + $words = explode(' ', $query); + $subTitle = trans('firefly.search_results_for', ['query' => $query]); $transactions = $searcher->searchTransactions($words); $accounts = $searcher->searchAccounts($words); @@ -45,9 +47,7 @@ class SearchController extends Controller } - return view('search.index')->with('title', 'Search')->with('subTitle', $subTitle)->with( - 'mainTitleIcon', 'fa-search' - )->with('query', $rawQuery)->with('result', $result); + return view('search.index', compact('title', 'subTitle', 'mainTitleIcon', 'query', 'result')); } } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index bdd0c6bab5..3002f05d9d 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -1,12 +1,13 @@ tag; $subTitleIcon = 'fa-tag'; + /** @var Collection $journals */ + $journals = $tag->transactionjournals()->expanded()->get(TransactionJournal::QUERYFIELDS); - return view('tags.show', compact('tag', 'subTitle', 'subTitleIcon')); + $sum = $journals->sum( + function (TransactionJournal $journal) { + return TransactionJournal::amount($journal); + } + ); + + return view('tags.show', compact('tag', 'subTitle', 'subTitleIcon', 'journals', 'sum')); } /** @@ -232,25 +241,7 @@ class TagController extends Controller */ public function store(TagFormRequest $request, TagRepositoryInterface $repository) { - if (Input::get('setTag') == 'true') { - $latitude = $request->get('latitude'); - $longitude = $request->get('longitude'); - $zoomLevel = $request->get('zoomLevel'); - } else { - $latitude = null; - $longitude = null; - $zoomLevel = null; - } - - $data = [ - 'tag' => $request->get('tag'), - 'date' => strlen($request->get('date')) > 0 ? new Carbon($request->get('date')) : null, - 'description' => strlen($request->get('description')) > 0 ? $request->get('description') : '', - 'latitude' => $latitude, - 'longitude' => $longitude, - 'zoomLevel' => $zoomLevel, - 'tagMode' => $request->get('tagMode'), - ]; + $data = $request->collectTagData(); $repository->store($data); Session::flash('success', 'The tag has been created!'); @@ -264,7 +255,7 @@ class TagController extends Controller } // redirect to previous URL. - return redirect(Session::get('tags.create.url')); + return redirect(session('tags.create.url')); } @@ -277,27 +268,7 @@ class TagController extends Controller */ public function update(TagFormRequest $request, TagRepositoryInterface $repository, Tag $tag) { - if (Input::get('setTag') == 'true') { - $latitude = $request->get('latitude'); - $longitude = $request->get('longitude'); - $zoomLevel = $request->get('zoomLevel'); - } else { - $latitude = null; - $longitude = null; - $zoomLevel = null; - } - - $data = [ - 'tag' => $request->get('tag'), - 'date' => strlen($request->get('date')) > 0 ? new Carbon($request->get('date')) : null, - 'description' => strlen($request->get('description')) > 0 ? $request->get('description') : '', - 'latitude' => $latitude, - 'longitude' => $longitude, - 'zoomLevel' => $zoomLevel, - 'tagMode' => $request->get('tagMode'), - ]; - - + $data = $request->collectTagData(); $repository->update($tag, $data); Session::flash('success', 'Tag "' . e($data['tag']) . '" updated.'); @@ -311,6 +282,6 @@ class TagController extends Controller } // redirect to previous URL. - return redirect(Session::get('tags.edit.url')); + return redirect(session('tags.edit.url')); } } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 41b0a4975f..8b328be162 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -7,6 +7,7 @@ use Config; use ExpandedForm; use FireflyIII\Events\TransactionJournalStored; use FireflyIII\Events\TransactionJournalUpdated; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Requests\JournalFormRequest; use FireflyIII\Models\PiggyBank; @@ -18,7 +19,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Support\Collection; use Input; -use Log; use Preferences; use Response; use Session; @@ -34,7 +34,7 @@ use View; class TransactionController extends Controller { /** - * @codeCoverageIgnore + * */ public function __construct() { @@ -49,7 +49,7 @@ class TransactionController extends Controller * * @return \Illuminate\View\View */ - public function create(ARI $repository, $what = TransactionType::DEPOSIT) + public function create(ARI $repository, string $what = TransactionType::DEPOSIT) { $what = strtolower($what); $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); @@ -66,7 +66,7 @@ class TransactionController extends Controller $piggies = ExpandedForm::makeSelectList($piggyBanks); $piggies[0] = trans('form.noPiggybank'); - $preFilled = Session::has('preFilled') ? Session::get('preFilled') : []; + $preFilled = Session::has('preFilled') ? session('preFilled') : []; $respondTo = ['account_id', 'account_from_id']; $subTitle = trans('form.add_new_' . $what); @@ -76,7 +76,7 @@ class TransactionController extends Controller Session::put('preFilled', $preFilled); // put previous url in session if not redirect from store (not "create another"). - if (Session::get('transactions.create.fromStore') !== true) { + if (session('transactions.create.fromStore') !== true) { Session::put('transactions.create.url', URL::previous()); } Session::forget('transactions.create.fromStore'); @@ -98,7 +98,7 @@ class TransactionController extends Controller */ public function delete(TransactionJournal $journal) { - $what = strtolower($journal->getTransactionType()); + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]); // put previous url in session @@ -126,7 +126,7 @@ class TransactionController extends Controller Preferences::mark(); // redirect to previous URL: - return redirect(Session::get('transactions.delete.url')); + return redirect(session('transactions.delete.url')); } /** @@ -136,19 +136,14 @@ class TransactionController extends Controller * @param TransactionJournal $journal * * @return $this + * @throws FireflyException */ public function edit(ARI $repository, TransactionJournal $journal) { - // cannot edit opening balance - if ($journal->isOpeningBalance()) { - return view('error')->with('message', 'Cannot edit this transaction. Edit the account instead!'); - } - - $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); $uploadSize = min($maxFileSize, $maxPostSize); - $what = strtolower($journal->getTransactionType()); + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets[0] = trans('form.noBudget'); @@ -157,12 +152,14 @@ class TransactionController extends Controller $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $preFilled = [ 'date' => $journal->date->format('Y-m-d'), + 'interest_date' => $journal->interest_date ? $journal->interest_date->format('Y-m-d') : '', + 'book_date' => $journal->book_date ? $journal->book_date->format('Y-m-d') : '', + 'process_date' => $journal->process_date ? $journal->process_date->format('Y-m-d') : '', 'category' => '', 'budget_id' => 0, 'piggy_bank_id' => 0, + 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), ]; - // get tags: - $preFilled['tags'] = join(',', $journal->tags->pluck('tag')->toArray()); $category = $journal->categories()->first(); if (!is_null($category)) { @@ -178,25 +175,29 @@ class TransactionController extends Controller $preFilled['piggy_bank_id'] = $journal->piggyBankEvents()->orderBy('date', 'DESC')->first()->piggy_bank_id; } - $preFilled['amount'] = $journal->amount_positive; + $preFilled['amount'] = TransactionJournal::amountPositive($journal); if ($journal->isWithdrawal()) { - $preFilled['account_id'] = $journal->source_account->id; - $preFilled['expense_account'] = $journal->destination_account->name_for_editform; + $preFilled['account_id'] = TransactionJournal::sourceAccount($journal)->id; + if (TransactionJournal::destinationAccountTypeStr($journal) != 'Cash account') { + $preFilled['expense_account'] = TransactionJournal::destinationAccount($journal)->name; + } } else { - $preFilled['account_id'] = $journal->destination_account->id; - $preFilled['revenue_account'] = $journal->source_account->name_for_editform; + $preFilled['account_id'] = TransactionJournal::destinationAccount($journal)->id; + if (TransactionJournal::sourceAccountTypeStr($journal) != 'Cash account') { + $preFilled['revenue_account'] = TransactionJournal::sourceAccount($journal)->name; + } } - $preFilled['account_from_id'] = $journal->source_account->id; - $preFilled['account_to_id'] = $journal->destination_account->id; + $preFilled['account_from_id'] = TransactionJournal::sourceAccount($journal)->id; + $preFilled['account_to_id'] = TransactionJournal::destinationAccount($journal)->id; Session::flash('preFilled', $preFilled); Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'edit-' . $what); // put previous url in session if not redirect from store (not "return_to_edit"). - if (Session::get('transactions.edit.fromUpdate') !== true) { + if (session('transactions.edit.fromUpdate') !== true) { Session::put('transactions.edit.url', URL::previous()); } Session::forget('transactions.edit.fromUpdate'); @@ -211,7 +212,7 @@ class TransactionController extends Controller * * @return \Illuminate\View\View */ - public function index(JournalRepositoryInterface $repository, $what) + public function index(JournalRepositoryInterface $repository, string $what) { $subTitleIcon = Config::get('firefly.transactionIconsByWhat.' . $what); $types = Config::get('firefly.transactionTypesByWhat.' . $what); @@ -270,15 +271,14 @@ class TransactionController extends Controller } ); - bcscale(2); $journal->transactions->each( function (Transaction $t) use ($journal, $repository) { $t->before = $repository->getAmountBefore($journal, $t); $t->after = bcadd($t->before, $t->amount); } ); - $what = strtolower($journal->getTransactionType()); - $subTitle = trans('firefly.' . $journal->getTransactionType()) . ' "' . e($journal->description) . '"'; + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; return view('transactions.show', compact('journal', 'events', 'subTitle', 'what')); } @@ -312,7 +312,6 @@ class TransactionController extends Controller if (count($att->getMessages()->get('attachments')) > 0) { Session::flash('info', $att->getMessages()->get('attachments')); } - Log::debug('Before event. From account name is: ' . $journal->source_account->name); event(new TransactionJournalStored($journal, intval($request->get('piggy_bank_id')))); @@ -327,7 +326,7 @@ class TransactionController extends Controller } // redirect to previous URL. - return redirect(Session::get('transactions.create.url')); + return redirect(session('transactions.create.url')); } @@ -372,7 +371,7 @@ class TransactionController extends Controller } // redirect to previous URL. - return redirect(Session::get('transactions.edit.url')); + return redirect(session('transactions.edit.url')); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3c1c958dec..d5d719d6f3 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -1,12 +1,15 @@ [ + 'web' => [ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, ], - 'web-auth' => [ + 'web-auth' => [ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, Authenticate::class, + AuthenticateTwoFactor::class, ], - 'web-auth-range' => [ + 'web-auth-no-two-factor' => [ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, Authenticate::class, + RedirectIfTwoFactorAuthenticated::class, + ], + 'web-auth-range' => [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + Authenticate::class, + AuthenticateTwoFactor::class, Range::class, Binder::class, ], diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index b67cee9d53..483cacaae4 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -1,10 +1,13 @@ ajax()) { return response('Unauthorized.', 401); } else { + return redirect()->guest('login'); + } + } else { + if (intval(Auth::user()->blocked) === 1) { + Auth::guard($guard)->logout(); + Session::flash('logoutMessage', trans('firefly.block_account_logout')); return redirect()->guest('login'); } diff --git a/app/Http/Middleware/AuthenticateTwoFactor.php b/app/Http/Middleware/AuthenticateTwoFactor.php new file mode 100644 index 0000000000..a27ba70151 --- /dev/null +++ b/app/Http/Middleware/AuthenticateTwoFactor.php @@ -0,0 +1,64 @@ +guest()) { + if ($request->ajax()) { + return response('Unauthorized.', 401); + } else { + return redirect()->guest('login'); + } + } else { + + if (intval(Auth::user()->blocked) === 1) { + Auth::guard($guard)->logout(); + Session::flash('logoutMessage', trans('firefly.block_account_logout')); + + return redirect()->guest('login'); + } + } + $twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', false)->data; + $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); + $isTwoFactorAuthenticated = Session::get('twofactor-authenticated'); + if ($twoFactorAuthEnabled && $hasTwoFactorAuthSecret && !$isTwoFactorAuthenticated) { + return redirect(route('two-factor')); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Binder.php b/app/Http/Middleware/Binder.php index c35b442513..a7d2c1d278 100644 --- a/app/Http/Middleware/Binder.php +++ b/app/Http/Middleware/Binder.php @@ -1,4 +1,5 @@ check()) { + + $twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', false)->data; + $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); + $isTwoFactorAuthenticated = Session::get('twofactor-authenticated'); + if ($twoFactorAuthEnabled && $hasTwoFactorAuthSecret && $isTwoFactorAuthenticated) { + return redirect('/'); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 413dde74b3..62c21ded02 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -1,4 +1,5 @@ 'iban', 'virtualBalance' => 'numeric', 'openingBalanceDate' => 'date', + 'accountNumber' => 'between:1,255|uniqueAccountNumberForUser', 'accountRole' => 'in:' . $accountRoles, 'active' => 'boolean', 'ccType' => 'in:' . $ccPaymentTypes, diff --git a/app/Http/Requests/AttachmentFormRequest.php b/app/Http/Requests/AttachmentFormRequest.php index 50a6665591..0f1583869a 100644 --- a/app/Http/Requests/AttachmentFormRequest.php +++ b/app/Http/Requests/AttachmentFormRequest.php @@ -1,4 +1,5 @@ subDay()->format('Y-m-d'); + $today = Carbon::create()->addDay()->format('Y-m-d'); + $formats = join(',', array_keys(config('firefly.export_formats'))); + + return [ + 'export_start_range' => 'required|date|after:' . $first, + 'export_end_range' => 'required|date|before:' . $today, + 'accounts' => 'required', + 'job' => 'required|belongsToUser:export_jobs,key', + 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', + 'include_attachments' => 'in:0,1', + 'include_config' => 'in:0,1', + 'exportFormat' => 'in:' . $formats, + ]; + } +} diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index b3377b4a47..a40760a54d 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -1,17 +1,19 @@ get('tags') ?? ''; + return [ 'what' => $this->get('what'), 'description' => $this->get('description'), 'account_id' => intval($this->get('account_id')), 'account_from_id' => intval($this->get('account_from_id')), 'account_to_id' => intval($this->get('account_to_id')), - 'expense_account' => $this->get('expense_account'), - 'revenue_account' => $this->get('revenue_account'), + 'expense_account' => $this->get('expense_account') ?? '', + 'revenue_account' => $this->get('revenue_account') ?? '', 'amount' => round($this->get('amount'), 2), 'user' => Auth::user()->id, 'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')), 'date' => new Carbon($this->get('date')), + 'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null, + 'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null, + 'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null, 'budget_id' => intval($this->get('budget_id')), - 'category' => $this->get('category'), - 'tags' => explode(',', $this->get('tags')), + 'category' => $this->get('category') ?? '', + 'tags' => explode(',', $tags), ]; } @@ -60,6 +67,9 @@ class JournalFormRequest extends Request 'what' => 'required|in:withdrawal,deposit,transfer', 'amount' => 'numeric|required|min:0.01', 'date' => 'required|date', + 'process_date' => 'date', + 'book_date' => 'date', + 'interest_date' => 'date', 'amount_currency_id_amount' => 'required|exists:transaction_currencies,id', ]; @@ -84,8 +94,7 @@ class JournalFormRequest extends Request $rules['category'] = 'between:1,255'; break; default: - abort(500, 'Cannot handle ' . $what); - break; + throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.'); } return $rules; diff --git a/app/Http/Requests/NewUserFormRequest.php b/app/Http/Requests/NewUserFormRequest.php index a7fa888e43..d707b02caa 100644 --- a/app/Http/Requests/NewUserFormRequest.php +++ b/app/Http/Requests/NewUserFormRequest.php @@ -1,4 +1,5 @@ subDay()->format('Y-m-d'); + $today = Carbon::create()->addDay()->format('Y-m-d'); + + return [ + 'start_date' => 'required|date|after:' . $first, + 'end_date' => 'required|date|before:' . $today, + 'accounts' => 'required', + 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', + ]; + } +} diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php index a4f5da9021..531770b38f 100644 --- a/app/Http/Requests/TagFormRequest.php +++ b/app/Http/Requests/TagFormRequest.php @@ -1,14 +1,16 @@ get('latitude'); + $longitude = $this->get('longitude'); + $zoomLevel = $this->get('zoomLevel'); + } else { + $latitude = null; + $longitude = null; + $zoomLevel = null; + } + $date = $this->get('date') ?? ''; + + $data = [ + 'tag' => $this->get('tag'), + 'date' => strlen($date) > 0 ? new Carbon($date) : null, + 'description' => $this->get('description') ?? '', + 'latitude' => $latitude, + 'longitude' => $longitude, + 'zoomLevel' => $zoomLevel, + 'tagMode' => $this->get('tagMode'), + ]; + + return $data; + + + } + /** * @return array */ diff --git a/app/Http/Requests/TestRuleFormRequest.php b/app/Http/Requests/TestRuleFormRequest.php new file mode 100644 index 0000000000..8a69aa35d1 --- /dev/null +++ b/app/Http/Requests/TestRuleFormRequest.php @@ -0,0 +1,55 @@ + 'required|min:1|in:' . join(',', $validTriggers), + 'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue', + ]; + + return $rules; + } +} diff --git a/app/Http/Requests/TokenFormRequest.php b/app/Http/Requests/TokenFormRequest.php new file mode 100644 index 0000000000..6bfacf9737 --- /dev/null +++ b/app/Http/Requests/TokenFormRequest.php @@ -0,0 +1,37 @@ + 'required|2faCode', + ]; + + return $rules; + } +} diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 71b894a813..7c05a79ac9 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -1,18 +1,23 @@ push(trans('breadcrumbs.home'), route('index')); } ); -//trans('breadcrumbs.') -// accounts +/** + * ACCOUNTS + */ Breadcrumbs::register( - 'accounts.index', function (BreadCrumbGenerator $breadcrumbs, $what) { + 'accounts.index', function (BreadCrumbGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.' . strtolower(e($what)) . '_accounts'), route('accounts.index', [$what])); } ); Breadcrumbs::register( - 'accounts.create', function (BreadCrumbGenerator $breadcrumbs, $what) { + 'accounts.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('accounts.index', $what); $breadcrumbs->push(trans('firefly.new_' . strtolower(e($what)) . '_account'), route('accounts.create', [$what])); } @@ -73,7 +79,77 @@ Breadcrumbs::register( } ); -// budgets. +/** + * ATTACHMENTS + */ +Breadcrumbs::register( + 'attachments.edit', function (BreadCrumbGenerator $breadcrumbs, Attachment $attachment) { + $object = $attachment->attachable; + if ($object instanceof TransactionJournal) { + $breadcrumbs->parent('transactions.show', $object); + $breadcrumbs->push($attachment->filename, route('attachments.edit', [$attachment])); + + } else { + throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); + } + +} +); +Breadcrumbs::register( + 'attachments.delete', function (BreadCrumbGenerator $breadcrumbs, Attachment $attachment) { + + $object = $attachment->attachable; + if ($object instanceof TransactionJournal) { + $breadcrumbs->parent('transactions.show', $object); + $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => $attachment->filename]), route('attachments.edit', [$attachment])); + + } else { + throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); + } +} +); + +/** + * BILLS + */ +Breadcrumbs::register( + 'bills.index', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.bills'), route('bills.index')); +} +); +Breadcrumbs::register( + 'bills.create', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('bills.index'); + $breadcrumbs->push(trans('breadcrumbs.newBill'), route('bills.create')); +} +); + +Breadcrumbs::register( + 'bills.edit', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.show', $bill); + $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => e($bill->name)]), route('bills.edit', [$bill->id])); +} +); +Breadcrumbs::register( + 'bills.delete', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.show', $bill); + $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => e($bill->name)]), route('bills.delete', [$bill->id])); +} +); + +Breadcrumbs::register( + 'bills.show', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.index'); + $breadcrumbs->push(e($bill->name), route('bills.show', [$bill->id])); + +} +); + + +/** + * BUDGETS + */ Breadcrumbs::register( 'budgets.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -119,7 +195,9 @@ Breadcrumbs::register( } ); -// categories +/** + * CATEGORIES + */ Breadcrumbs::register( 'categories.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -174,7 +252,9 @@ Breadcrumbs::register( } ); -// CSV: +/** + * CSV CONTROLLER + */ Breadcrumbs::register( 'csv.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -210,8 +290,9 @@ Breadcrumbs::register( } ); - -// currencies. +/** + * CURRENCIES + */ Breadcrumbs::register( 'currency.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -239,8 +320,19 @@ Breadcrumbs::register( } ); +/** + * EXPORT + */ +Breadcrumbs::register( + 'export.index', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.export_data'), route('export.index')); +} +); -// piggy banks +/** + * PIGGY BANKS + */ Breadcrumbs::register( 'piggy-banks.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -275,7 +367,9 @@ Breadcrumbs::register( } ); -// preferences +/** + * PREFERENCES + */ Breadcrumbs::register( 'preferences', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -284,7 +378,17 @@ Breadcrumbs::register( } ); -// profile +Breadcrumbs::register( + 'preferences.code', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences')); + +} +); + +/** + * PROFILE + */ Breadcrumbs::register( 'profile', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -307,42 +411,9 @@ Breadcrumbs::register( } ); -// bills -Breadcrumbs::register( - 'bills.index', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.bills'), route('bills.index')); -} -); -Breadcrumbs::register( - 'bills.create', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('bills.index'); - $breadcrumbs->push(trans('breadcrumbs.newBill'), route('bills.create')); -} -); - -Breadcrumbs::register( - 'bills.edit', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => e($bill->name)]), route('bills.edit', [$bill->id])); -} -); -Breadcrumbs::register( - 'bills.delete', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => e($bill->name)]), route('bills.delete', [$bill->id])); -} -); - -Breadcrumbs::register( - 'bills.show', function (BreadCrumbGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.index'); - $breadcrumbs->push(e($bill->name), route('bills.show', [$bill->id])); - -} -); - -// reports +/** + * REPORTS + */ Breadcrumbs::register( 'reports.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -374,7 +445,56 @@ Breadcrumbs::register( } ); -// search +Breadcrumbs::register( + 'rules.rule.create', function (BreadCrumbGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.rule.create', [$ruleGroup])); +} +); +Breadcrumbs::register( + 'rules.rule.edit', function (BreadCrumbGenerator $breadcrumbs, Rule $rule) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.edit_rule', ['title' => $rule->title]), route('rules.rule.edit', [$rule])); +} +); +Breadcrumbs::register( + 'rules.rule.delete', function (BreadCrumbGenerator $breadcrumbs, Rule $rule) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.delete_rule', ['title' => $rule->title]), route('rules.rule.delete', [$rule])); +} +); +Breadcrumbs::register( + 'rules.rule-group.create', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.make_new_rule_group'), route('rules.rule-group.create')); +} +); +Breadcrumbs::register( + 'rules.rule-group.edit', function (BreadCrumbGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]), route('rules.rule-group.edit', [$ruleGroup])); +} +); +Breadcrumbs::register( + 'rules.rule-group.delete', function (BreadCrumbGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]), route('rules.rule-group.delete', [$ruleGroup])); +} +); + +Breadcrumbs::register( + 'rules.rule-group.select_transactions', function (BreadCrumbGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push( + trans('firefly.execute_group_on_existing_transactions', ['title' => $ruleGroup->title]), route('rules.rule-group.select_transactions', [$ruleGroup]) + ); +} +); + + +/** + * SEARCH + */ Breadcrumbs::register( 'search', function (BreadCrumbGenerator $breadcrumbs, $query) { $breadcrumbs->parent('home'); @@ -382,43 +502,9 @@ Breadcrumbs::register( } ); -// transactions -Breadcrumbs::register( - 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, $what) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); -} -); -Breadcrumbs::register( - 'transactions.create', function (BreadCrumbGenerator $breadcrumbs, $what) { - $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); -} -); - -Breadcrumbs::register( - 'transactions.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit', [$journal->id])); -} -); -Breadcrumbs::register( - 'transactions.delete', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.delete_journal', ['description' => e($journal->description)]), route('transactions.delete', [$journal->id])); -} -); - -Breadcrumbs::register( - 'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { - - $breadcrumbs->parent('transactions.index', strtolower($journal->getTransactionType())); - $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); - -} -); - -// tags +/** + * TAGS + */ Breadcrumbs::register( 'tags.index', function (BreadCrumbGenerator $breadcrumbs) { $breadcrumbs->parent('home'); @@ -454,3 +540,42 @@ Breadcrumbs::register( $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id])); } ); + +/** + * TRANSACTIONS + */ +Breadcrumbs::register( + 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); +} +); +Breadcrumbs::register( + 'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { + $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); +} +); + +Breadcrumbs::register( + 'transactions.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit', [$journal->id])); +} +); +Breadcrumbs::register( + 'transactions.delete', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.delete_journal', ['description' => e($journal->description)]), route('transactions.delete', [$journal->id])); +} +); + +Breadcrumbs::register( + 'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { + + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); + +} +); diff --git a/app/Http/routes.php b/app/Http/routes.php index d0554de944..fc0929b5b7 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,4 +1,5 @@ 'web-auth-no-two-factor'], function () { + Route::get('/two-factor', ['uses' => 'Auth\TwoFactorController@index', 'as' => 'two-factor']); + Route::get('/lost-two-factor', ['uses' => 'Auth\TwoFactorController@lostTwoFactor', 'as' => 'lost-two-factor']); + Route::post('/two-factor', ['uses' => 'Auth\TwoFactorController@postIndex', 'as' => 'two-factor-post']); + Route::get('/flush', ['uses' => 'HomeController@flush']); } ); @@ -34,7 +48,8 @@ Route::group( Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']); Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']); Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']); - Route::get('/flush', ['uses' => 'HomeController@flush', 'as' => 'flush']); + + Route::get('/routes', ['uses' => 'HomeController@routes']); /** * Account Controller */ @@ -58,7 +73,6 @@ Route::group( Route::get('/attachment/edit/{attachment}', ['uses' => 'AttachmentController@edit', 'as' => 'attachments.edit']); Route::get('/attachment/delete/{attachment}', ['uses' => 'AttachmentController@delete', 'as' => 'attachments.delete']); - Route::get('/attachment/show/{attachment}', ['uses' => 'AttachmentController@show', 'as' => 'attachments.show']); Route::get('/attachment/preview/{attachment}', ['uses' => 'AttachmentController@preview', 'as' => 'attachments.preview']); Route::get('/attachment/download/{attachment}', ['uses' => 'AttachmentController@download', 'as' => 'attachments.download']); @@ -133,6 +147,14 @@ Route::group( Route::post('/currency/update/{currency}', ['uses' => 'CurrencyController@update', 'as' => 'currency.update']); Route::post('/currency/destroy/{currency}', ['uses' => 'CurrencyController@destroy', 'as' => 'currency.destroy']); + /** + * Export Controller + */ + Route::get('/export', ['uses' => 'ExportController@index', 'as' => 'export.index']); + Route::post('/export/submit', ['uses' => 'ExportController@postIndex', 'as' => 'export.export']); + Route::get('/export/status/{jobKey}', ['uses' => 'ExportController@getStatus', 'as' => 'export.status']); + Route::get('/export/download/{jobKey}', ['uses' => 'ExportController@download', 'as' => 'export.download']); + /** * ALL CHART Controllers @@ -178,6 +200,7 @@ Route::group( // reports: Route::get('/chart/report/in-out/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOut']); Route::get('/chart/report/in-out-sum/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOutSummarized']); + Route::get('/chart/report/net-worth/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@netWorth']); /** @@ -232,6 +255,9 @@ Route::group( */ Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']); Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']); + Route::get('/preferences/code', ['uses' => 'PreferencesController@code', 'as' => 'preferences.code']); + Route::get('/preferences/delete-code', ['uses' => 'PreferencesController@deleteCode', 'as' => 'preferences.delete-code']); + Route::post('/preferences/code', ['uses' => 'PreferencesController@postCode']); /** * Profile Controller @@ -260,6 +286,7 @@ Route::group( Route::get('/rules/rules/down/{rule}', ['uses' => 'RuleController@down', 'as' => 'rules.rule.down']); Route::get('/rules/rules/edit/{rule}', ['uses' => 'RuleController@edit', 'as' => 'rules.rule.edit']); Route::get('/rules/rules/delete/{rule}', ['uses' => 'RuleController@delete', 'as' => 'rules.rule.delete']); + Route::get('/rules/rules/test_triggers', ['uses' => 'RuleController@testTriggers', 'as' => 'rules.rule.test_triggers']); // rules POST: Route::post('/rules/rules/trigger/reorder/{rule}', ['uses' => 'RuleController@reorderRuleTriggers']); @@ -275,11 +302,15 @@ Route::group( Route::get('/rules/groups/delete/{ruleGroup}', ['uses' => 'RuleGroupController@delete', 'as' => 'rules.rule-group.delete']); Route::get('/rules/groups/up/{ruleGroup}', ['uses' => 'RuleGroupController@up', 'as' => 'rules.rule-group.up']); Route::get('/rules/groups/down/{ruleGroup}', ['uses' => 'RuleGroupController@down', 'as' => 'rules.rule-group.down']); + Route::get( + '/rules/groups/select_transactions/{ruleGroup}', ['uses' => 'RuleGroupController@selectTransactions', 'as' => 'rules.rule-group.select_transactions'] + ); // rule groups POST Route::post('/rules/groups/store', ['uses' => 'RuleGroupController@store', 'as' => 'rules.rule-group.store']); Route::post('/rules/groups/update/{ruleGroup}', ['uses' => 'RuleGroupController@update', 'as' => 'rules.rule-group.update']); Route::post('/rules/groups/destroy/{ruleGroup}', ['uses' => 'RuleGroupController@destroy', 'as' => 'rules.rule-group.destroy']); + Route::post('/rules/groups/execute/{ruleGroup}', ['uses' => 'RuleGroupController@execute', 'as' => 'rules.rule-group.execute']); /** * Search Controller diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php new file mode 100644 index 0000000000..242f9d85f1 --- /dev/null +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -0,0 +1,175 @@ +ruleGroup = $ruleGroup; + } + + /** + * @return Collection + */ + public function getAccounts() + { + return $this->accounts; + } + + /** + * + * @param Collection $accounts + */ + public function setAccounts(Collection $accounts) + { + $this->accounts = $accounts; + } + + /** + * @return \Carbon\Carbon + */ + public function getEndDate() + { + return $this->endDate; + } + + /** + * + * @param Carbon $date + */ + public function setEndDate(Carbon $date) + { + $this->endDate = $date; + } + + /** + * @return \Carbon\Carbon + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * + * @param Carbon $date + */ + public function setStartDate(Carbon $date) + { + $this->startDate = $date; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // Lookup all journals that match the parameters specified + $journals = $this->collectJournals(); + + // Find processors for each rule within the current rule group + $processors = $this->collectProcessors(); + + // Execute the rules for each transaction + foreach ($journals as $journal) { + /** @var Processor $processor */ + foreach ($processors as $processor) { + $processor->handleTransactionJournal($journal); + + // Stop processing this group if the rule specifies 'stop_processing' + if ($processor->getRule()->stop_processing) { + break; + } + } + } + } + + /** + * Collect all journals that should be processed + * + * @return Collection + */ + protected function collectJournals() + { + $args = [$this->accounts, $this->user, $this->startDate, $this->endDate]; + $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); + + return $journalCollector->collect(); + } + + /** + * Collects a list of rule processors, one for each rule within the rule group + * + * @return array + */ + protected function collectProcessors() + { + // Find all rules belonging to this rulegroup + $rules = $this->ruleGroup->rules() + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', 1) + ->get(['rules.*']); + + // Create a list of processors for these rules + return array_map( + function ($rule) { + return Processor::make($rule); + }, $rules->all() + ); + } + +} diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php index 264fe0f180..97a3858ccc 100644 --- a/app/Jobs/Job.php +++ b/app/Jobs/Job.php @@ -1,4 +1,5 @@ user = $user; + $this->destination = $destination; + $this->ipAddress = $ipAddress; + $this->exception = $exceptionData; + + Log::debug('In mail job constructor for error handler.'); + Log::error('Exception is: ' . json_encode($exceptionData)); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Log::debug('Start of handle()'); + if ($this->attempts() < 3) { + // mail? + try { + $email = env('SITE_OWNER'); + $args = $this->exception; + $args['loggedIn'] = !is_null($this->user->id); + $args['user'] = $this->user; + $args['ip'] = $this->ipAddress; + + Mail::send( + ['emails.error-html', 'emails.error'], $args, + function (Message $message) use ($email) { + if ($email != 'mail@example.com') { + $message->to($email, $email)->subject('Caught an error in Firely III.'); + } + } + ); + } catch (Swift_TransportException $e) { + // could also not mail! :o + Log::error('Swift Transport Exception' . $e->getMessage()); + } catch (ErrorException $e) { + Log::error('ErrorException ' . $e->getMessage()); + } + Log::debug('Successfully handled error.'); + } + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 1bdfd2ad71..28a3c904ec 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -37,6 +37,18 @@ use Watson\Validating\ValidatingTrait; * @property \Carbon\Carbon $lastActivityDate * @property float $piggyBalance * @property float $percentage + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereDeletedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereAccountTypeId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereActive($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereEncrypted($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereVirtualBalance($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account whereIban($value) + * @mixin \Eloquent */ class Account extends Model { @@ -134,7 +146,6 @@ class Account extends Model } /** - * @codeCoverageIgnore * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function accountMeta() @@ -171,11 +182,9 @@ class Account extends Model * * @param string $fieldName * - * @codeCoverageIgnore - * - * @return string|null + * @return string */ - public function getMeta($fieldName) + public function getMeta($fieldName): string { foreach ($this->accountMeta as $meta) { if ($meta->name == $fieldName) { @@ -183,8 +192,7 @@ class Account extends Model } } - return null; - + return ''; } /** diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index cf44fb4c8a..aaa4f2ca44 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -12,6 +12,13 @@ use Illuminate\Database\Eloquent\Model; * @property string $name * @property string $data * @property-read Account $account + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereAccountId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountMeta whereData($value) + * @mixin \Eloquent */ class AccountMeta extends Model { diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index bf4727ee19..7af6352c90 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -11,6 +11,12 @@ use Illuminate\Database\Eloquent\Model; * @property string $type * @property boolean $editable * @property-read \Illuminate\Database\Eloquent\Collection|Account[] $accounts + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountType whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountType whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountType whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountType whereType($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AccountType whereEditable($value) + * @mixin \Eloquent */ class AccountType extends Model { diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index f0aa86f69a..7685847ac2 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -1,4 +1,5 @@ user_id == Auth::user()->id) { + return $value; + } + } + throw new NotFoundHttpException; + } + /** * Get all of the owning imageable models. */ @@ -44,14 +77,30 @@ class Attachment extends Model } /** - * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * Returns the expected filename for this attachment. + * + * @return string */ - public function user() + public function fileName(): string { - return $this->belongsTo('FireflyIII\User'); + return sprintf('at-%s.data', strval($this->id)); } + /** + * @codeCoverageIgnore + * + * @param $value + * + * @return null|string + */ + public function getDescriptionAttribute($value) + { + if (is_null($value)) { + return null; + } + + return Crypt::decrypt($value); + } /** * @codeCoverageIgnore @@ -69,14 +118,6 @@ class Attachment extends Model return Crypt::decrypt($value); } - /** - * @param string $value - */ - public function setFilenameAttribute($value) - { - $this->attributes['filename'] = Crypt::encrypt($value); - } - /** * @codeCoverageIgnore * @@ -94,11 +135,19 @@ class Attachment extends Model } /** - * @param string $value + * @codeCoverageIgnore + * + * @param $value + * + * @return null|string */ - public function setMimeAttribute($value) + public function getNotesAttribute($value) { - $this->attributes['mime'] = Crypt::encrypt($value); + if (is_null($value)) { + return null; + } + + return Crypt::decrypt($value); } /** @@ -117,30 +166,6 @@ class Attachment extends Model return Crypt::decrypt($value); } - /** - * @param string $value - */ - public function setTitleAttribute($value) - { - $this->attributes['title'] = Crypt::encrypt($value); - } - - /** - * @codeCoverageIgnore - * - * @param $value - * - * @return null|string - */ - public function getDescriptionAttribute($value) - { - if (is_null($value)) { - return null; - } - - return Crypt::decrypt($value); - } - /** * @param string $value */ @@ -150,19 +175,19 @@ class Attachment extends Model } /** - * @codeCoverageIgnore - * - * @param $value - * - * @return null|string + * @param string $value */ - public function getNotesAttribute($value) + public function setFilenameAttribute($value) { - if (is_null($value)) { - return null; - } + $this->attributes['filename'] = Crypt::encrypt($value); + } - return Crypt::decrypt($value); + /** + * @param string $value + */ + public function setMimeAttribute($value) + { + $this->attributes['mime'] = Crypt::encrypt($value); } /** @@ -174,19 +199,20 @@ class Attachment extends Model } /** - * @param Attachment $value - * - * @return Attachment + * @param string $value */ - public static function routeBinder(Attachment $value) + public function setTitleAttribute($value) { - if (Auth::check()) { + $this->attributes['title'] = Crypt::encrypt($value); + } - if ($value->user_id == Auth::user()->id) { - return $value; - } - } - throw new NotFoundHttpException; + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo('FireflyIII\User'); } } diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 5094390e7c..167ea38842 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -27,6 +27,22 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property-read \FireflyIII\User $user * @property \Carbon\Carbon $nextExpectedMatch * @property \Carbon\Carbon $lastFoundMatch + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereMatch($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereAmountMin($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereAmountMax($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereDate($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereActive($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereAutomatch($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereRepeatFreq($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereSkip($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereNameEncrypted($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill whereMatchEncrypted($value) + * @mixin \Eloquent */ class Bill extends Model { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 231c70bcf5..8b26f14c3c 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -24,6 +24,15 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $budgeted * @property float $amount * @property \Carbon\Carbon $date + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereDeletedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereActive($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereEncrypted($value) + * @mixin \Eloquent */ class Budget extends Model { diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index de3733c353..884c439e4a 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -14,7 +14,17 @@ use Illuminate\Database\Eloquent\Model; * @property boolean $repeats * @property string $repeat_freq * @property-read Budget $budget + * @property int $component_id * @property-read \Illuminate\Database\Eloquent\Collection|LimitRepetition[] $limitrepetitions + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereBudgetId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereStartdate($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereAmount($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereRepeats($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\BudgetLimit whereRepeatFreq($value) + * @mixin \Eloquent */ class BudgetLimit extends Model { diff --git a/app/Models/Category.php b/app/Models/Category.php index f18ddb75a4..ef6d20c3f9 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -22,6 +22,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property float $spent * @property \Carbon\Carbon $lastActivity * @property string $type + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereDeletedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereEncrypted($value) + * @mixin \Eloquent */ class Category extends Model { diff --git a/app/Models/Component.php b/app/Models/Component.php index 52f4dc4ba8..5e8214d2aa 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -5,15 +5,23 @@ use Illuminate\Database\Eloquent\Model; /** * Class Component * - * @property int $transaction_journal_id + * @property int $transaction_journal_id * @package FireflyIII\Models - * @property integer $id + * @property integer $id * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property \Carbon\Carbon $deleted_at - * @property string $name - * @property integer $user_id - * @property string $class + * @property string $name + * @property integer $user_id + * @property string $class + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereDeletedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereName($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Component whereClass($value) + * @mixin \Eloquent */ class Component extends Model { diff --git a/app/Models/ExportJob.php b/app/Models/ExportJob.php new file mode 100644 index 0000000000..e37316f7cc --- /dev/null +++ b/app/Models/ExportJob.php @@ -0,0 +1,74 @@ +where('user_id', Auth::user()->id)->first(); + if (!is_null($model)) { + return $model; + } + } + throw new NotFoundHttpException; + } + + /** + * @param $status + */ + public function change($status) + { + Log::debug('Job ' . $this->key . ' to status "' . $status . '".'); + $this->status = $status; + $this->save(); + } + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo('FireflyIII\User'); + } +} diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php index e08bcc1160..7257b67689 100644 --- a/app/Models/LimitRepetition.php +++ b/app/Models/LimitRepetition.php @@ -16,6 +16,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property float $amount * @property-read BudgetLimit $budgetLimit * @property int $budget_id + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereBudgetLimitId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereStartdate($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereEnddate($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LimitRepetition whereAmount($value) + * @mixin \Eloquent */ class LimitRepetition extends Model { diff --git a/app/Models/Permission.php b/app/Models/Permission.php deleted file mode 100644 index f0b6d51c46..0000000000 --- a/app/Models/Permission.php +++ /dev/null @@ -1,20 +0,0 @@ -belongsToMany('FireflyIII\User'); + } + } diff --git a/app/Models/Rule.php b/app/Models/Rule.php index faf2110825..7805f32752 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -1,4 +1,5 @@ 'required|boolean', ]; - /** @var bool */ - private $joinedTransactionTypes; - /** * @param $value * @@ -142,46 +184,6 @@ class TransactionJournal extends Model return $this->belongsToMany('FireflyIII\Models\Category'); } - /** - * @return float - */ - public function getAmountAttribute() - { - $cache = new CacheProperties(); - $cache->addProperty($this->id); - $cache->addProperty('amount'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - bcscale(2); - $transaction = $this->transactions->sortByDesc('amount')->first(); - $amount = $transaction->amount; - if ($this->isWithdrawal()) { - $amount = $amount * -1; - } - $cache->store($amount); - - return $amount; - - } - - /** - * @return string - */ - public function getAmountPositiveAttribute() - { - $amount = '0'; - /** @var Transaction $t */ - foreach ($this->transactions as $t) { - if ($t->amount > 0) { - $amount = $t->amount; - } - } - - return $amount; - } - /** * @codeCoverageIgnore * @@ -199,31 +201,49 @@ class TransactionJournal extends Model } /** - * @return Account - */ - public function getDestinationAccountAttribute() - { - $account = $this->transactions()->where('amount', '>', 0)->first()->account; - - return $account; - } - - /** - * @return Account - */ - public function getSourceAccountAttribute() - { - $account = $this->transactions()->where('amount', '<', 0)->first()->account; - - return $account; - } - - /** + * @param $value + * * @return string */ - public function getTransactionType() + public function getDestinationAccountNameAttribute($value) { - return $this->transactionType->type; + if (!is_null($value) && strlen(strval($value)) > 0) { + return Crypt::decrypt($value); + } + + return null; + } + + /** + * + * @param string $fieldName + * + * @return string + */ + public function getMeta($fieldName): string + { + foreach ($this->transactionjournalmeta as $meta) { + if ($meta->name == $fieldName) { + return $meta->data; + } + } + + return ''; + } + + /** + * @param $value + * + * @return string + */ + public function getSourceAccountNameAttribute($value) + { + if (!is_null($value) && strlen(strval($value)) > 0) { + return Crypt::decrypt($value); + } + + return null; + } /** @@ -231,44 +251,47 @@ class TransactionJournal extends Model */ public function isDeposit() { - if (!is_null($this->type)) { - return $this->type == TransactionType::DEPOSIT; + if (!is_null($this->transaction_type_type)) { + return $this->transaction_type_type == TransactionType::DEPOSIT; } return $this->transactionType->isDeposit(); } /** + * * @return bool */ public function isOpeningBalance() { - if (!is_null($this->type)) { - return $this->type == TransactionType::OPENING_BALANCE; + if (!is_null($this->transaction_type_type)) { + return $this->transaction_type_type == TransactionType::OPENING_BALANCE; } return $this->transactionType->isOpeningBalance(); } /** + * * @return bool */ public function isTransfer() { - if (!is_null($this->type)) { - return $this->type == TransactionType::TRANSFER; + if (!is_null($this->transaction_type_type)) { + return $this->transaction_type_type == TransactionType::TRANSFER; } return $this->transactionType->isTransfer(); } /** + * * @return bool */ public function isWithdrawal() { - if (!is_null($this->type)) { - return $this->type == TransactionType::WITHDRAWAL; + if (!is_null($this->transaction_type_type)) { + return $this->transaction_type_type == TransactionType::WITHDRAWAL; } return $this->transactionType->isWithdrawal(); @@ -324,6 +347,48 @@ class TransactionJournal extends Model return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); } + /** + * @param EloquentBuilder $query + */ + public function scopeExpanded(EloquentBuilder $query) + { + // left join transaction type: + if (!self::isJoined($query, 'transaction_types')) { + $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); + } + + // left join transaction currency: + $query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id'); + + // left join destination (for amount and account info). + $query->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') + ->where('destination.amount', '>', 0); + } + ); + // join destination account + $query->leftJoin('accounts as destination_account', 'destination_account.id', '=', 'destination.account_id'); + // join destination account type + $query->leftJoin('account_types as destination_acct_type', 'destination_account.account_type_id', '=', 'destination_acct_type.id'); + + // left join source (for amount and account info). + $query->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') + ->where('source.amount', '<', 0); + } + ); + // join destination account + $query->leftJoin('accounts as source_account', 'source_account.id', '=', 'source.account_id'); + // join destination account type + $query->leftJoin('account_types as source_acct_type', 'source_account.account_type_id', '=', 'source_acct_type.id'); + + $query->with(['categories', 'budgets', 'attachments', 'bill']); + + + } + /** * @codeCoverageIgnore * @@ -332,31 +397,13 @@ class TransactionJournal extends Model */ public function scopeTransactionTypes(EloquentBuilder $query, array $types) { - if (is_null($this->joinedTransactionTypes)) { - $query->leftJoin( - 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' - ); - $this->joinedTransactionTypes = true; + + if (!self::isJoined($query, 'transaction_types')) { + $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); } $query->whereIn('transaction_types.type', $types); } - /** - * @codeCoverageIgnore - * Automatically includes the 'with' parameters to get relevant related - * objects. - * - * @param EloquentBuilder $query - */ - public function scopeWithRelevantData(EloquentBuilder $query) - { - $query->with( - ['transactions' => function (HasMany $q) { - $q->orderBy('amount', 'ASC'); - }, 'transactionType', 'transactionCurrency', 'budgets', 'categories', 'transactions.account.accounttype', 'bill'] - ); - } - /** * @codeCoverageIgnore * @@ -404,6 +451,14 @@ class TransactionJournal extends Model return $this->belongsToMany('FireflyIII\Models\TransactionGroup'); } + /** + * @return HasMany + */ + public function transactionjournalmeta(): HasMany + { + return $this->hasMany('FireflyIII\Models\TransactionJournalMeta'); + } + /** * @codeCoverageIgnore * @return \Illuminate\Database\Eloquent\Relations\HasMany diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php new file mode 100644 index 0000000000..37bff90fae --- /dev/null +++ b/app/Models/TransactionJournalMeta.php @@ -0,0 +1,49 @@ +belongsTo('FireflyIII\Models\TransactionJournal'); + } +} diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 801cc9c6d0..c6c39a1946 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -12,6 +12,12 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property \Carbon\Carbon $deleted_at * @property string $type * @property-read \Illuminate\Database\Eloquent\Collection|TransactionJournal[] $transactionJournals + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType whereId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType whereDeletedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType whereType($value) + * @mixin \Eloquent */ class TransactionType extends Model { diff --git a/app/Providers/AccountServiceProvider.php b/app/Providers/AccountServiceProvider.php new file mode 100644 index 0000000000..b8bd3dec20 --- /dev/null +++ b/app/Providers/AccountServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Account\AccountRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Account\AccountRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Account\AccountRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ca50e9b7ee..e370e91496 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -1,4 +1,5 @@ app->bind( + 'FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Attachment\AttachmentRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Attachment\AttachmentRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 7d3f4d2b73..db0aa37305 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -1,4 +1,5 @@ app->bind( + 'FireflyIII\Repositories\Bill\BillRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Bill\BillRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Bill\BillRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/BudgetServiceProvider.php b/app/Providers/BudgetServiceProvider.php new file mode 100644 index 0000000000..02364b9eb5 --- /dev/null +++ b/app/Providers/BudgetServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Budget\BudgetRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Budget\BudgetRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Budget\BudgetRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/CategoryServiceProvider.php b/app/Providers/CategoryServiceProvider.php new file mode 100644 index 0000000000..7840fe6071 --- /dev/null +++ b/app/Providers/CategoryServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Category\CategoryRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Category\CategoryRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Category\CategoryRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7d0b742adf..5b742be2b7 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -1,4 +1,5 @@ [ 'FireflyIII\Handlers\Events\ScanForBillsAfterUpdate', - 'FireflyIII\Handlers\Events\UpdateJournalConnection', 'FireflyIII\Handlers\Events\FireRulesForUpdate', ], 'FireflyIII\Events\TransactionJournalStored' => [ 'FireflyIII\Handlers\Events\ScanForBillsAfterStore', - 'FireflyIII\Handlers\Events\ConnectJournalToPiggyBank', 'FireflyIII\Handlers\Events\FireRulesForStore', ], + 'Illuminate\Auth\Events\Logout' => [ + 'FireflyIII\Handlers\Events\UserEventListener@onUserLogout', + ], ]; - /** * Register any other events for your application. * @@ -92,6 +93,26 @@ class EventServiceProvider extends ServiceProvider // } + /** + * + */ + protected function registerCreateEvents() + { + + // move this routine to a filter + // in case of repeated piggy banks and/or other problems. + PiggyBank::created( + function (PiggyBank $piggyBank) { + $repetition = new PiggyBankRepetition; + $repetition->piggyBank()->associate($piggyBank); + $repetition->startdate = is_null($piggyBank->startdate) ? null : $piggyBank->startdate; + $repetition->targetdate = is_null($piggyBank->targetdate) ? null : $piggyBank->targetdate; + $repetition->currentamount = 0; + $repetition->save(); + } + ); + } + /** * */ @@ -120,24 +141,4 @@ class EventServiceProvider extends ServiceProvider } - /** - * - */ - protected function registerCreateEvents() - { - - // move this routine to a filter - // in case of repeated piggy banks and/or other problems. - PiggyBank::created( - function (PiggyBank $piggyBank) { - $repetition = new PiggyBankRepetition; - $repetition->piggyBank()->associate($piggyBank); - $repetition->startdate = is_null($piggyBank->startdate) ? null : $piggyBank->startdate; - $repetition->targetdate = is_null($piggyBank->targetdate) ? null : $piggyBank->targetdate; - $repetition->currentamount = 0; - $repetition->save(); - } - ); - } - } diff --git a/app/Providers/ExportJobServiceProvider.php b/app/Providers/ExportJobServiceProvider.php new file mode 100644 index 0000000000..b63d2b8819 --- /dev/null +++ b/app/Providers/ExportJobServiceProvider.php @@ -0,0 +1,50 @@ +app->bind( + 'FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\ExportJob\ExportJobRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\ExportJob\ExportJobRepository', $arguments); + } + ); + } + + /** + * Register the application services. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 13712858c3..1318dafce1 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -1,4 +1,5 @@ app->bind('FireflyIII\Repositories\Account\AccountRepositoryInterface', 'FireflyIII\Repositories\Account\AccountRepository'); - $this->app->bind('FireflyIII\Repositories\Budget\BudgetRepositoryInterface', 'FireflyIII\Repositories\Budget\BudgetRepository'); - $this->app->bind('FireflyIII\Repositories\Category\CategoryRepositoryInterface', 'FireflyIII\Repositories\Category\CategoryRepository'); $this->app->bind('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface', 'FireflyIII\Repositories\Category\SingleCategoryRepository'); - $this->app->bind('FireflyIII\Repositories\Journal\JournalRepositoryInterface', 'FireflyIII\Repositories\Journal\JournalRepository'); - $this->app->bind('FireflyIII\Repositories\Bill\BillRepositoryInterface', 'FireflyIII\Repositories\Bill\BillRepository'); - $this->app->bind('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface', 'FireflyIII\Repositories\PiggyBank\PiggyBankRepository'); $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository'); - $this->app->bind('FireflyIII\Repositories\Tag\TagRepositoryInterface', 'FireflyIII\Repositories\Tag\TagRepository'); - $this->app->bind('FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface', 'FireflyIII\Repositories\Attachment\AttachmentRepository'); - $this->app->bind('FireflyIII\Repositories\Rule\RuleRepositoryInterface', 'FireflyIII\Repositories\Rule\RuleRepository'); - $this->app->bind('FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface', 'FireflyIII\Repositories\RuleGroup\RuleGroupRepository'); $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search'); + $this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository'); // CSV import $this->app->bind('FireflyIII\Helpers\Csv\WizardInterface', 'FireflyIII\Helpers\Csv\Wizard'); @@ -126,7 +118,6 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind('FireflyIII\Helpers\Report\BalanceReportHelperInterface', 'FireflyIII\Helpers\Report\BalanceReportHelper'); $this->app->bind('FireflyIII\Helpers\Report\BudgetReportHelperInterface', 'FireflyIII\Helpers\Report\BudgetReportHelper'); - } } diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php new file mode 100644 index 0000000000..1bff963407 --- /dev/null +++ b/app/Providers/JournalServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Journal\JournalRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Journal\JournalRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Journal\JournalRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/PiggyBankServiceProvider.php b/app/Providers/PiggyBankServiceProvider.php new file mode 100644 index 0000000000..2586776891 --- /dev/null +++ b/app/Providers/PiggyBankServiceProvider.php @@ -0,0 +1,50 @@ +app->bind( + 'FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\PiggyBank\PiggyBankRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\PiggyBank\PiggyBankRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index db8667315f..9a500f25e3 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -1,4 +1,5 @@ group( - ['namespace' => $this->namespace], function ($router) { + ['namespace' => $this->namespace], function (Router $router) { /** @noinspection PhpIncludeInspection */ require app_path('Http/routes.php'); } diff --git a/app/Providers/RuleGroupServiceProvider.php b/app/Providers/RuleGroupServiceProvider.php new file mode 100644 index 0000000000..82a9566303 --- /dev/null +++ b/app/Providers/RuleGroupServiceProvider.php @@ -0,0 +1,50 @@ +app->bind( + 'FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\RuleGroup\RuleGroupRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\RuleGroup\RuleGroupRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/RuleServiceProvider.php b/app/Providers/RuleServiceProvider.php new file mode 100644 index 0000000000..c3480920f1 --- /dev/null +++ b/app/Providers/RuleServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Rule\RuleRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Rule\RuleRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Rule\RuleRepository', $arguments); + } + ); + } +} diff --git a/app/Providers/TagServiceProvider.php b/app/Providers/TagServiceProvider.php new file mode 100644 index 0000000000..227b27e73c --- /dev/null +++ b/app/Providers/TagServiceProvider.php @@ -0,0 +1,49 @@ +app->bind( + 'FireflyIII\Repositories\Tag\TagRepositoryInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && Auth::check()) { + return app('FireflyIII\Repositories\Tag\TagRepository', [Auth::user()]); + } else { + if (!isset($arguments[0]) && !Auth::check()) { + throw new FireflyException('There is no user present.'); + } + } + + return app('FireflyIII\Repositories\Tag\TagRepository', $arguments); + } + ); + } +} diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 628aca2d15..40a40d2c6d 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -1,9 +1,11 @@ user = $user; + } /** * @param Attachment $attachment * * @return bool */ - public function destroy(Attachment $attachment) + public function destroy(Attachment $attachment): bool { /** @var \FireflyIII\Helpers\Attachments\AttachmentHelperInterface $helper */ $helper = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface'); @@ -25,6 +40,16 @@ class AttachmentRepository implements AttachmentRepositoryInterface $file = $helper->getAttachmentLocation($attachment); unlink($file); $attachment->delete(); + + return true; + } + + /** + * @return Collection + */ + public function get(): Collection + { + return $this->user->attachments()->get(); } /** @@ -33,7 +58,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface * * @return Attachment */ - public function update(Attachment $attachment, array $data) + public function update(Attachment $attachment, array $data): Attachment { $attachment->title = $data['title']; diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index 44ae352b15..2c25be8baa 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -1,8 +1,10 @@ user = $user; + } + /** * @param Bill $bill * - * @return boolean|null + * @return boolean */ - public function destroy(Bill $bill) + public function destroy(Bill $bill): bool { - return $bill->delete(); + $bill->delete(); + + return true; } /** * @return Collection */ - public function getActiveBills() + public function getActiveBills(): Collection { /** @var Collection $set */ - $set = Auth::user()->bills() - ->where('active', 1) - ->get( - [ - 'bills.*', - DB::Raw('(`bills`.`amount_min` + `bills`.`amount_max` / 2) as `expectedAmount`'), - ] - )->sortBy('name'); + $set = $this->user->bills() + ->where('active', 1) + ->get( + [ + 'bills.*', + DB::raw('(`bills`.`amount_min` + `bills`.`amount_max` / 2) as `expectedAmount`'), + ] + )->sortBy('name'); return $set; } @@ -62,26 +77,27 @@ class BillRepository implements BillRepositoryInterface * * @return Collection */ - public function getAllJournalsInRange(Collection $bills, Carbon $start, Carbon $end) + public function getAllJournalsInRange(Collection $bills, Carbon $start, Carbon $end): Collection { $ids = $bills->pluck('id')->toArray(); - $set = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->whereIn('bill_id', $ids) - ->before($end) - ->after($start) - ->groupBy('transaction_journals.bill_id') - ->get( - [ - 'transaction_journals.bill_id', - DB::Raw('SUM(`transactions`.`amount`) as `journalAmount`'), - ] - ); + $set = $this->user->transactionjournals() + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->whereIn('bill_id', $ids) + ->before($end) + ->after($start) + ->groupBy('transaction_journals.bill_id') + ->get( + [ + 'transaction_journals.bill_id', + 'transaction_journals.id', + DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), + ] + ); return $set; } @@ -89,10 +105,10 @@ class BillRepository implements BillRepositoryInterface /** * @return Collection */ - public function getBills() + public function getBills(): Collection { /** @var Collection $set */ - $set = Auth::user()->bills()->orderBy('name', 'ASC')->get(); + $set = $this->user->bills()->orderBy('name', 'ASC')->get(); $set = $set->sortBy( function (Bill $bill) { @@ -111,24 +127,24 @@ class BillRepository implements BillRepositoryInterface * * @return Collection */ - public function getBillsForAccounts(Collection $accounts) + public function getBillsForAccounts(Collection $accounts): Collection { $ids = $accounts->pluck('id')->toArray(); - $set = Auth::user()->bills() - ->leftJoin( - 'transaction_journals', function (JoinClause $join) { - $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); - } - ) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); - } - ) - ->whereIn('transactions.account_id', $ids) - ->whereNull('transaction_journals.deleted_at') - ->groupBy('bills.id') - ->get(['bills.*']); + $set = $this->user->bills() + ->leftJoin( + 'transaction_journals', function (JoinClause $join) { + $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); + } + ) + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->whereIn('transactions.account_id', $ids) + ->whereNull('transaction_journals.deleted_at') + ->groupBy('bills.id') + ->get(['bills.*']); $set = $set->sortBy( function (Bill $bill) { @@ -151,7 +167,7 @@ class BillRepository implements BillRepositoryInterface * * @return string */ - public function getBillsPaidInRange(Carbon $start, Carbon $end) + public function getBillsPaidInRange(Carbon $start, Carbon $end): string { $amount = '0'; $bills = $this->getActiveBills(); @@ -161,16 +177,17 @@ class BillRepository implements BillRepositoryInterface $ranges = $this->getRanges($bill, $start, $end); foreach ($ranges as $range) { - $paid = $bill->transactionjournals() - ->before($range['end']) - ->after($range['start']) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->first([DB::Raw('SUM(`transactions`.`amount`) as `sum_amount`')]); - $amount = bcadd($amount, $paid->sum_amount); + $paid = $bill->transactionjournals() + ->before($range['end']) + ->after($range['start']) + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->first([DB::raw('SUM(`transactions`.`amount`) as `sum_amount`')]); + $sumAmount = $paid->sum_amount ?? '0'; + $amount = bcadd($amount, $sumAmount); } } @@ -185,7 +202,7 @@ class BillRepository implements BillRepositoryInterface * * @return string */ - public function getBillsUnpaidInRange(Carbon $start, Carbon $end) + public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string { $amount = '0'; $bills = $this->getActiveBills(); @@ -195,16 +212,17 @@ class BillRepository implements BillRepositoryInterface $ranges = $this->getRanges($bill, $start, $end); $paidBill = '0'; foreach ($ranges as $range) { - $paid = $bill->transactionjournals() - ->before($range['end']) - ->after($range['start']) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); - } - ) - ->first([DB::Raw('SUM(`transactions`.`amount`) as `sum_amount`')]); - $paidBill = bcadd($paid->sum_amount, $paidBill); + $paid = $bill->transactionjournals() + ->before($range['end']) + ->after($range['start']) + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); + } + ) + ->first([DB::raw('SUM(`transactions`.`amount`) as `sum_amount`')]); + $sumAmount = $paid->sum_amount ?? '0'; + $paidBill = bcadd($sumAmount, $paidBill); } if ($paidBill == 0) { $amount = bcadd($amount, $bill->expectedAmount); @@ -223,7 +241,7 @@ class BillRepository implements BillRepositoryInterface * * @return string */ - public function getCreditCardBill(Carbon $start, Carbon $end) + public function getCreditCardBill(Carbon $start, Carbon $end): string { /** @var AccountRepositoryInterface $accountRepository */ @@ -242,7 +260,7 @@ class BillRepository implements BillRepositoryInterface ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') ->where('transactions.account_id', $creditCard->id) ->where('transactions.amount', '>', 0)// this makes the filter unnecessary. - ->where('transaction_journals.user_id', Auth::user()->id) + ->where('transaction_journals.user_id', $this->user->id) ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) ->where('transaction_types.type', TransactionType::TRANSFER); @@ -251,7 +269,7 @@ class BillRepository implements BillRepositoryInterface 'transactions', function (JoinClause $join) { $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); } - )->first([DB::Raw('SUM(`transactions`.`amount`) as `sum_amount`')]); + )->first([DB::raw('SUM(`transactions`.`amount`) as `sum_amount`')]); $amount = bcadd($amount, $set->sum_amount); } else { @@ -271,19 +289,14 @@ class BillRepository implements BillRepositoryInterface * * @return Collection */ - public function getJournals(Bill $bill) + public function getJournals(Bill $bill): Collection { $set = $bill->transactionjournals() - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('amount', '<', 0); - } - ) + ->expanded() ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC') - ->get(['transaction_journals.*', 'transactions.amount as journalAmount']); + ->get(TransactionJournal::QUERYFIELDS); return $set; } @@ -299,7 +312,7 @@ class BillRepository implements BillRepositoryInterface * * @return Collection */ - public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end) + public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end): Collection { return $bill->transactionjournals()->before($end)->after($start)->get(); } @@ -309,7 +322,7 @@ class BillRepository implements BillRepositoryInterface * * @return Collection */ - public function getPossiblyRelatedJournals(Bill $bill) + public function getPossiblyRelatedJournals(Bill $bill): Collection { $set = new Collection( DB::table('transactions')->where('amount', '>', 0)->where('amount', '>=', $bill->amount_min)->where('amount', '<=', $bill->amount_max) @@ -319,7 +332,7 @@ class BillRepository implements BillRepositoryInterface $journals = new Collection; if (count($ids) > 0) { - $journals = Auth::user()->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->whereIn('transaction_journals.id', $ids)->get( + $journals = $this->user->transactionjournals()->transactionTypes([TransactionType::WITHDRAWAL])->whereIn('transaction_journals.id', $ids)->get( ['transaction_journals.*'] ); } @@ -336,9 +349,9 @@ class BillRepository implements BillRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return mixed + * @return array */ - public function getRanges(Bill $bill, Carbon $start, Carbon $end) + public function getRanges(Bill $bill, Carbon $start, Carbon $end): array { $startOfBill = $bill->date; $startOfBill = Navigation::startOfPeriod($startOfBill, $bill->repeat_freq); @@ -373,16 +386,16 @@ class BillRepository implements BillRepositoryInterface /** * @param Bill $bill * - * @return Carbon|null + * @return \Carbon\Carbon */ - public function lastFoundMatch(Bill $bill) + public function lastFoundMatch(Bill $bill): Carbon { $last = $bill->transactionjournals()->orderBy('date', 'DESC')->first(); if ($last) { return $last->date; } - return null; + return Carbon::now()->addDays(2); // in the future! } /** @@ -390,10 +403,11 @@ class BillRepository implements BillRepositoryInterface * * @return \Carbon\Carbon */ - public function nextExpectedMatch(Bill $bill) + public function nextExpectedMatch(Bill $bill): Carbon { - $finalDate = null; + $finalDate = Carbon::now(); + $finalDate->year = 1900; if ($bill->active == 0) { return $finalDate; } @@ -437,11 +451,10 @@ class BillRepository implements BillRepositoryInterface * @param Bill $bill * @param TransactionJournal $journal * - * @return boolean|null + * @return bool */ - public function scan(Bill $bill, TransactionJournal $journal) + public function scan(Bill $bill, TransactionJournal $journal): bool { - /* * Can only support withdrawals. */ @@ -450,10 +463,9 @@ class BillRepository implements BillRepositoryInterface } $matches = explode(',', $bill->match); - $description = strtolower($journal->description) . ' ' . strtolower($journal->destination_account->name); + $description = strtolower($journal->description) . ' ' . strtolower(TransactionJournal::destinationAccount($journal)->name); $wordMatch = $this->doWordMatch($matches, $description); - $amountMatch = $this->doAmountMatch($journal->amount_positive, $bill->amount_min, $bill->amount_max); - Log::debug('Journal #' . $journal->id . ' has description "' . $description . '"'); + $amountMatch = $this->doAmountMatch(TransactionJournal::amountPositive($journal), $bill->amount_min, $bill->amount_max); /* @@ -464,8 +476,6 @@ class BillRepository implements BillRepositoryInterface $journal->save(); return true; - } else { - Log::debug('Wordmatch: ' . (($wordMatch) ? 'true' : 'false') . ' AmountMatch: ' . (($amountMatch) ? 'true' : 'false')); } if ($bill->id == $journal->bill_id) { // if no match, but bill used to match, remove it: @@ -484,7 +494,7 @@ class BillRepository implements BillRepositoryInterface * * @return Bill */ - public function store(array $data) + public function store(array $data): Bill { @@ -513,7 +523,7 @@ class BillRepository implements BillRepositoryInterface * * @return Bill */ - public function update(Bill $bill, array $data) + public function update(Bill $bill, array $data): Bill { @@ -538,7 +548,7 @@ class BillRepository implements BillRepositoryInterface * * @return bool */ - protected function doAmountMatch($amount, $min, $max) + protected function doAmountMatch($amount, $min, $max): bool { if ($amount >= $min && $amount <= $max) { return true; @@ -553,7 +563,7 @@ class BillRepository implements BillRepositoryInterface * * @return bool */ - protected function doWordMatch(array $matches, $description) + protected function doWordMatch(array $matches, $description): bool { $wordMatch = false; $count = 0; diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 0cefc960c8..10b3156dd1 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } /** * @param Budget $budget @@ -81,7 +95,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn public function getActiveBudgets() { /** @var Collection $set */ - $set = Auth::user()->budgets()->where('active', 1)->get(); + $set = $this->user->budgets()->where('active', 1)->get(); $set = $set->sortBy( function (Budget $budget) { @@ -106,7 +120,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) - ->where('budgets.user_id', Auth::user()->id) + ->where('budgets.user_id', $this->user->id) ->get(['limit_repetitions.*', 'budget_limits.budget_id']); } @@ -123,21 +137,21 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn { $budgetIds = $budgets->pluck('id')->toArray(); - $set = Auth::user()->budgets() - ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id') - ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')) - ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d')) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->whereIn('budgets.id', $budgetIds) - ->get( - [ - 'budgets.*', - DB::Raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'), - DB::Raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'), - ] - ); + $set = $this->user->budgets() + ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id') + ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')) + ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d')) + ->groupBy('budgets.id') + ->groupBy('dateFormatted') + ->whereIn('budgets.id', $budgetIds) + ->get( + [ + 'budgets.*', + DB::raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'), + DB::raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'), + ] + ); return $set; } @@ -148,7 +162,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn public function getBudgets() { /** @var Collection $set */ - $set = Auth::user()->budgets()->get(); + $set = $this->user->budgets()->get(); $set = $set->sortBy( function (Budget $budget) { @@ -174,26 +188,26 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn $ids = $accounts->pluck('id')->toArray(); /** @var Collection $set */ - $set = Auth::user()->budgets() - ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereIn('transactions.account_id', $ids) - ->get( - [ - 'budgets.*', - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), - DB::Raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - ] - ); + $set = $this->user->budgets() + ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->groupBy('budgets.id') + ->groupBy('dateFormatted') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->whereIn('transactions.account_id', $ids) + ->get( + [ + 'budgets.*', + DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), + DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + ] + ); $set = $set->sortBy( function (Budget $budget) { @@ -236,27 +250,27 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn $budgetIds = $budgets->pluck('id')->toArray(); /** @var Collection $set */ - $set = Auth::user()->budgets() - ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereIn('transactions.account_id', $ids) - ->whereIn('budgets.id', $budgetIds) - ->get( - [ - 'budgets.*', - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), - DB::Raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - ] - ); + $set = $this->user->budgets() + ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->groupBy('budgets.id') + ->groupBy('dateFormatted') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->whereIn('transactions.account_id', $ids) + ->whereIn('budgets.id', $budgetIds) + ->get( + [ + 'budgets.*', + DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), + DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + ] + ); $set = $set->sortBy( function (Budget $budget) { @@ -292,27 +306,27 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end) { /** @var Collection $set */ - $set = Auth::user() - ->budgets() - ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') - ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->where( - function (Builder $query) use ($start, $end) { - $query->where( - function (Builder $query) use ($start, $end) { - $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); - $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); - } - ); - $query->orWhere( - function (Builder $query) { - $query->whereNull('limit_repetitions.startdate'); - $query->whereNull('limit_repetitions.enddate'); - } - ); - } - ) - ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); + $set = $this->user + ->budgets() + ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') + ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->where( + function (Builder $query) use ($start, $end) { + $query->where( + function (Builder $query) use ($start, $end) { + $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); + $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); + } + ); + $query->orWhere( + function (Builder $query) { + $query->whereNull('limit_repetitions.startdate'); + $query->whereNull('limit_repetitions.enddate'); + } + ); + } + ) + ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); $set = $set->sortBy( function (Budget $budget) { @@ -353,51 +367,18 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn */ public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end) { - $set = Auth::user()->budgets() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') - ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('budgets.id', $budget->id) - ->where('transactions.amount', '<', 0) - ->groupBy('transaction_journals.date') - ->orderBy('transaction_journals.date') - ->get(['transaction_journals.date', DB::Raw('SUM(`transactions`.`amount`) as `dailyAmount`')]); - - return $set; - } - - /** - * Returns the expenses for this budget grouped per month, with the date - * in "dateFormatted" (a string, not a Carbon) and the amount in "dailyAmount". - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getExpensesPerMonth(Budget $budget, Carbon $start, Carbon $end) - { - $set = Auth::user()->budgets() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') - ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereNull('transaction_journals.deleted_at') - ->where('budgets.id', $budget->id) - ->where('transactions.amount', '<', 0) - ->groupBy('dateFormatted') - ->orderBy('transaction_journals.date') - ->get( - [ - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), - DB::Raw('SUM(`transactions`.`amount`) as `monthlyAmount`'), - ] - ); + $set = $this->user->budgets() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') + ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('budgets.id', $budget->id) + ->where('transactions.amount', '<', 0) + ->groupBy('transaction_journals.date') + ->orderBy('transaction_journals.date') + ->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `dailyAmount`')]); return $set; } @@ -423,7 +404,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn public function getInactiveBudgets() { /** @var Collection $set */ - $set = Auth::user()->budgets()->where('active', 0)->get(); + $set = $this->user->budgets()->where('active', 0)->get(); $set = $set->sortBy( function (Budget $budget) { @@ -443,14 +424,15 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn * * @return LengthAwarePaginator */ - public function getJournals(Budget $budget, LimitRepetition $repetition = null, $take = 50) + public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50) { $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; - $setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset) + $setQuery = $budget->transactionjournals()->expanded() + ->take($take)->offset($offset) ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC'); - $countQuery = $budget->transactionJournals(); + $countQuery = $budget->transactionjournals(); if (!is_null($repetition->id)) { @@ -459,7 +441,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn } - $set = $setQuery->get(['transaction_journals.*']); + $set = $setQuery->get(TransactionJournal::QUERYFIELDS); $count = $countQuery->count(); @@ -476,49 +458,55 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn */ public function getWithoutBudget(Carbon $start, Carbon $end) { - return Auth::user() - ->transactionjournals() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('budget_transaction_journal.id') - ->before($end) - ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->get(['transaction_journals.*']); + return $this->user + ->transactionjournals() + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('budget_transaction_journal.id') + ->before($end) + ->after($start) + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->get(['transaction_journals.*']); } /** - * @param Carbon $start - * @param Carbon $end + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * - * @return double + * @return string */ - public function getWithoutBudgetSum(Carbon $start, Carbon $end) + public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string { - $entry = Auth::user() - ->transactionjournals() - ->whereNotIn( - 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { - $query - ->select('transaction_journals.id') - ->from('transaction_journals') - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) - ->whereNotNull('budget_transaction_journal.budget_id'); - } - ) - ->after($start) - ->before($end) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->first([DB::Raw('SUM(`transactions`.`amount`) as `journalAmount`')]); + $ids = $accounts->pluck('id')->toArray(); + $entry = $this->user + ->transactionjournals() + ->whereNotIn( + 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { + $query + ->select('transaction_journals.id') + ->from('transaction_journals') + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) + ->whereNotNull('budget_transaction_journal.budget_id'); + } + ) + ->after($start) + ->before($end) + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->whereIn('transactions.account_id', $ids) + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); + if (is_null($entry->journalAmount)) { + return '0'; + } return $entry->journalAmount; } @@ -545,20 +533,20 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn { $ids = $accounts->pluck('id')->toArray(); /** @var Collection $query */ - $query = Auth::user()->transactionJournals() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->whereIn('transactions.account_id', $ids) - ->where('transactions.amount', '<', 0) - ->before($end) - ->after($start) - ->groupBy('budget_id') - ->groupBy('dateFormatted') - ->get( - ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', - DB::Raw('SUM(`transactions`.`amount`) AS `sum`')] - ); + $query = $this->user->transactionJournals() + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + ->whereIn('transactions.account_id', $ids) + ->where('transactions.amount', '<', 0) + ->before($end) + ->after($start) + ->groupBy('budget_id') + ->groupBy('dateFormatted') + ->get( + ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', + DB::raw('SUM(`transactions`.`amount`) AS `sum`')] + ); $return = []; foreach ($query->toArray() as $entry) { @@ -586,37 +574,37 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn { $accountIds = $accounts->pluck('id')->toArray(); $budgetIds = $budgets->pluck('id')->toArray(); - $set = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions AS t_from', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_to', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - } - ) - ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->whereIn('t_from.account_id', $accountIds) - ->whereNotIn('t_to.account_id', $accountIds) - ->where( - function (Builder $q) use ($budgetIds) { - $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); - $q->orWhereNull('budget_transaction_journal.budget_id'); - } - ) - ->after($start) - ->before($end) - ->groupBy('t_from.account_id') - ->groupBy('budget_transaction_journal.budget_id') - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]) - ->get( - [ - 't_from.account_id', 'budget_transaction_journal.budget_id', - DB::Raw('SUM(`t_from`.`amount`) AS `spent`'), - ] - ); + $set = $this->user->transactionjournals() + ->leftJoin( + 'transactions AS t_from', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); + } + ) + ->leftJoin( + 'transactions AS t_to', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); + } + ) + ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + ->whereIn('t_from.account_id', $accountIds) + ->whereNotIn('t_to.account_id', $accountIds) + ->where( + function (Builder $q) use ($budgetIds) { + $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); + $q->orWhereNull('budget_transaction_journal.budget_id'); + } + ) + ->after($start) + ->before($end) + ->groupBy('t_from.account_id') + ->groupBy('budget_transaction_journal.budget_id') + ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. + ->get( + [ + 't_from.account_id', 'budget_transaction_journal.budget_id', + DB::raw('SUM(`t_from`.`amount`) AS `spent`'), + ] + ); return $set; @@ -636,16 +624,16 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn * * @return array */ - public function spentPerDay(Budget $budget, Carbon $start, Carbon $end) + public function spentPerDay(Budget $budget, Carbon $start, Carbon $end): array { /** @var Collection $query */ - $query = $budget->transactionJournals() + $query = $budget->transactionjournals() ->transactionTypes([TransactionType::WITHDRAWAL]) ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.amount', '<', 0) ->before($end) ->after($start) - ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); $return = []; foreach ($query->toArray() as $entry) { @@ -692,11 +680,11 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn /** * @param Budget $budget * @param Carbon $date - * @param $amount + * @param int $amount * * @return BudgetLimit */ - public function updateLimitAmount(Budget $budget, Carbon $date, $amount) + public function updateLimitAmount(Budget $budget, Carbon $date, int $amount) { // there should be a budget limit for this startdate: /** @var BudgetLimit $limit */ diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 0aec8c451a..f14526831d 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } /** * Returns a collection of Categories appended with the amount of money that has been earned @@ -33,36 +46,36 @@ class CategoryRepository implements CategoryRepositoryInterface public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end) { - $collection = Auth::user()->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions AS t_src', function (JoinClause $join) { - $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_dest', function (JoinClause $join) { - $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - } - ) - ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned) - ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts - ->whereIn( + $collection = $this->user->categories() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions AS t_src', function (JoinClause $join) { + $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); + } + ) + ->leftJoin( + 'transactions AS t_dest', function (JoinClause $join) { + $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); + } + ) + ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned) + ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts + ->whereIn( 'transaction_types.type', [TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] ) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('dateFormatted') - ->get( - [ - 'categories.*', - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - DB::Raw('SUM(`t_dest`.`amount`) AS `earned`'), - ] - ); + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->groupBy('categories.id') + ->groupBy('dateFormatted') + ->get( + [ + 'categories.*', + DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), + DB::raw('SUM(`t_dest`.`amount`) AS `earned`'), + ] + ); return $collection; @@ -77,7 +90,7 @@ class CategoryRepository implements CategoryRepositoryInterface public function listCategories() { /** @var Collection $set */ - $set = Auth::user()->categories()->orderBy('name', 'ASC')->get(); + $set = $this->user->categories()->orderBy('name', 'ASC')->get(); $set = $set->sortBy( function (Category $category) { return strtolower($category->name); @@ -104,27 +117,27 @@ class CategoryRepository implements CategoryRepositoryInterface public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end) { - $set = Auth::user()->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL]) - ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()) - ->whereIn('categories.id', $categories->pluck('id')->toArray()) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('transaction_types.type') - ->groupBy('dateFormatted') - ->get( - [ - 'categories.*', - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'), - 'transaction_types.type', - DB::Raw('SUM(`amount`) as `sum`'), - ] - ); + $set = $this->user->categories() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL]) + ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()) + ->whereIn('categories.id', $categories->pluck('id')->toArray()) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->groupBy('categories.id') + ->groupBy('transaction_types.type') + ->groupBy('dateFormatted') + ->get( + [ + 'categories.*', + DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'), + 'transaction_types.type', + DB::raw('SUM(`amount`) as `sum`'), + ] + ); return $set; @@ -141,16 +154,16 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function listNoCategory(Carbon $start, Carbon $end) { - return Auth::user() - ->transactionjournals() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('category_transaction_journal.id') - ->before($end) - ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->get(['transaction_journals.*']); + return $this->user + ->transactionjournals() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start) + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->get(['transaction_journals.*']); } /** @@ -167,27 +180,27 @@ class CategoryRepository implements CategoryRepositoryInterface public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end) { $accountIds = $accounts->pluck('id')->toArray(); - $query = Auth::user()->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions AS t_src', function (JoinClause $join) { - $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_dest', function (JoinClause $join) { - $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - } - ) - ->whereIn( - 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] - )// spent on these things. - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('dateFormatted'); + $query = $this->user->categories() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions AS t_src', function (JoinClause $join) { + $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); + } + ) + ->leftJoin( + 'transactions AS t_dest', function (JoinClause $join) { + $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); + } + ) + ->whereIn( + 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] + )// spent on these things. + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->groupBy('categories.id') + ->groupBy('dateFormatted'); if (count($accountIds) > 0) { $query->whereIn('t_src.account_id', $accountIds)// from these accounts (spent) @@ -197,8 +210,8 @@ class CategoryRepository implements CategoryRepositoryInterface $collection = $query->get( [ 'categories.*', - DB::Raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - DB::Raw('SUM(`t_src`.`amount`) AS `spent`'), + DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), + DB::raw('SUM(`t_src`.`amount`) AS `spent`'), ] ); @@ -256,14 +269,14 @@ class CategoryRepository implements CategoryRepositoryInterface } // is withdrawal or transfer AND account_from is in the list of $accounts - $query = Auth::user() - ->transactionjournals() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('category_transaction_journal.id') - ->before($end) - ->after($start) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->transactionTypes($types); + $query = $this->user + ->transactionjournals() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start) + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->transactionTypes($types); if (count($accountIds) > 0) { $query->whereIn('transactions.account_id', $accountIds); } @@ -271,7 +284,7 @@ class CategoryRepository implements CategoryRepositoryInterface $single = $query->first( [ - DB::Raw('SUM(`transactions`.`amount`) as `sum`'), + DB::raw('SUM(`transactions`.`amount`) as `sum`'), ] ); if (!is_null($single)) { diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 7de84fe660..ec89ccec5e 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -1,4 +1,5 @@ transactionJournals() + $query = $category->transactionjournals() ->transactionTypes([TransactionType::DEPOSIT]) ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.amount', '>', 0) ->before($end) ->after($start) - ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); $return = []; foreach ($query->toArray() as $entry) { @@ -114,13 +114,11 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate { $offset = $page > 0 ? $page * 50 : 0; - return $category->transactionJournals()->withRelevantData()->take(50)->offset($offset) + return $category->transactionjournals()->expanded()->take(50)->offset($offset) ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC') - ->get( - ['transaction_journals.*'] - ); + ->get(TransactionJournal::QUERYFIELDS); } @@ -136,16 +134,16 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate { $offset = $page > 0 ? $page * 50 : 0; - return $category->transactionJournals() + return $category->transactionjournals() ->after($start) ->before($end) - ->withRelevantData()->take(50)->offset($offset) + ->expanded() + ->take(50) + ->offset($offset) ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC') - ->get( - ['transaction_journals.*'] - ); + ->get(TransactionJournal::QUERYFIELDS); } @@ -185,13 +183,13 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate public function spentPerDay(Category $category, Carbon $start, Carbon $end) { /** @var Collection $query */ - $query = $category->transactionJournals() + $query = $category->transactionjournals() ->transactionTypes([TransactionType::WITHDRAWAL]) ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.amount', '<', 0) ->before($end) ->after($start) - ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + ->groupBy('date')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); $return = []; foreach ($query->toArray() as $entry) { diff --git a/app/Repositories/Category/SingleCategoryRepositoryInterface.php b/app/Repositories/Category/SingleCategoryRepositoryInterface.php index cae94328d4..b954a0684a 100644 --- a/app/Repositories/Category/SingleCategoryRepositoryInterface.php +++ b/app/Repositories/Category/SingleCategoryRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } + + /** + * @return bool + */ + public function cleanup() + { + $dayAgo = Carbon::create()->subDay(); + $set = ExportJob::where('created_at', '<', $dayAgo->format('Y-m-d H:i:s')) + ->whereIn('status', ['never_started', 'export_status_finished', 'export_downloaded']) + ->get(); + + // loop set: + /** @var ExportJob $entry */ + foreach ($set as $entry) { + $key = $entry->key; + $len = strlen($key); + $files = scandir(storage_path('export')); + /** @var string $file */ + foreach ($files as $file) { + if (substr($file, 0, $len) === $key) { + unlink(storage_path('export') . DIRECTORY_SEPARATOR . $file); + } + } + $entry->delete(); + } + + return true; + } + + /** + * @return ExportJob + */ + public function create() + { + $exportJob = new ExportJob; + $exportJob->user()->associate($this->user); + /* + * In theory this random string could give db error. + */ + $exportJob->key = Str::random(12); + $exportJob->status = 'export_status_never_started'; + $exportJob->save(); + + return $exportJob; + } + + /** + * @param string $key + * + * @return ExportJob|null + */ + public function findByKey(string $key) + { + return $this->user->exportJobs()->where('key', $key)->first(); + } + +} diff --git a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php new file mode 100644 index 0000000000..1eb1352f4f --- /dev/null +++ b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php @@ -0,0 +1,39 @@ +accounts = $accounts; + $this->user = $user; + $this->start = $start; + $this->end = $end; + } + + /** + * @return Collection + */ + public function collect() + { + // get all the journals: + $ids = $this->accounts->pluck('id')->toArray(); + + return $this->user->transactionjournals() + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereIn('transactions.account_id', $ids) + ->before($this->end) + ->after($this->start) + ->orderBy('transaction_journals.date') + ->get(['transaction_journals.*']); + } + +} diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index ba3b4200e3..956a4645f3 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -1,10 +1,11 @@ user = $user; + } /** * @param TransactionJournal $journal @@ -44,7 +58,7 @@ class JournalRepository implements JournalRepositoryInterface */ public function first() { - $entry = Auth::user()->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); + $entry = $this->user->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); return $entry; } @@ -64,15 +78,36 @@ class JournalRepository implements JournalRepositoryInterface ->where('transaction_journals.order', '>=', $journal->order) ->where('transaction_journals.id', '!=', $journal->id) ->get(['transactions.*']); - $sum = 0; + $sum = '0'; foreach ($set as $entry) { - $sum += $entry->amount; + $sum = bcadd($entry->amount, $sum); } return $sum; } + /** + * @param array $types + * @param int $offset + * @param int $count + * + * @return Collection + */ + public function getCollectionOfTypes(array $types, int $offset, int $count) + { + $set = $this->user->transactionJournals() + ->expanded() + ->transactionTypes($types) + ->take($count)->offset($offset) + ->orderBy('date', 'DESC') + ->orderBy('order', 'ASC') + ->orderBy('id', 'DESC') + ->get(TransactionJournal::QUERYFIELDS); + + return $set; + } + /** * @param TransactionType $dbType * @@ -80,7 +115,7 @@ class JournalRepository implements JournalRepositoryInterface */ public function getJournalsOfType(TransactionType $dbType) { - return Auth::user()->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get(); + return $this->user->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get(); } /** @@ -88,29 +123,35 @@ class JournalRepository implements JournalRepositoryInterface * @param int $offset * @param int $page * + * @param int $pagesize + * * @return LengthAwarePaginator */ - public function getJournalsOfTypes(array $types, $offset, $page) + public function getJournalsOfTypes(array $types, int $offset, int $page, int $pagesize = 50) { - $set = Auth::user()->transactionJournals()->transactionTypes($types)->withRelevantData()->take(50)->offset($offset) - ->orderBy('date', 'DESC') - ->orderBy('order', 'ASC') - ->orderBy('id', 'DESC') - ->get( - ['transaction_journals.*'] - ); - $count = Auth::user()->transactionJournals()->transactionTypes($types)->count(); - $journals = new LengthAwarePaginator($set, $count, 50, $page); + $set = $this->user + ->transactionJournals() + ->expanded() + ->transactionTypes($types) + ->take($pagesize) + ->offset($offset) + ->orderBy('date', 'DESC') + ->orderBy('order', 'ASC') + ->orderBy('id', 'DESC') + ->get(TransactionJournal::QUERYFIELDS); + + $count = $this->user->transactionJournals()->transactionTypes($types)->count(); + $journals = new LengthAwarePaginator($set, $count, $pagesize, $page); return $journals; } /** - * @param $type + * @param string $type * * @return TransactionType */ - public function getTransactionType($type) + public function getTransactionType(string $type) { return TransactionType::whereType($type)->first(); } @@ -121,9 +162,9 @@ class JournalRepository implements JournalRepositoryInterface * * @return TransactionJournal */ - public function getWithDate($journalId, Carbon $date) + public function getWithDate(int $journalId, Carbon $date) { - return Auth::user()->transactionjournals()->where('id', $journalId)->where('date', $date->format('Y-m-d 00:00:00'))->first(); + return $this->user->transactionjournals()->where('id', $journalId)->where('date', $date->format('Y-m-d 00:00:00'))->first(); } /** @@ -170,6 +211,9 @@ class JournalRepository implements JournalRepositoryInterface 'description' => $data['description'], 'completed' => 0, 'date' => $data['date'], + 'interest_date' => $data['interest_date'], + 'book_date' => $data['book_date'], + 'process_date' => $data['process_date'], ] ); $journal->save(); @@ -231,6 +275,9 @@ class JournalRepository implements JournalRepositoryInterface $journal->transaction_currency_id = $data['amount_currency_id_amount']; $journal->description = $data['description']; $journal->date = $data['date']; + $journal->interest_date = $data['interest_date']; + $journal->book_date = $data['book_date']; + $journal->process_date = $data['process_date']; // unlink all categories, recreate them: @@ -323,7 +370,7 @@ class JournalRepository implements JournalRepositoryInterface * @param array $data * * @return array - * + * @throws FireflyException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function storeAccounts(TransactionType $type, array $data) @@ -347,14 +394,14 @@ class JournalRepository implements JournalRepositoryInterface if (is_null($toAccount)) { Log::error('"to"-account is null, so we cannot continue!'); - abort(500, '"to"-account is null, so we cannot continue!'); + throw new FireflyException('"to"-account is null, so we cannot continue!'); // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd if (is_null($fromAccount)) { Log::error('"from"-account is null, so we cannot continue!'); - abort(500, '"from"-account is null, so we cannot continue!'); + throw new FireflyException('"from"-account is null, so we cannot continue!'); // @codeCoverageIgnoreStart } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index fb7ccb6cc9..d917190cbc 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } + /** * @param PiggyBank $piggyBank - * @param $amount + * @param string $amount * * @return bool */ - public function createEvent(PiggyBank $piggyBank, $amount) + public function createEvent(PiggyBank $piggyBank, string $amount) { PiggyBankEvent::create(['date' => Carbon::now(), 'amount' => $amount, 'piggy_bank_id' => $piggyBank->id]); @@ -47,7 +61,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function getEventSummarySet(PiggyBank $piggyBank) { - return DB::table('piggy_bank_events')->where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::Raw('SUM(`amount`) AS `sum`')]); + return DB::table('piggy_bank_events')->where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::raw('SUM(`amount`) AS `sum`')]); } /** @@ -65,7 +79,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function getMaxOrder() { - return intval(Auth::user()->piggyBanks()->max('order')); + return intval($this->user->piggyBanks()->max('order')); } /** @@ -74,7 +88,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function getPiggyBanks() { /** @var Collection $set */ - $set = Auth::user()->piggyBanks()->orderBy('order', 'ASC')->get(); + $set = $this->user->piggyBanks()->orderBy('order', 'ASC')->get(); return $set; } @@ -89,7 +103,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface // split query to make it work in sqlite: $set = PiggyBank:: leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.id') - ->where('accounts.user_id', Auth::user()->id)->get(['piggy_banks.*']); + ->where('accounts.user_id', $this->user->id)->get(['piggy_banks.*']); foreach ($set as $e) { $e->order = 0; $e->save(); @@ -107,9 +121,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * * @return void */ - public function setOrder($piggyBankId, $order) + public function setOrder(int $piggyBankId, int $order) { - $piggyBank = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', Auth::user()->id) + $piggyBank = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', $this->user->id) ->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); if ($piggyBank) { $piggyBank->order = $order; @@ -143,7 +157,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface $piggyBank->name = $data['name']; $piggyBank->account_id = intval($data['account_id']); - $piggyBank->targetamount = floatval($data['targetamount']); + $piggyBank->targetamount = round($data['targetamount'], 2); $piggyBank->targetdate = $data['targetdate']; $piggyBank->startdate = $data['startdate']; diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 8d0f81b654..d57f6300c0 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } /** * @return int */ public function count() { - return Auth::user()->rules()->count(); + return $this->user->rules()->count(); } /** @@ -54,7 +68,7 @@ class RuleRepository implements RuleRepositoryInterface */ public function getFirstRuleGroup() { - return Auth::user()->ruleGroups()->first(); + return $this->user->ruleGroups()->first(); } /** @@ -67,6 +81,22 @@ class RuleRepository implements RuleRepositoryInterface return intval($ruleGroup->rules()->max('order')); } + /** + * @param Rule $rule + * + * @return string + * @throws FireflyException + */ + public function getPrimaryTrigger(Rule $rule): string + { + $count = $rule->ruleTriggers()->count(); + if ($count === 0) { + throw new FireflyException('Rules should have more than zero triggers, rule #' . $rule->id . ' has none!'); + } + + return $rule->ruleTriggers()->where('trigger_type', 'user_action')->first()->trigger_value; + } + /** * @param Rule $rule * @@ -187,7 +217,7 @@ class RuleRepository implements RuleRepositoryInterface public function store(array $data) { /** @var RuleGroup $ruleGroup */ - $ruleGroup = Auth::user()->ruleGroups()->find($data['rule_group_id']); + $ruleGroup = $this->user->ruleGroups()->find($data['rule_group_id']); // get max order: $order = $this->getHighestOrderInRuleGroup($ruleGroup); @@ -206,47 +236,10 @@ class RuleRepository implements RuleRepositoryInterface $rule->save(); // start storing triggers: - $order = 1; - $stopProcessing = false; - - $triggerValues = [ - 'action' => 'user_action', - 'value' => $data['trigger'], - 'stopProcessing' => $stopProcessing, - 'order' => $order, - ]; - - $this->storeTrigger($rule, $triggerValues); - foreach ($data['rule-triggers'] as $index => $trigger) { - $value = $data['rule-trigger-values'][$index]; - $stopProcessing = isset($data['rule-trigger-stop'][$index]) ? true : false; - - $triggerValues = [ - 'action' => $trigger, - 'value' => $value, - 'stopProcessing' => $stopProcessing, - 'order' => $order, - ]; - - $this->storeTrigger($rule, $triggerValues); - $order++; - } + $this->storeTriggers($rule, $data); // same for actions. - $order = 1; - foreach ($data['rule-actions'] as $index => $action) { - $value = $data['rule-action-values'][$index]; - $stopProcessing = isset($data['rule-action-stop'][$index]) ? true : false; - - $actionValues = [ - 'action' => $action, - 'value' => $value, - 'stopProcessing' => $stopProcessing, - 'order' => $order, - ]; - - $this->storeAction($rule, $actionValues); - } + $this->storeActions($rule, $data); return $rule; } @@ -314,6 +307,44 @@ class RuleRepository implements RuleRepositoryInterface $rule->ruleActions()->delete(); // recreate triggers: + $this->storeTriggers($rule, $data); + + // recreate actions: + $this->storeActions($rule, $data); + + + return $rule; + } + + /** + * @param Rule $rule + * @param array $data + */ + private function storeActions(Rule $rule, array $data) + { + $order = 1; + foreach ($data['rule-actions'] as $index => $action) { + $value = $data['rule-action-values'][$index]; + $stopProcessing = isset($data['rule-action-stop'][$index]) ? true : false; + + $actionValues = [ + 'action' => $action, + 'value' => $value, + 'stopProcessing' => $stopProcessing, + 'order' => $order, + ]; + + $this->storeAction($rule, $actionValues); + } + + } + + /** + * @param Rule $rule + * @param array $data + */ + private function storeTriggers(Rule $rule, array $data) + { $order = 1; $stopProcessing = false; @@ -339,24 +370,5 @@ class RuleRepository implements RuleRepositoryInterface $this->storeTrigger($rule, $triggerValues); $order++; } - - // recreate actions: - $order = 1; - foreach ($data['rule-actions'] as $index => $action) { - $value = $data['rule-action-values'][$index]; - $stopProcessing = isset($data['rule-action-stop'][$index]) ? true : false; - - $actionValues = [ - 'action' => $action, - 'value' => $value, - 'stopProcessing' => $stopProcessing, - 'order' => $order, - ]; - - $this->storeAction($rule, $actionValues); - } - - - return $rule; } } diff --git a/app/Repositories/Rule/RuleRepositoryInterface.php b/app/Repositories/Rule/RuleRepositoryInterface.php index 21f554ff48..8e7963d841 100644 --- a/app/Repositories/Rule/RuleRepositoryInterface.php +++ b/app/Repositories/Rule/RuleRepositoryInterface.php @@ -1,4 +1,5 @@ user = $user; + } + /** * @return int */ public function count() { - return Auth::user()->ruleGroups()->count(); + return $this->user->ruleGroups()->count(); } /** @@ -59,7 +74,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface */ public function get() { - return Auth::user()->ruleGroups()->orderBy('order', 'ASC')->get(); + return $this->user->ruleGroups()->orderBy('order', 'ASC')->get(); } /** @@ -67,11 +82,38 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface */ public function getHighestOrderRuleGroup() { - $entry = Auth::user()->ruleGroups()->max('order'); + $entry = $this->user->ruleGroups()->max('order'); return intval($entry); } + /** + * @param User $user + * + * @return Collection + */ + public function getRuleGroupsWithRules(User $user): Collection + { + return $user->ruleGroups() + ->orderBy('active', 'DESC') + ->orderBy('order', 'ASC') + ->with( + [ + 'rules' => function (HasMany $query) { + $query->orderBy('active', 'DESC'); + $query->orderBy('order', 'ASC'); + + }, + 'rules.ruleTriggers' => function (HasMany $query) { + $query->orderBy('order', 'ASC'); + }, + 'rules.ruleActions' => function (HasMany $query) { + $query->orderBy('order', 'ASC'); + }, + ] + )->get(); + } + /** * @param RuleGroup $ruleGroup * @@ -82,7 +124,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface $order = $ruleGroup->order; // find the rule with order+1 and give it order-1 - $other = Auth::user()->ruleGroups()->where('order', ($order + 1))->first(); + $other = $this->user->ruleGroups()->where('order', ($order + 1))->first(); if ($other) { $other->order = ($other->order - 1); $other->save(); @@ -103,7 +145,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface $order = $ruleGroup->order; // find the rule with order-1 and give it order+1 - $other = Auth::user()->ruleGroups()->where('order', ($order - 1))->first(); + $other = $this->user->ruleGroups()->where('order', ($order - 1))->first(); if ($other) { $other->order = ($other->order + 1); $other->save(); @@ -119,9 +161,9 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface */ public function resetRuleGroupOrder() { - Auth::user()->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]); + $this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]); - $set = Auth::user()->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get(); + $set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get(); $count = 1; /** @var RuleGroup $entry */ foreach ($set as $entry) { @@ -202,5 +244,4 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $ruleGroup; } - } diff --git a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php index 3eeb10d860..332a481354 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php +++ b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php @@ -1,9 +1,11 @@ whereIn('accounts.id', $ids) ->after($start) ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); - $amount = $entry->journalAmount; + $amount = $entry->journalAmount ?? '0'; return $amount; } diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 952090e750..7ec2bcd6c8 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -1,15 +1,16 @@ user = $user; + } /** * @param Collection $accounts @@ -32,34 +45,34 @@ class TagRepository implements TagRepositoryInterface public function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end) { $ids = $accounts->pluck('id')->toArray(); - $set = Auth::user()->tags() - ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id') - ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') - ->leftJoin( - 'transactions AS t_from', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_to', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - } - ) - ->where('tags.tagMode', 'balancingAct') - ->where('transaction_types.type', TransactionType::TRANSFER) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereNull('transaction_journals.deleted_at') - ->whereIn('t_from.account_id', $ids) - ->whereIn('t_to.account_id', $ids) - ->groupBy('t_to.account_id') - ->get( - [ - 't_to.account_id', - DB::Raw('SUM(`t_to`.`amount`) as `sum`'), - ] - ); + $set = $this->user->tags() + ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id') + ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->leftJoin( + 'transactions AS t_from', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); + } + ) + ->leftJoin( + 'transactions AS t_to', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); + } + ) + ->where('tags.tagMode', 'balancingAct') + ->where('transaction_types.type', TransactionType::TRANSFER) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->whereNull('transaction_journals.deleted_at') + ->whereIn('t_from.account_id', $ids) + ->whereIn('t_to.account_id', $ids) + ->groupBy('t_to.account_id') + ->get( + [ + 't_to.account_id', + DB::raw('SUM(`t_to`.`amount`) as `sum`'), + ] + ); return $set; } @@ -116,9 +129,8 @@ class TagRepository implements TagRepositoryInterface { // the quickest way to do this is by scanning all balancingAct tags // because there will be less of them any way. - $tags = Auth::user()->tags()->where('tagMode', 'balancingAct')->get(); + $tags = $this->user->tags()->where('tagMode', 'balancingAct')->get(); $amount = '0'; - bcscale(2); /** @var Tag $tag */ foreach ($tags as $tag) { @@ -128,8 +140,8 @@ class TagRepository implements TagRepositoryInterface /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - if ($journal->destination_account->id == $account->id) { - $amount = bcadd($amount, $journal->amount); + if (TransactionJournal::destinationAccount($journal)->id == $account->id) { + $amount = bcadd($amount, TransactionJournal::amount($journal)); } } } @@ -156,7 +168,7 @@ class TagRepository implements TagRepositoryInterface public function get() { /** @var Collection $tags */ - $tags = Auth::user()->tags()->get(); + $tags = $this->user->tags()->get(); $tags = $tags->sortBy( function (Tag $tag) { return strtolower($tag->tag); @@ -181,7 +193,7 @@ class TagRepository implements TagRepositoryInterface $tag->longitude = $data['longitude']; $tag->zoomLevel = $data['zoomLevel']; $tag->tagMode = $data['tagMode']; - $tag->user()->associate(Auth::user()); + $tag->user()->associate($this->user); $tag->save(); return $tag; @@ -375,10 +387,11 @@ class TagRepository implements TagRepositoryInterface foreach ($tag->transactionjournals as $check) { // $checkAccount is the source_account for a withdrawal // $checkAccount is the destination_account for a deposit - if ($check->isWithdrawal() && $check->source_account->id != $journal->destination_account->id) { + + if ($check->isWithdrawal() && TransactionJournal::sourceAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } - if ($check->isDeposit() && $check->destination_account->id != $journal->destination_account->id) { + if ($check->isDeposit() && TransactionJournal::destinationAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index 35f5c0f83c..77c2d73c8b 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -1,4 +1,5 @@ first(); + $user->attachRole($admin); + + return true; + } + + /** + * @return int + */ + public function count(): int + { + return User::count(); + } +} diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php new file mode 100644 index 0000000000..3af280c180 --- /dev/null +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -0,0 +1,34 @@ +action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { // journal has this tag maybe? $tag = Tag::firstOrCreateEncrypted(['tag' => $this->action->action_value, 'user_id' => Auth::user()->id]); - $count = $this->journal->tags()->where('id', $tag->id)->count(); + $count = $journal->tags()->where('tag_id', $tag->id)->count(); if ($count == 0) { - $this->journal->tags()->save($tag); + $journal->tags()->save($tag); } return true; diff --git a/app/Rules/Actions/AppendDescription.php b/app/Rules/Actions/AppendDescription.php index f64c91ee03..21fb94de9e 100644 --- a/app/Rules/Actions/AppendDescription.php +++ b/app/Rules/Actions/AppendDescription.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->description = $this->journal->description . $this->action->action_value; - $this->journal->save(); + $journal->description = $journal->description . $this->action->action_value; + $journal->save(); return true; } diff --git a/app/Rules/Actions/ClearBudget.php b/app/Rules/Actions/ClearBudget.php index 109bd75d77..503d3ca3be 100644 --- a/app/Rules/Actions/ClearBudget.php +++ b/app/Rules/Actions/ClearBudget.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->budgets()->detach(); + $journal->budgets()->detach(); return true; } diff --git a/app/Rules/Actions/ClearCategory.php b/app/Rules/Actions/ClearCategory.php index 9f7a7a1857..98ba1c58a8 100644 --- a/app/Rules/Actions/ClearCategory.php +++ b/app/Rules/Actions/ClearCategory.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->categories()->detach(); + $journal->categories()->detach(); return true; } diff --git a/app/Rules/Actions/PrependDescription.php b/app/Rules/Actions/PrependDescription.php index 97cc5fa1fe..3a692d8049 100644 --- a/app/Rules/Actions/PrependDescription.php +++ b/app/Rules/Actions/PrependDescription.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->description = $this->action->action_value . $this->journal->description; - $this->journal->save(); + $journal->description = $this->action->action_value . $journal->description; + $journal->save(); return true; } diff --git a/app/Rules/Actions/RemoveAllTags.php b/app/Rules/Actions/RemoveAllTags.php index 9ac96b68ec..ebfbe2c49c 100644 --- a/app/Rules/Actions/RemoveAllTags.php +++ b/app/Rules/Actions/RemoveAllTags.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->tags()->detach(); + $journal->tags()->detach(); return true; diff --git a/app/Rules/Actions/RemoveTag.php b/app/Rules/Actions/RemoveTag.php index 4c0bb7888c..b90b4eccb6 100644 --- a/app/Rules/Actions/RemoveTag.php +++ b/app/Rules/Actions/RemoveTag.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { // if tag does not exist, no need to continue: $name = $this->action->action_value; @@ -53,7 +54,7 @@ class RemoveTag implements ActionInterface )->first(); if (!is_null($tag)) { - $this->journal->tags()->detach([$tag->id]); + $journal->tags()->detach([$tag->id]); } return true; diff --git a/app/Rules/Actions/SetBudget.php b/app/Rules/Actions/SetBudget.php index 559d4387d8..7dca1968aa 100644 --- a/app/Rules/Actions/SetBudget.php +++ b/app/Rules/Actions/SetBudget.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { /** @var BudgetRepositoryInterface $repository */ $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); @@ -54,8 +55,8 @@ class SetBudget implements ActionInterface } )->first(); if (!is_null($budget)) { - Log::debug('Will set budget "' . $search . '" (#' . $budget->id . ') on journal #' . $this->journal->id . '.'); - $this->journal->budgets()->sync([$budget->id]); + Log::debug('Will set budget "' . $search . '" (#' . $budget->id . ') on journal #' . $journal->id . '.'); + $journal->budgets()->sync([$budget->id]); } else { Log::debug('Could not find budget "' . $search . '". Failed.'); } diff --git a/app/Rules/Actions/SetCategory.php b/app/Rules/Actions/SetCategory.php index c72bd42f8b..09e76a7461 100644 --- a/app/Rules/Actions/SetCategory.php +++ b/app/Rules/Actions/SetCategory.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { $name = $this->action->action_value; $category = Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => Auth::user()->id]); - Log::debug('Will set category "' . $name . '" (#' . $category->id . ') on journal #' . $this->journal->id . '.'); - $this->journal->categories()->sync([$category->id]); + Log::debug('Will set category "' . $name . '" (#' . $category->id . ') on journal #' . $journal->id . '.'); + $journal->categories()->sync([$category->id]); return true; } diff --git a/app/Rules/Actions/SetDescription.php b/app/Rules/Actions/SetDescription.php index 2a2b81c82a..98bb9c1284 100644 --- a/app/Rules/Actions/SetDescription.php +++ b/app/Rules/Actions/SetDescription.php @@ -1,4 +1,5 @@ action = $action; - $this->journal = $journal; + $this->action = $action; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function act() + public function act(TransactionJournal $journal) { - $this->journal->description = $this->action->action_value; - $this->journal->save(); + $journal->description = $this->action->action_value; + $journal->save(); return true; } diff --git a/app/Rules/Factory/ActionFactory.php b/app/Rules/Factory/ActionFactory.php new file mode 100644 index 0000000000..7cb356bb4a --- /dev/null +++ b/app/Rules/Factory/ActionFactory.php @@ -0,0 +1,84 @@ +action_type); + + return new $class($action); + } + + /** + * Returns the class name to be used for actions with the given name. This is a lookup function + * that will match the given action type (ie. "change_category") to the matching class name + * (SetCategory) using the configuration (firefly.php). + * + * @param string $actionType + * + * @return string + * @throws FireflyException + */ + public static function getActionClass(string $actionType): string + { + $actionTypes = self::getActionTypes(); + + if (!array_key_exists($actionType, $actionTypes)) { + throw new FireflyException('No such action exists ("' . e($actionType) . '").'); + } + + $class = $actionTypes[$actionType]; + if (!class_exists($class)) { + throw new FireflyException('Could not instantiate class for rule action type "' . e($actionType) . '" (' . e($class) . ').'); + } + + return $class; + } + + /** + * Returns a map with actiontypes, mapped to the class representing that type + * + * @return array + */ + protected static function getActionTypes(): array + { + if (count(self::$actionTypes) === 0) { + self::$actionTypes = Domain::getRuleActions(); + } + + return self::$actionTypes; + } +} diff --git a/app/Rules/Factory/TriggerFactory.php b/app/Rules/Factory/TriggerFactory.php new file mode 100644 index 0000000000..6ada8ca347 --- /dev/null +++ b/app/Rules/Factory/TriggerFactory.php @@ -0,0 +1,114 @@ +trigger_type; + + /** @var AbstractTrigger $class */ + $class = self::getTriggerClass($triggerType); + $obj = $class::makeFromTriggerValue($trigger->trigger_value); + + return $obj; + } + + /** + * This method is equal to TriggerFactory::getTrigger but accepts a textual representation of the trigger type + * (for example "description_is"), the trigger value ("Rent") and whether or not Firefly III should stop processing + * other triggers (if present) after this trigger. + * + * This method is used when the RuleTriggers from TriggerFactory::getTrigger do not exist (yet). + * + * @param string $triggerType + * @param string $triggerValue + * @param bool $stopProcessing + * + * @see TriggerFactory::getTrigger + * @return AbstractTrigger + * @throws FireflyException + */ + public static function makeTriggerFromStrings(string $triggerType, string $triggerValue, bool $stopProcessing) + { + /** @var AbstractTrigger $class */ + $class = self::getTriggerClass($triggerType); + $obj = $class::makeFromStrings($triggerValue, $stopProcessing); + + return $obj; + } + + /** + * Returns a map with trigger types, mapped to the class representing that type. + * + * @return array + */ + protected static function getTriggerTypes(): array + { + if (count(self::$triggerTypes) === 0) { + self::$triggerTypes = Domain::getRuleTriggers(); + } + + return self::$triggerTypes; + } + + /** + * Returns the class name to be used for triggers with the given name. This is a lookup function + * that will match the given trigger type (ie. "from_account_ends") to the matching class name + * (FromAccountEnds) using the configuration (firefly.php). + * + * @param string $triggerType + * + * @return TriggerInterface|string + * @throws FireflyException + */ + private static function getTriggerClass(string $triggerType): string + { + $triggerTypes = self::getTriggerTypes(); + + if (!array_key_exists($triggerType, $triggerTypes)) { + throw new FireflyException('No such trigger exists ("' . e($triggerType) . '").'); + } + + $class = $triggerTypes[$triggerType]; + if (!class_exists($class)) { + throw new FireflyException('Could not instantiate class for rule trigger type "' . e($triggerType) . '" (' . e($class) . ').'); + } + + return $class; + } +} diff --git a/app/Rules/Processor.php b/app/Rules/Processor.php index 97d237208b..5b1e4f330b 100644 --- a/app/Rules/Processor.php +++ b/app/Rules/Processor.php @@ -1,4 +1,5 @@ rule = $rule; - $this->journal = $journal; - $this->triggerTypes = Domain::getRuleTriggers(); - $this->actionTypes = Domain::getRuleActions(); + $this->triggers = new Collection; + $this->actions = new Collection; } /** - * @return TransactionJournal + * This method will make a Processor that will process each transaction journal using the triggers + * and actions found in the given Rule. + * + * @param Rule $rule + * + * @return Processor */ - public function getJournal() + public static function make(Rule $rule) { - return $this->journal; + $self = new self; + $self->rule = $rule; + + $triggerSet = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); + /** @var RuleTrigger $trigger */ + foreach ($triggerSet as $trigger) { + $self->triggers->push(TriggerFactory::getTrigger($trigger)); + } + $self->actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); + + return $self; } /** - * @param TransactionJournal $journal + * This method will make a Processor that will process each transaction journal using the given + * trigger (singular!). It can only report if the transaction journal was hit by the given trigger + * and will not be able to act on it using actions. + * + * @param string $triggerName + * @param string $triggerValue + * + * @return Processor */ - public function setJournal($journal) + public static function makeFromString(string $triggerName, string $triggerValue) { - $this->journal = $journal; + $self = new self; + $trigger = TriggerFactory::makeTriggerFromStrings($triggerName, $triggerValue, false); + $self->triggers->push($trigger); + + return $self; } /** - * @return Rule + * This method will make a Processor that will process each transaction journal using the given + * triggers. It can only report if the transaction journal was hit by the given triggers + * and will not be able to act on it using actions. + * + * The given triggers must be in the following format: + * + * [type => xx, value => yy, stopProcessing => bool], [type => xx, value => yy, stopProcessing => bool], + * + * @param array $triggers + * + * @return Processor + */ + public static function makeFromStringArray(array $triggers) + { + $self = new self; + foreach ($triggers as $entry) { + $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stopProcessing']); + $self->triggers->push($trigger); + } + + return $self; + } + + /** + * + * @return \FireflyIII\Models\Rule */ public function getRule() { @@ -73,43 +123,44 @@ class Processor } /** - * @param Rule $rule + * This method will scan the given transaction journal and check if it matches the triggers found in the Processor + * If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal + * matches all of the triggers (regardless of whether the Processor could act on it). + * + * @param TransactionJournal $journal + * + * @return bool */ - public function setRule($rule) - { - $this->rule = $rule; - } - - public function handle() + public function handleTransactionJournal(TransactionJournal $journal) { + $this->journal = $journal; // get all triggers: $triggered = $this->triggered(); if ($triggered) { - Log::debug('Rule #' . $this->rule->id . ' was triggered. Now process each action.'); - $this->actions(); + if ($this->actions->count() > 0) { + $this->actions(); + } + + return true; } + return false; + } /** * @return bool */ - protected function actions() + private function actions() { /** * @var int $index * @var RuleAction $action */ - foreach ($this->rule->ruleActions()->orderBy('order', 'ASC')->get() as $action) { - $type = $action->action_type; - $class = $this->actionTypes[$type]; - Log::debug('Action #' . $action->id . ' for rule #' . $action->rule_id . ' (' . $type . ')'); - if (!class_exists($class)) { - abort(500, 'Could not instantiate class for rule action type "' . $type . '" (' . $class . ').'); - } + foreach ($this->actions as $action) { /** @var ActionInterface $actionClass */ - $actionClass = new $class($action, $this->journal); - $actionClass->act(); + $actionClass = ActionFactory::getAction($action); + $actionClass->act($this->journal); if ($action->stop_processing) { break; } @@ -120,39 +171,31 @@ class Processor } /** + * Method to check whether the current transaction would be triggered + * by the given list of triggers + * * @return bool */ - protected function triggered() + private function triggered() { $foundTriggers = 0; $hitTriggers = 0; /** @var RuleTrigger $trigger */ - foreach ($this->rule->ruleTriggers()->orderBy('order', 'ASC')->get() as $trigger) { + foreach ($this->triggers as $trigger) { $foundTriggers++; - $type = $trigger->trigger_type; - if (!isset($this->triggerTypes[$type])) { - abort(500, 'No such trigger exists ("' . $type . '").'); - } - - $class = $this->triggerTypes[$type]; - Log::debug('Trigger #' . $trigger->id . ' for rule #' . $trigger->rule_id . ' (' . $type . ')'); - if (!class_exists($class)) { - abort(500, 'Could not instantiate class for rule trigger type "' . $type . '" (' . $class . ').'); - } - /** @var TriggerInterface $triggerClass */ - $triggerClass = new $class($trigger, $this->journal); - if ($triggerClass->triggered()) { + /** @var AbstractTrigger $trigger */ + if ($trigger->triggered($this->journal)) { $hitTriggers++; } - if ($trigger->stop_processing) { + if ($trigger->stopProcessing) { break; } } Log::debug('Total: ' . $foundTriggers . ' found triggers. ' . $hitTriggers . ' triggers were hit.'); - return ($hitTriggers == $foundTriggers); + return ($hitTriggers == $foundTriggers && $foundTriggers > 0); } diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php new file mode 100644 index 0000000000..3c970430dc --- /dev/null +++ b/app/Rules/TransactionMatcher.php @@ -0,0 +1,167 @@ +repository = $repository; + + } + + /** + * This method will search the user's transaction journal (with an upper limit of $range) for + * transaction journals matching the given $triggers. This is accomplished by trying to fire these + * triggers onto each transaction journal until enough matches are found ($limit). + * + * @return Collection + * + */ + public function findMatchingTransactions(): Collection + { + if (count($this->triggers) === 0) { + return new Collection; + } + $pagesize = min($this->range / 2, $this->limit * 2); + + // Variables used within the loop + $processed = 0; + $page = 1; + $result = new Collection(); + $processor = Processor::makeFromStringArray($this->triggers); + + // Start a loop to fetch batches of transactions. The loop will finish if: + // - all transactions have been fetched from the database + // - the maximum number of transactions to return has been found + // - the maximum number of transactions to search in have been searched + do { + // Fetch a batch of transactions from the database + $offset = $page > 0 ? ($page - 1) * $pagesize : 0; + $set = $this->repository->getCollectionOfTypes($this->transactionTypes, $offset, $pagesize); + + // Filter transactions that match the given triggers. + $filtered = $set->filter( + function (TransactionJournal $journal) use ($processor) { + return $processor->handleTransactionJournal($journal); + } + ); + + // merge: + $result = $result->merge($filtered); + + // Update counters + $page++; + $processed += count($set); + + // Check for conditions to finish the loop + $reachedEndOfList = $set->count() < $pagesize; + $foundEnough = $result->count() >= $this->limit; + $searchedEnough = ($processed >= $this->range); + } while (!$reachedEndOfList && !$foundEnough && !$searchedEnough); + + // If the list of matchingTransactions is larger than the maximum number of results + // (e.g. if a large percentage of the transactions match), truncate the list + $result = $result->slice(0, $this->limit); + + return $result; + } + + /** + * @return int + */ + public function getLimit(): int + { + return $this->limit; + } + + /** + * @param int $limit + * + * @return TransactionMatcher + */ + public function setLimit($limit): TransactionMatcher + { + $this->limit = $limit; + + return $this; + } + + /** + * @return int + */ + public function getRange(): int + { + return $this->range; + } + + /** + * @param int $range + * + * @return TransactionMatcher + */ + public function setRange($range): TransactionMatcher + { + $this->range = $range; + + return $this; + + } + + /** + * @return array + */ + public function getTriggers(): array + { + return $this->triggers; + } + + /** + * @param array $triggers + * + * @return TransactionMatcher + */ + public function setTriggers($triggers): TransactionMatcher + { + $this->triggers = $triggers; + + return $this; + } + + +} diff --git a/app/Rules/Triggers/AbstractTrigger.php b/app/Rules/Triggers/AbstractTrigger.php new file mode 100644 index 0000000000..cd319afdf6 --- /dev/null +++ b/app/Rules/Triggers/AbstractTrigger.php @@ -0,0 +1,100 @@ +triggerValue = $triggerValue; + $self->stopProcessing = $stopProcessing; + + return $self; + } + + /** + * @param RuleTrigger $trigger + * + * @return AbstractTrigger + */ + public static function makeFromTrigger(RuleTrigger $trigger) + { + $self = new static; + $self->trigger = $trigger; + $self->triggerValue = $trigger->trigger_value; + $self->stopProcessing = $trigger->stop_processing; + + return $self; + } + + /** + * @param RuleTrigger $trigger + * @param TransactionJournal $journal + */ + public static function makeFromTriggerAndJournal(RuleTrigger $trigger, TransactionJournal $journal) + { + $self = new static; + $self->trigger = $trigger; + $self->triggerValue = $trigger->trigger_value; + $self->stopProcessing = $trigger->stop_processing; + $self->journal = $journal; + } + + /** + * @param string $triggerValue + * + * @return AbstractTrigger + */ + public static function makeFromTriggerValue(string $triggerValue) + { + $self = new static; + $self->triggerValue = $triggerValue; + + return $self; + } + + +} diff --git a/app/Rules/Triggers/AmountExactly.php b/app/Rules/Triggers/AmountExactly.php index 5116dd1694..825847298d 100644 --- a/app/Rules/Triggers/AmountExactly.php +++ b/app/Rules/Triggers/AmountExactly.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return false; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $amount = $this->journal->amount_positive; - $compare = $this->trigger->trigger_value; + $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $compare = $this->triggerValue; $result = bccomp($amount, $compare, 4); if ($result === 0) { - // found something - Log::debug($amount . ' is exactly ' . $compare . '. Return true.'); - return true; } - // found nothing. - Log::debug($amount . ' is not exactly ' . $compare . '. Return false.'); - return false; } diff --git a/app/Rules/Triggers/AmountLess.php b/app/Rules/Triggers/AmountLess.php index e358417128..a77b3d2141 100644 --- a/app/Rules/Triggers/AmountLess.php +++ b/app/Rules/Triggers/AmountLess.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return false; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $amount = $this->journal->amount_positive; - $compare = $this->trigger->trigger_value; + $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $compare = $this->triggerValue; $result = bccomp($amount, $compare, 4); if ($result === -1) { - // found something - Log::debug($amount . ' is less than ' . $compare . '. Return true.'); - return true; } - // found nothing. - Log::debug($amount . ' is not less than ' . $compare . '. Return false.'); - return false; } diff --git a/app/Rules/Triggers/AmountMore.php b/app/Rules/Triggers/AmountMore.php index fb4c12fef1..1a0f3eb386 100644 --- a/app/Rules/Triggers/AmountMore.php +++ b/app/Rules/Triggers/AmountMore.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return bccomp('0', strval($value)) === 0; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $amount = $this->journal->amount_positive; - $compare = $this->trigger->trigger_value; + $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $compare = $this->triggerValue; $result = bccomp($amount, $compare, 4); if ($result === 1) { - // found something - Log::debug($amount . ' is more than ' . $compare . '. Return true.'); - return true; } - // found nothing. - Log::debug($amount . ' is not more than ' . $compare . '. Return false.'); - return false; } diff --git a/app/Rules/Triggers/DescriptionContains.php b/app/Rules/Triggers/DescriptionContains.php index 26d311422a..99dfe769f3 100644 --- a/app/Rules/Triggers/DescriptionContains.php +++ b/app/Rules/Triggers/DescriptionContains.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $search = strtolower($this->trigger->trigger_value); - $source = strtolower($this->journal->description); + $search = strtolower($this->triggerValue); + $source = strtolower($journal->description); $strpos = strpos($source, $search); if (!($strpos === false)) { - // found something - Log::debug('"' . $source . '" contains the text "' . $search . '". Return true.'); - return true; } - // found nothing. - Log::debug('"' . $source . '" does not contain the text "' . $search . '". Return false.'); - return false; } diff --git a/app/Rules/Triggers/DescriptionEnds.php b/app/Rules/Triggers/DescriptionEnds.php index 83e7ea0fe9..7e01574f21 100644 --- a/app/Rules/Triggers/DescriptionEnds.php +++ b/app/Rules/Triggers/DescriptionEnds.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $description = strtolower($this->journal->description); + $description = strtolower($journal->description); $descriptionLength = strlen($description); - $search = strtolower($this->trigger->trigger_value); + $search = strtolower($this->triggerValue); $searchLength = strlen($search); // if the string to search for is longer than the description, // shorten the search string. if ($searchLength > $descriptionLength) { - Log::debug('Search string "' . $search . '" (' . $searchLength . ') is longer than "' . $description . '" (' . $descriptionLength . '). '); $search = substr($search, ($descriptionLength * -1)); $searchLength = strlen($search); - Log::debug('Search string is now "' . $search . '" (' . $searchLength . ') instead.'); } - $part = substr($description, $searchLength * -1); if ($part == $search) { - Log::debug('"' . $description . '" ends with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $description . '" does not end with "' . $search . '". Return false.'); return false; - } } diff --git a/app/Rules/Triggers/DescriptionIs.php b/app/Rules/Triggers/DescriptionIs.php index a8fb482eb2..67f6d0d90f 100644 --- a/app/Rules/Triggers/DescriptionIs.php +++ b/app/Rules/Triggers/DescriptionIs.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; - } - - /** * @return bool */ - public function triggered() + public static function willMatchEverything($value = null) { - $description = strtolower($this->journal->description); - $search = strtolower($this->trigger->trigger_value); + if (!is_null($value)) { + return false; + } + + return true; + } + + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function triggered(TransactionJournal $journal) + { + $description = strtolower($journal->description); + $search = strtolower($this->triggerValue); if ($description == $search) { - Log::debug('"' . $description . '" equals "' . $search . '" exactly. Return true.'); - return true; } - Log::debug('"' . $description . '" does not equal "' . $search . '". Return false.'); return false; - } } diff --git a/app/Rules/Triggers/DescriptionStarts.php b/app/Rules/Triggers/DescriptionStarts.php index 0f8facd716..5d42363cd7 100644 --- a/app/Rules/Triggers/DescriptionStarts.php +++ b/app/Rules/Triggers/DescriptionStarts.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $description = strtolower($this->journal->description); - $search = strtolower($this->trigger->trigger_value); + $description = strtolower($journal->description); + $search = strtolower($this->triggerValue); $part = substr($description, 0, strlen($search)); if ($part == $search) { - Log::debug('"' . $description . '" starts with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $description . '" does not start with "' . $search . '". Return false.'); return false; - } } diff --git a/app/Rules/Triggers/FromAccountContains.php b/app/Rules/Triggers/FromAccountContains.php index d9df90d330..a6336f4841 100644 --- a/app/Rules/Triggers/FromAccountContains.php +++ b/app/Rules/Triggers/FromAccountContains.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $fromAccountName = strtolower($this->journal->source_account->name); - $search = strtolower($this->trigger->trigger_value); + $fromAccountName = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); + $search = strtolower($this->triggerValue); $strpos = strpos($fromAccountName, $search); if (!($strpos === false)) { - // found something - Log::debug('"' . $fromAccountName . '" contains the text "' . $search . '". Return true.'); - return true; } - // found nothing. - Log::debug('"' . $fromAccountName . '" does not contain the text "' . $search . '". Return false.'); - return false; } diff --git a/app/Rules/Triggers/FromAccountEnds.php b/app/Rules/Triggers/FromAccountEnds.php index 3836e542fa..91f7da1d3f 100644 --- a/app/Rules/Triggers/FromAccountEnds.php +++ b/app/Rules/Triggers/FromAccountEnds.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $name = strtolower($this->journal->source_account->name); + $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $nameLength = strlen($name); - $search = strtolower($this->trigger->trigger_value); + $search = strtolower($this->triggerValue); $searchLength = strlen($search); // if the string to search for is longer than the account name, // shorten the search string. if ($searchLength > $nameLength) { - Log::debug('Search string "' . $search . '" (' . $searchLength . ') is longer than "' . $name . '" (' . $nameLength . '). '); $search = substr($search, ($nameLength * -1)); $searchLength = strlen($search); - Log::debug('Search string is now "' . $search . '" (' . $searchLength . ') instead.'); } $part = substr($name, $searchLength * -1); if ($part == $search) { - Log::debug('"' . $name . '" ends with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $name . '" does not end with "' . $search . '". Return false.'); return false; diff --git a/app/Rules/Triggers/FromAccountIs.php b/app/Rules/Triggers/FromAccountIs.php index a9e42667ab..526f00adf8 100644 --- a/app/Rules/Triggers/FromAccountIs.php +++ b/app/Rules/Triggers/FromAccountIs.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return false; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $fromAccountName = strtolower($this->journal->source_account->name); - $search = strtolower($this->trigger->trigger_value); - - if ($fromAccountName == $search) { - Log::debug('"' . $fromAccountName . '" equals "' . $search . '" exactly. Return true.'); + $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); + $search = strtolower($this->triggerValue); + if ($name == $search) { return true; } - Log::debug('"' . $fromAccountName . '" does not equal "' . $search . '". Return false.'); return false; diff --git a/app/Rules/Triggers/FromAccountStarts.php b/app/Rules/Triggers/FromAccountStarts.php index 444a65d0fa..f4ce6e7280 100644 --- a/app/Rules/Triggers/FromAccountStarts.php +++ b/app/Rules/Triggers/FromAccountStarts.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; - } - - /** * @return bool */ - public function triggered() + public static function willMatchEverything($value = null) { - $fromAccountName = strtolower($this->journal->source_account->name); - $search = strtolower($this->trigger->trigger_value); + if (!is_null($value)) { + return strval($value) === ''; + } - $part = substr($fromAccountName, 0, strlen($search)); + return true; + } + + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function triggered(TransactionJournal $journal) + { + $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); + $search = strtolower($this->triggerValue); + + $part = substr($name, 0, strlen($search)); if ($part == $search) { - Log::debug('"' . $fromAccountName . '" starts with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $fromAccountName . '" does not start with "' . $search . '". Return false.'); return false; - } } diff --git a/app/Rules/Triggers/ToAccountContains.php b/app/Rules/Triggers/ToAccountContains.php index 9656896fde..cdee61969d 100644 --- a/app/Rules/Triggers/ToAccountContains.php +++ b/app/Rules/Triggers/ToAccountContains.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $toAccountName = strtolower($this->journal->destination_account->name); - $search = strtolower($this->trigger->trigger_value); + $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); + $search = strtolower($this->triggerValue); $strpos = strpos($toAccountName, $search); if (!($strpos === false)) { - // found something - Log::debug('"' . $toAccountName . '" contains the text "' . $search . '". Return true.'); - return true; } - // found nothing. - Log::debug('"' . $toAccountName . '" does not contain the text "' . $search . '". Return false.'); - return false; - } } diff --git a/app/Rules/Triggers/ToAccountEnds.php b/app/Rules/Triggers/ToAccountEnds.php index 354c2015d5..a021c735af 100644 --- a/app/Rules/Triggers/ToAccountEnds.php +++ b/app/Rules/Triggers/ToAccountEnds.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $toAccountName = strtolower($this->journal->destination_account->name); + $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $toAccountNameLength = strlen($toAccountName); - $search = strtolower($this->trigger->trigger_value); + $search = strtolower($this->triggerValue); $searchLength = strlen($search); // if the string to search for is longer than the account name, // shorten the search string. if ($searchLength > $toAccountNameLength) { - Log::debug('Search string "' . $search . '" (' . $searchLength . ') is longer than "' . $toAccountName . '" (' . $toAccountNameLength . '). '); $search = substr($search, ($toAccountNameLength * -1)); $searchLength = strlen($search); - Log::debug('Search string is now "' . $search . '" (' . $searchLength . ') instead.'); } $part = substr($toAccountName, $searchLength * -1); if ($part == $search) { - Log::debug('"' . $toAccountName . '" ends with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $toAccountName . '" does not end with "' . $search . '". Return false.'); return false; - } } diff --git a/app/Rules/Triggers/ToAccountIs.php b/app/Rules/Triggers/ToAccountIs.php index 1a1ab08b91..4dc6d884ec 100644 --- a/app/Rules/Triggers/ToAccountIs.php +++ b/app/Rules/Triggers/ToAccountIs.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return false; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $toAccountName = strtolower($this->journal->destination_account->name); - $search = strtolower($this->trigger->trigger_value); + $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); + $search = strtolower($this->triggerValue); if ($toAccountName == $search) { - Log::debug('"' . $toAccountName . '" equals "' . $search . '" exactly. Return true.'); - return true; } - Log::debug('"' . $toAccountName . '" does not equal "' . $search . '". Return false.'); return false; diff --git a/app/Rules/Triggers/ToAccountStarts.php b/app/Rules/Triggers/ToAccountStarts.php index 52eddebf2d..45b1fc444c 100644 --- a/app/Rules/Triggers/ToAccountStarts.php +++ b/app/Rules/Triggers/ToAccountStarts.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return strval($value) === ''; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $toAccountName = strtolower($this->journal->destination_account->name); - $search = strtolower($this->trigger->trigger_value); + $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); + $search = strtolower($this->triggerValue); $part = substr($toAccountName, 0, strlen($search)); if ($part == $search) { - Log::debug('"' . $toAccountName . '" starts with "' . $search . '". Return true.'); - return true; } - Log::debug('"' . $toAccountName . '" does not start with "' . $search . '". Return false.'); return false; diff --git a/app/Rules/Triggers/TransactionType.php b/app/Rules/Triggers/TransactionType.php index d7aa688d62..ea14512c46 100644 --- a/app/Rules/Triggers/TransactionType.php +++ b/app/Rules/Triggers/TransactionType.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; + if (!is_null($value)) { + return false; + } + + return true; } /** + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - $type = strtolower($this->journal->transactionType->type); - $search = strtolower($this->trigger->trigger_value); + $type = !is_null($journal->transaction_type_type) ? $journal->transaction_type_type : strtolower($journal->transactionType->type); + $search = strtolower($this->triggerValue); if ($type == $search) { - Log::debug('Journal is of type "' . $type . '" which matches with "' . $search . '". Return true'); - return true; } - Log::debug('Journal is of type "' . $type . '" which does not match with "' . $search . '". Return false'); return false; } diff --git a/app/Rules/Triggers/TriggerInterface.php b/app/Rules/Triggers/TriggerInterface.php index 17147db9e4..2081f5142f 100644 --- a/app/Rules/Triggers/TriggerInterface.php +++ b/app/Rules/Triggers/TriggerInterface.php @@ -1,4 +1,5 @@ trigger = $trigger; - $this->journal = $journal; - + return true; } /** * This trigger is always triggered, because the rule that it is a part of has been pre-selected on this condition. * + * @param TransactionJournal $journal + * * @return bool */ - public function triggered() + public function triggered(TransactionJournal $journal) { - Log::debug('user_action always returns true.'); - return true; } - } diff --git a/app/Sql/Query.php b/app/Sql/Query.php index 8abf2376e1..ccc9a6244f 100644 --- a/app/Sql/Query.php +++ b/app/Sql/Query.php @@ -1,4 +1,5 @@ formatAnything($this->getDefaultCurrency(), $amount, $coloured); } @@ -33,16 +34,17 @@ class Amount * as a currency, given two things: the currency required and the current locale. * * @param TransactionCurrency $format - * @param $amount + * @param string $amount * @param bool $coloured * * @return string */ - public function formatAnything(TransactionCurrency $format, $amount, $coloured = true) + public function formatAnything(TransactionCurrency $format, string $amount, bool $coloured = true): string { $locale = setlocale(LC_MONETARY, 0); + $float = round($amount, 2); $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); - $result = $formatter->formatCurrency($amount, $format->code); + $result = $formatter->formatCurrency($float, $format->code); if ($coloured === true) { if ($amount == 0) { @@ -61,38 +63,37 @@ class Amount /** * - * @param TransactionJournal $journal - * @param bool $coloured + * @param \FireflyIII\Models\TransactionJournal $journal + * @param bool $coloured * * @return string */ - public function formatJournal(TransactionJournal $journal, $coloured = true) + public function formatJournal(TransactionJournal $journal, bool $coloured = true): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('formatJournal'); - - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + $locale = setlocale(LC_MONETARY, 0); + $float = round($journal->destination_amount, 2); + if ($journal->isWithdrawal()) { + $float = round($journal->source_amount, 2); } + $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); + $currencyCode = $journal->transaction_currency_code ?? $journal->transactionCurrency->code; + $result = $formatter->formatCurrency($float, $currencyCode); - if ($journal->isTransfer() && $coloured) { - $txt = '' . $this->formatAnything($journal->transactionCurrency, $journal->amount_positive, false) . ''; - $cache->store($txt); - - return $txt; + if ($coloured === true && $float == 0) { + return '' . $result . ''; // always grey. } - if ($journal->isTransfer() && !$coloured) { - $txt = $this->formatAnything($journal->transactionCurrency, $journal->amount_positive, false); - $cache->store($txt); - - return $txt; + if (!$coloured) { + return $result; } + if (!$journal->isTransfer()) { + if ($float > 0) { + return '' . $result . ''; + } - $txt = $this->formatAnything($journal->transactionCurrency, $journal->amount, $coloured); - $cache->store($txt); - - return $txt; + return '' . $result . ''; + } else { + return '' . $result . ''; + } } /** @@ -101,25 +102,13 @@ class Amount * * @return string */ - public function formatTransaction(Transaction $transaction, $coloured = true) + public function formatTransaction(Transaction $transaction, bool $coloured = true) { $currency = $transaction->transactionJournal->transactionCurrency; return $this->formatAnything($currency, $transaction->amount, $coloured); } - /** - * @param string $symbol - * @param float $amount - * @param bool $coloured - * - * @return string - */ - public function formatWithSymbol($symbol, $amount, $coloured = true) - { - return $this->formatAnything($this->getDefaultCurrency(), $amount, $coloured); - } - /** * @return Collection */ diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index b9cbde52ae..586d4ff8f4 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -1,4 +1,5 @@ where('account_types.editable', 1) @@ -47,4 +52,22 @@ class AccountList implements BinderInterface } throw new NotFoundHttpException; } + + /** + * @param array $ids + * + * @return array + */ + protected static function filterIds(array $ids): array + { + $new = []; + foreach ($ids as $id) { + if (intval($id) > 0) { + $new[] = $id; + } + } + $new = array_unique($new); + + return $new; + } } diff --git a/app/Support/Binder/BinderInterface.php b/app/Support/Binder/BinderInterface.php index 900d47b042..b3b3b1d583 100644 --- a/app/Support/Binder/BinderInterface.php +++ b/app/Support/Binder/BinderInterface.php @@ -1,4 +1,5 @@ properties = new Collection; - $this->addProperty(Auth::user()->id); - $this->addProperty(Prefs::lastActivity()); + if (Auth::check()) { + $this->addProperty(Auth::user()->id); + $this->addProperty(Prefs::lastActivity()); + } } /** @@ -53,7 +56,7 @@ class CacheProperties /** * @return string */ - public function getMd5() + public function getMd5(): string { return $this->md5; } @@ -61,7 +64,7 @@ class CacheProperties /** * @return bool */ - public function has() + public function has(): bool { if (getenv('APP_ENV') == 'testing') { return false; diff --git a/app/Support/Domain.php b/app/Support/Domain.php index 0c4d209b26..d1df4dc185 100644 --- a/app/Support/Domain.php +++ b/app/Support/Domain.php @@ -1,4 +1,5 @@ label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -51,7 +52,7 @@ class ExpandedForm * * @return string */ - public function balance($name, $value = null, array $options = []) + public function balance(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -75,7 +76,7 @@ class ExpandedForm * * @return string */ - public function checkbox($name, $value = 1, $checked = null, $options = []) + public function checkbox(string $name, $value = 1, $checked = null, $options = []): string { $options['checked'] = $checked === true ? true : null; $label = $this->label($name, $options); @@ -97,7 +98,7 @@ class ExpandedForm * * @return string */ - public function date($name, $value = null, array $options = []) + public function date(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -115,7 +116,7 @@ class ExpandedForm * * @return string */ - public function file($name, array $options = []) + public function file(string $name, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -133,7 +134,7 @@ class ExpandedForm * * @return string */ - public function integer($name, $value = null, array $options = []) + public function integer(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -153,7 +154,7 @@ class ExpandedForm * * @return string */ - public function location($name, $value = null, array $options = []) + public function location(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -172,9 +173,9 @@ class ExpandedForm * @param \Illuminate\Support\Collection $set * @param bool $addEmpty * - * @return mixed + * @return array */ - public function makeSelectList(Collection $set, $addEmpty = false) + public function makeSelectList(Collection $set, bool $addEmpty = false): array { $selectList = []; if ($addEmpty) { @@ -205,7 +206,7 @@ class ExpandedForm * * @return string */ - public function multiCheckbox($name, array $list = [], $selected = null, array $options = []) + public function multiCheckbox(string $name, array $list = [], $selected = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -226,7 +227,7 @@ class ExpandedForm * * @return string */ - public function multiRadio($name, array $list = [], $selected = null, array $options = []) + public function multiRadio(string $name, array $list = [], $selected = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -245,7 +246,7 @@ class ExpandedForm * * @return string */ - public function optionsList($type, $name) + public function optionsList($type, $name): string { $previousValue = null; @@ -271,7 +272,7 @@ class ExpandedForm * * @return string */ - public function select($name, array $list = [], $selected = null, array $options = []) + public function select(string $name, array $list = [], $selected = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -291,7 +292,7 @@ class ExpandedForm * * @return string */ - public function staticText($name, $value, array $options = []) + public function staticText(string $name, $value, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -310,7 +311,7 @@ class ExpandedForm * * @return string */ - public function tags($name, $value = null, array $options = []) + public function tags(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -329,7 +330,7 @@ class ExpandedForm * * @return string */ - public function text($name, $value = null, array $options = []) + public function text(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -348,7 +349,7 @@ class ExpandedForm * * @return string */ - public function textarea($name, $value = null, array $options = []) + public function textarea(string $name, $value = null, array $options = []): string { $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); @@ -368,7 +369,7 @@ class ExpandedForm * * @return array */ - protected function expandOptionArray($name, $label, array $options) + protected function expandOptionArray(string $name, $label, array $options): array { $options['class'] = 'form-control'; $options['id'] = 'ffInput_' . $name; @@ -384,10 +385,10 @@ class ExpandedForm * * @return mixed */ - protected function fillFieldValue($name, $value) + protected function fillFieldValue(string $name, $value) { if (Session::has('preFilled')) { - $preFilled = Session::get('preFilled'); + $preFilled = session('preFilled'); $value = isset($preFilled[$name]) && is_null($value) ? $preFilled[$name] : $value; } // @codeCoverageIgnoreStart @@ -409,13 +410,13 @@ class ExpandedForm * * @return string */ - protected function getHolderClasses($name) + protected function getHolderClasses(string $name): string { /* * Get errors from session: */ /** @var MessageBag $errors */ - $errors = Session::get('errors'); + $errors = session('errors'); $classes = 'form-group'; if (!is_null($errors) && $errors->has($name)) { @@ -431,13 +432,13 @@ class ExpandedForm * * @return mixed */ - protected function label($name, $options) + protected function label(string $name, array $options): string { if (isset($options['label'])) { return $options['label']; } - return trans('form.' . $name); + return strval(trans('form.' . $name)); } } diff --git a/app/Support/Facades/Amount.php b/app/Support/Facades/Amount.php index 4ef94d5c93..c6d2b43250 100644 --- a/app/Support/Facades/Amount.php +++ b/app/Support/Facades/Amount.php @@ -1,4 +1,5 @@ $user, + 'description' => 'Some journal for attachment', + 'date' => $start, + 'from' => 'Job', + 'to' => 'TestData Checking Account', + 'amount' => '100', + 'transaction_type' => 2, + ]; + $journal = self::createJournal($args); + + // and now attachments + $encrypted = Crypt::encrypt('I are secret'); + $one = Attachment::create( + [ + 'attachable_id' => $journal->id, + 'attachable_type' => 'FireflyIII\Models\TransactionJournal', + 'user_id' => $user->id, + 'md5' => md5('Hallo'), + 'filename' => 'empty-file.txt', + 'title' => 'Empty file', + 'description' => 'This file is empty', + 'notes' => 'What notes', + 'mime' => 'text/plain', + 'size' => strlen($encrypted), + 'uploaded' => 1, + ] + ); + + + // and now attachment. + $two = Attachment::create( + [ + 'attachable_id' => $journal->id, + 'attachable_type' => 'FireflyIII\Models\TransactionJournal', + 'user_id' => $user->id, + 'md5' => md5('Ook hallo'), + 'filename' => 'empty-file-2.txt', + 'title' => 'Empty file 2', + 'description' => 'This file is empty too', + 'notes' => 'What notes do', + 'mime' => 'text/plain', + 'size' => strlen($encrypted), + 'uploaded' => 1, + ] + ); + // echo crypted data to the file. + $disk = Storage::disk('upload'); + $disk->put('at-' . $one->id . '.data', $encrypted); + $disk->put('at-' . $two->id . '.data', $encrypted); + + return $journal; } /** * @param User $user + * + * @return bool */ - public static function createBills(User $user) + public static function createBills(User $user): bool { Bill::create( [ @@ -93,13 +179,283 @@ class TestData 'skip' => 0, ] ); + + return true; + } /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @param User $user + * @param User $user + * @param Carbon $current + * @param string $name + * @param string $amount + * + * @return BudgetLimit */ - public static function createPiggybanks(User $user) + public static function createBudgetLimit(User $user, Carbon $current, string $name, string $amount): BudgetLimit + { + $start = clone $current; + $end = clone $current; + $budget = self::findBudget($user, $name); + $start->startOfMonth(); + $end->endOfMonth(); + + $limit = BudgetLimit::create( + [ + 'budget_id' => $budget->id, + 'startdate' => $start->format('Y-m-d'), + 'amount' => $amount, + 'repeats' => 0, + 'repeat_freq' => 'monthly', + ] + ); + + return $limit; + } + + /** + * @param User $user + * + * @return bool + */ + public static function createBudgets(User $user): bool + { + Budget::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); + Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $user->id]); + Budget::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); + + // some empty budgets. + foreach (['A', 'B', 'C', 'D', 'E'] as $letter) { + Budget::firstOrCreateEncrypted(['name' => 'Empty budget ' . $letter, 'user_id' => $user->id]); + } + + return true; + } + + /** + * @param User $user + * @param Carbon $date + * + * @return TransactionJournal + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public static function createCar(User $user, Carbon $date): TransactionJournal + { + // twice: + $amount = strval(rand(4000, 5000) / 100); + $args = [ + 'user' => $user, + 'description' => 'Bought gas', + 'date' => new Carbon($date->format('Y-m') . '-10'),// paid on 10th + 'from' => 'TestData Checking Account', + 'to' => 'Shell', + 'amount' => $amount, + 'category' => 'Car', + 'budget' => 'Car', + ]; + self::createJournal($args); + + // again! + $args['date'] = new Carbon($date->format('Y-m') . '-20'); // paid on 20th + $args['amount'] = strval(rand(4000, 5000) / 100); + $args['description'] = 'Gas for car'; + $journal = self::createJournal($args); + + return $journal; + } + + /** + * @param User $user + * + * @return bool + */ + public static function createCategories(User $user): bool + { + Category::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); + Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); + + return true; + } + + /** + * @param User $user + * @param Carbon $date + * + * @return bool + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public static function createDrinksAndOthers(User $user, Carbon $date): bool + { + $start = clone $date; + $end = clone $date; + $today = new Carbon; + $start->startOfMonth(); + $end->endOfMonth(); + $current = clone $start; + while ($current < $end && $current < $today) { + + // weekly drink: + $thisDate = clone $current; + $thisDate->addDay(); + $amount = strval(rand(1500, 3600) / 100); + $args = [ + 'user' => $user, + 'description' => 'Going out for drinks', + 'date' => $thisDate, + 'from' => 'TestData Checking Account', + 'to' => 'Cafe Central', + 'amount' => $amount, + 'category' => 'Drinks', + 'budget' => 'Going out', + ]; + self::createJournal($args); + + $current->addWeek(); + } + + return true; + } + + /** + * @param User $user + * + * @return bool + */ + public static function createExpenseAccounts(User $user): bool + { + $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl', + 'Coolblue', 'Shell', + 'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord']; + foreach ($expenses as $name) { + // create account: + Account::create( + [ + 'user_id' => $user->id, + 'account_type_id' => 4, + 'name' => $name, + 'active' => 1, + 'encrypted' => 1, + ] + ); + } + + return true; + + } + + /** + * @param User $user + * @param Carbon $date + * + * @return bool + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public static function createGroceries(User $user, Carbon $date): bool + { + $start = clone $date; + $end = clone $date; + $today = new Carbon; + $start->startOfMonth(); + $end->endOfMonth(); + + $stores = ['Albert Heijn', 'PLUS', 'Bakker']; + $descriptions = ['Groceries', 'Bought some groceries', 'Got groceries']; + + $current = clone $start; + while ($current < $end && $current < $today) { + // daily groceries: + $amount = (string)round((rand(1500, 2500) / 100), 2); + + $args = [ + 'user' => $user, + 'description' => $descriptions[rand(0, count($descriptions) - 1)], + 'date' => $current, + 'from' => 'TestData Checking Account', + 'to' => $stores[rand(0, count($stores) - 1)], + 'amount' => $amount, + 'category' => 'Daily groceries', + 'budget' => 'Groceries', + ]; + self::createJournal($args); + $current->addDay(); + } + + return true; + } + + /** + * @param User $user + * @param string $description + * @param Carbon $date + * @param string $amount + * + * @return TransactionJournal + */ + public static function createIncome(User $user, string $description, Carbon $date, string $amount): TransactionJournal + { + $date = new Carbon($date->format('Y-m') . '-23'); // paid on 23rd. + $today = new Carbon; + if ($date >= $today) { + return new TransactionJournal; + } + + // create journal: + $args = [ + 'user' => $user, + 'description' => $description, + 'date' => $date, + 'from' => 'Job', + 'to' => 'TestData Checking Account', + 'amount' => $amount, + 'category' => 'Salary', + 'transaction_type' => 2, + ]; + $journal = self::createJournal($args); + + return $journal; + + } + + /** + * @param array $opt + * + * @return TransactionJournal + */ + public static function createJournal(array $opt): TransactionJournal + { + $type = $opt['transaction_type'] ?? 1; + + $journal = TransactionJournal::create( + [ + 'user_id' => $opt['user']->id, + 'transaction_type_id' => $type, + 'transaction_currency_id' => 1, + 'description' => $opt['description'], + 'completed' => 1, + 'date' => $opt['date'], + ] + ); + self::createTransactions($journal, self::findAccount($opt['user'], $opt['from']), self::findAccount($opt['user'], $opt['to']), $opt['amount']); + if (isset($opt['category'])) { + $journal->categories()->save(self::findCategory($opt['user'], $opt['category'])); + } + if (isset($opt['budget'])) { + $journal->budgets()->save(self::findBudget($opt['user'], $opt['budget'])); + } + + return $journal; + } + + /** + * @param User $user + * + * @return bool + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public static function createPiggybanks(User $user): bool { $account = self::findAccount($user, 'TestData Savings'); @@ -231,27 +587,404 @@ class TestData ] ); + return true; + } + + /** + * @param User $user + * @param string $description + * @param Carbon $date + * @param string $amount + * + * @return TransactionJournal + */ + public static function createPower(User $user, string $description, Carbon $date, string $amount): TransactionJournal + { + $args = [ + 'user' => $user, + 'description' => $description, + 'date' => new Carbon($date->format('Y-m') . '-06'),// paid on 10th + 'from' => 'TestData Checking Account', + 'to' => 'Greenchoice', + 'amount' => $amount, + 'category' => 'House', + 'budget' => 'Bills', + ]; + $journal = self::createJournal($args); + + return $journal; + + } + + /** + * @param User $user + * @param string $description + * @param Carbon $date + * @param string $amount + * + * @return TransactionJournal + */ + public static function createRent(User $user, string $description, Carbon $date, string $amount): TransactionJournal + { + $args = [ + 'user' => $user, + 'description' => $description, + 'date' => $date, + 'from' => 'TestData Checking Account', + 'to' => 'Land lord', + 'amount' => $amount, + 'category' => 'Rent', + 'budget' => 'Bills', + ]; + $journal = self::createJournal($args); + + return $journal; + + } + + /** + * @param User $user + * + * @return bool + */ + public static function createRevenueAccounts(User $user): bool + { + $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google']; + foreach ($revenues as $name) { + // create account: + Account::create( + [ + 'user_id' => $user->id, + 'account_type_id' => 5, + 'name' => $name, + 'active' => 1, + 'encrypted' => 1, + ] + ); + } + + return true; + } + + /** + * @param User $user + * + * @return RuleGroup + */ + public static function createRules(User $user): RuleGroup + { + $group = RuleGroup::create( + [ + 'user_id' => $user->id, + 'order' => 1, + 'title' => trans('firefly.default_rule_group_name'), + 'description' => trans('firefly.default_rule_group_description'), + 'active' => 1, + ] + ); + $rule = Rule::create( + [ + 'user_id' => $user->id, + 'rule_group_id' => $group->id, + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + 'title' => trans('firefly.default_rule_name'), + 'description' => trans('firefly.default_rule_description'), + ] + ); + + // three triggers: + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'order' => 1, + 'active' => 1, + 'stop_processing' => 0, + 'trigger_type' => 'user_action', + 'trigger_value' => 'store-journal', + ] + ); + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'order' => 2, + 'active' => 1, + 'stop_processing' => 0, + 'trigger_type' => 'description_is', + 'trigger_value' => trans('firefly.default_rule_trigger_description'), + ] + ); + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'order' => 3, + 'active' => 1, + 'stop_processing' => 0, + 'trigger_type' => 'from_account_is', + 'trigger_value' => trans('firefly.default_rule_trigger_from_account'), + ] + ); + + // two actions: + RuleAction::create( + [ + 'rule_id' => $rule->id, + 'order' => 1, + 'active' => 1, + 'action_type' => 'prepend_description', + 'action_value' => trans('firefly.default_rule_action_prepend'), + ] + ); + RuleAction::create( + [ + 'rule_id' => $rule->id, + 'order' => 1, + 'active' => 1, + 'action_type' => 'set_category', + 'action_value' => trans('firefly.default_rule_action_set_category'), + ] + ); + + return $group; + } + + /** + * @param User $user + * @param Carbon $date + * + * @return TransactionJournal + */ + public static function createSavings(User $user, Carbon $date): TransactionJournal + { + $args = [ + 'user' => $user, + 'description' => 'Save money', + 'date' => new Carbon($date->format('Y-m') . '-24'),// paid on 24th. + 'from' => 'TestData Checking Account', + 'to' => 'TestData Savings', + 'amount' => '150', + 'category' => 'Money management', + 'transaction_type' => 3, + ]; + $journal = self::createJournal($args); + + return $journal; + + } + + /** + * @param User $user + * @param string $description + * @param Carbon $date + * @param string $amount + * + * @return TransactionJournal + */ + public static function createTV(User $user, string $description, Carbon $date, string $amount): TransactionJournal + { + $args = [ + 'user' => $user, + 'description' => $description, + 'date' => new Carbon($date->format('Y-m') . '-15'), + 'from' => 'TestData Checking Account', + 'to' => 'XS4All', + 'amount' => $amount, + 'category' => 'House', + 'budget' => 'Bills', + ]; + $journal = self::createJournal($args); + + return $journal; + + } + + /** + * @param User $user + * @param Carbon $date + * + * @return Tag + */ + public static function createTags(User $user, Carbon $date): Tag + { + $title = 'SomeTag' . $date->month . '.' . $date->year . '.nothing'; + + $tag = Tag::create( + [ + 'user_id' => $user->id, + 'tag' => $title, + 'tagMode' => 'nothing', + 'date' => $date->format('Y-m-d'), + + + ] + ); + + return $tag; + } + + /** + * @param TransactionJournal $journal + * @param Account $from + * @param Account $to + * @param string $amount + * + * @return bool + */ + public static function createTransactions(TransactionJournal $journal, Account $from, Account $to, string $amount): bool + { + Transaction::create( + [ + 'account_id' => $from->id, + 'transaction_journal_id' => $journal->id, + 'amount' => bcmul($amount, '-1'), + + ] + ); + Transaction::create( + [ + 'account_id' => $to->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $amount, + + ] + ); + + return true; + } + + /** + * @return User + */ + public static function createUsers(): User + { + $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); + User::create(['email' => 'thegrumpydictator+empty@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); + User::create(['email' => 'thegrumpydictator+deleteme@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); + + + $admin = Role::where('name', 'owner')->first(); + $user->attachRole($admin); + + return $user; + } + + /** + * @param User $user + * @param string $description + * @param Carbon $date + * @param string $amount + * + * @return TransactionJournal + */ + public static function createWater(User $user, string $description, Carbon $date, string $amount): TransactionJournal + { + $args = [ + 'user' => $user, + 'description' => $description, + 'date' => new Carbon($date->format('Y-m') . '-10'), // paid on 10th + 'from' => 'TestData Checking Account', + 'to' => 'Vitens', + 'amount' => $amount, + 'category' => 'House', + 'budget' => 'Bills', + ]; + $journal = self::createJournal($args); + + return $journal; + } /** * @param User $user * @param $name * - * @return Account|null + * @return Account */ - public static function findAccount(User $user, $name) + public static function findAccount(User $user, string $name): Account { /** @var Account $account */ foreach ($user->accounts()->get() as $account) { if ($account->name == $name) { - Log::debug('Trying to find "' . $name . '" in "' . $account->name . '", and found it!'); - return $account; } - Log::debug('Trying to find "' . $name . '" in "' . $account->name . '".'); } - return null; + return new Account; + } + + /** + * @param User $user + * @param $name + * + * @return Budget + */ + public static function findBudget(User $user, string $name): Budget + { + /** @var Budget $budget */ + foreach (Budget::get() as $budget) { + if ($budget->name == $name && $user->id == $budget->user_id) { + return $budget; + } + } + + return Budget::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]); + } + + /** + * @param User $user + * @param $name + * + * @return Category + */ + public static function findCategory(User $user, string $name): Category + { + /** @var Category $category */ + foreach (Category::get() as $category) { + if ($category->name == $name && $user->id == $category->user_id) { + return $category; + } + } + + return Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]); + } + + /** + * @param User $user + * @param Carbon $date + * + * @return TransactionJournal + */ + public static function openingBalanceSavings(User $user, Carbon $date): TransactionJournal + { + // opposing account for opening balance: + $opposing = Account::create( + [ + 'user_id' => $user->id, + 'account_type_id' => 6, + 'name' => 'Opposing for savings', + 'active' => 1, + 'encrypted' => 1, + ] + ); + + // savings + $savings = TestData::findAccount($user, 'TestData Savings'); + + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 4, + 'transaction_currency_id' => 1, + 'description' => 'Opening balance for savings account', + 'completed' => 1, + 'date' => $date->format('Y-m-d'), + ] + ); + self::createTransactions($journal, $opposing, $savings, '10000'); + + return $journal; + } diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php new file mode 100644 index 0000000000..53688699a0 --- /dev/null +++ b/app/Support/Models/TransactionJournalSupport.php @@ -0,0 +1,219 @@ +addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('amount'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $transaction = $journal->transactions->sortByDesc('amount')->first(); + $amount = $transaction->amount; + if ($journal->isWithdrawal()) { + $amount = bcmul($amount, '-1'); + } + $cache->store($amount); + + return $amount; + + + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + public static function amountPositive(TransactionJournal $journal): string + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('amount-positive'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $amount = '0'; + /** @var Transaction $t */ + foreach ($journal->transactions as $t) { + if ($t->amount > 0) { + $amount = $t->amount; + } + } + $cache->store($amount); + + return $amount; + } + + /** + * @param TransactionJournal $journal + * + * @return Account + */ + public static function destinationAccount(TransactionJournal $journal): Account + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-account'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + if (!is_null($transaction)) { + $account = $transaction->account; + $cache->store($account); + } else { + $account = new Account; + } + + return $account; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + public static function destinationAccountTypeStr(TransactionJournal $journal): string + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-account-type-str'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $account = self::destinationAccount($journal); + $type = $account->accountType ? $account->accountType->type : '(unknown)'; + $cache->store($type); + + return $type; + } + + /** + * @param Builder $query + * @param string $table + * + * @return bool + */ + public static function isJoined(Builder $query, string $table):bool + { + $joins = $query->getQuery()->joins; + if (is_null($joins)) { + return false; + } + foreach ($joins as $join) { + if ($join->table === $table) { + return true; + } + } + + return false; + } + + /** + * @param TransactionJournal $journal + * + * @return Account + */ + public static function sourceAccount(TransactionJournal $journal): Account + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-account'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transaction = $journal->transactions()->where('amount', '<', 0)->first(); + if (!is_null($transaction)) { + $account = $transaction->account; + $cache->store($account); + } else { + $account = new Account; + } + + return $account; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + public static function sourceAccountTypeStr(TransactionJournal $journal): string + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-account-type-str'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $account = self::sourceAccount($journal); + $type = $account->accountType ? $account->accountType->type : '(unknown)'; + $cache->store($type); + + return $type; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + public static function transactionTypeStr(TransactionJournal $journal): string + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('type-string'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $typeStr = $journal->transaction_type_type ?? $journal->transactionType->type; + $cache->store($typeStr); + + return $typeStr; + } + + +} diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index fe83828573..51cb6091bc 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -1,4 +1,5 @@ 'endOfDay', @@ -161,30 +162,30 @@ class Navigation * @return string * @throws FireflyException */ - public function periodShow(Carbon $date, $repeatFrequency) + public function periodShow(Carbon $date, string $repeatFrequency) { $formatMap = [ - '1D' => '%e %B %Y', - 'daily' => '%e %B %Y', - 'custom' => '%e %B %Y', - '1W' => 'Week %W, %Y', - 'week' => 'Week %W, %Y', - 'weekly' => 'Week %W, %Y', - '3M' => '%B %Y', - 'quarter' => '%B %Y', - '1M' => '%B %Y', - 'month' => '%B %Y', - 'monthly' => '%B %Y', - '1Y' => '%Y', - 'year' => '%Y', - 'yearly' => '%Y', - '6M' => '%B %Y', + '1D' => trans('config.specific_day'), + 'daily' => trans('config.specific_day'), + 'custom' => trans('config.specific_day'), + '1W' => trans('config.week_in_year'), + 'week' => trans('config.week_in_year'), + 'weekly' => trans('config.week_in_year'), + '3M' => trans('config.quarter_of_year'), + 'quarter' => trans('config.quarter_of_year'), + '1M' => trans('config.month'), + 'month' => trans('config.month'), + 'monthly' => trans('config.month'), + '1Y' => trans('config.year'), + 'year' => trans('config.year'), + 'yearly' => trans('config.year'), + '6M' => trans('config.half_year'), ]; if (isset($formatMap[$repeatFrequency])) { - return $date->formatLocalized($formatMap[$repeatFrequency]); + return $date->formatLocalized(strval($formatMap[$repeatFrequency])); } throw new FireflyException('No date formats for frequency "' . $repeatFrequency . '"!'); } @@ -196,7 +197,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function startOfPeriod(Carbon $theDate, $repeatFreq) + public function startOfPeriod(Carbon $theDate, string $repeatFreq) { $date = clone $theDate; @@ -247,7 +248,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function subtractPeriod(Carbon $theDate, $repeatFreq, $subtract = 1) + public function subtractPeriod(Carbon $theDate, string $repeatFreq, int $subtract = 1) { $date = clone $theDate; // 1D 1W 1M 3M 6M 1Y @@ -286,13 +287,14 @@ class Navigation // a custom range requires the session start // and session end to calculate the difference in days. // this is then subtracted from $theDate (* $subtract). - if($repeatFreq === 'custom') { + if ($repeatFreq === 'custom') { /** @var Carbon $tStart */ $tStart = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $tEnd */ $tEnd = session('end', Carbon::now()->endOfMonth()); $diffInDays = $tStart->diffInDays($tEnd); $date->subDays($diffInDays * $subtract); + return $date; } @@ -306,7 +308,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function updateEndDate($range, Carbon $start) + public function updateEndDate(string $range, Carbon $start) { $functionMap = [ '1D' => 'endOfDay', @@ -342,7 +344,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function updateStartDate($range, Carbon $start) + public function updateStartDate(string $range, Carbon $start) { $functionMap = [ '1D' => 'startOfDay', diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 531eb3e0ad..b285d63b19 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -1,4 +1,5 @@ id . $name; + if (Cache::has($fullName)) { + Cache::forget($fullName); + } + /** @var Preference $preference */ + $preference = Preference::where('user_id', Auth::user()->id)->where('name', $name)->first(); + $preference->delete(); + + return true; + } + /** * @param string $name * @param string $default diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index bb2f126bbb..3a85e69577 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -1,4 +1,5 @@ accounts()->with('accounttype')->where( function (EloquentBuilder $q) use ($words) { @@ -38,7 +39,7 @@ class Search implements SearchInterface * * @return Collection */ - public function searchBudgets(array $words) + public function searchBudgets(array $words): Collection { /** @var Collection $set */ $set = Auth::user()->budgets()->get(); @@ -63,7 +64,7 @@ class Search implements SearchInterface * * @return Collection */ - public function searchCategories(array $words) + public function searchCategories(array $words): Collection { /** @var Collection $set */ $set = Auth::user()->categories()->get(); @@ -89,7 +90,7 @@ class Search implements SearchInterface * * @return Collection */ - public function searchTags(array $words) + public function searchTags(array $words): Collection { return new Collection; } @@ -99,19 +100,19 @@ class Search implements SearchInterface * * @return Collection */ - public function searchTransactions(array $words) + public function searchTransactions(array $words): Collection { // decrypted transaction journals: - $decrypted = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 0)->where( + $decrypted = Auth::user()->transactionjournals()->expanded()->where('encrypted', 0)->where( function (EloquentBuilder $q) use ($words) { foreach ($words as $word) { $q->orWhere('description', 'LIKE', '%' . e($word) . '%'); } } - )->get(); + )->get(TransactionJournal::QUERYFIELDS); // encrypted - $all = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 1)->get(); + $all = Auth::user()->transactionjournals()->expanded()->where('encrypted', 1)->get(TransactionJournal::QUERYFIELDS); $set = $all->filter( function (TransactionJournal $journal) use ($words) { foreach ($words as $word) { diff --git a/app/Support/Search/SearchInterface.php b/app/Support/Search/SearchInterface.php index 431275e8fe..c44c0d8285 100644 --- a/app/Support/Search/SearchInterface.php +++ b/app/Support/Search/SearchInterface.php @@ -1,4 +1,5 @@ get(); // @codeCoverageIgnore } - bcscale(2); - - $balance = $account->transactions()->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount'); + $balance = strval( + $account->transactions()->leftJoin( + 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' + )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount') + ); if (!$ignoreVirtualBalance) { $balance = bcadd($balance, $account->virtual_balance); } - $cache->store(round($balance, 2)); + $cache->store($balance); - return round($balance, 2); + return $balance; } /** @@ -87,7 +88,7 @@ class Steam ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) ->groupBy('transaction_journals.date') - ->get(['transaction_journals.date', DB::Raw('SUM(`transactions`.`amount`) as `modified`')]); + ->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `modified`')]); $currentBalance = $startBalance; foreach ($set as $entry) { $currentBalance = bcadd($currentBalance, $entry->modified); @@ -102,6 +103,7 @@ class Steam } /** + * This method always ignores the virtual balance. * * @param array $ids * @param \Carbon\Carbon $date @@ -120,19 +122,17 @@ class Steam return $cache->get(); // @codeCoverageIgnore } - bcscale(2); - $balances = Transaction:: leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->where('transaction_journals.date', '<=', $date->format('Y-m-d')) ->groupBy('transactions.account_id') ->whereIn('transactions.account_id', $ids) - ->get(['transactions.account_id', DB::Raw('sum(`transactions`.`amount`) as aggregate')]); + ->get(['transactions.account_id', DB::raw('sum(`transactions`.`amount`) as aggregate')]); $result = []; foreach ($balances as $entry) { $accountId = intval($entry->account_id); - $balance = round($entry->aggregate, 2); + $balance = $entry->aggregate; $result[$accountId] = $balance; } @@ -176,14 +176,14 @@ class Steam if (!(strpos($string, 'k') === false)) { // has a K in it, remove the K and multiply by 1024. - $bytes = bcmul(rtrim($string, 'k'), 1024); + $bytes = bcmul(rtrim($string, 'k'), '1024'); return intval($bytes); } if (!(strpos($string, 'm') === false)) { // has a M in it, remove the M and multiply by 1048576. - $bytes = bcmul(rtrim($string, 'm'), 1048576); + $bytes = bcmul(rtrim($string, 'm'), '1048576'); return intval($bytes); } diff --git a/app/Support/Twig/Budget.php b/app/Support/Twig/Budget.php index a67313513c..d3a1d0cd89 100644 --- a/app/Support/Twig/Budget.php +++ b/app/Support/Twig/Budget.php @@ -1,4 +1,5 @@ formatAmount(), @@ -44,7 +45,7 @@ class General extends Twig_Extension /** * {@inheritDoc} */ - public function getFunctions() + public function getFunctions(): array { return [ $this->getCurrencyCode(), @@ -62,19 +63,141 @@ class General extends Twig_Extension /** * {@inheritDoc} */ - public function getName() + public function getName(): string { return 'FireflyIII\Support\Twig\General'; } + /** + * Will return "active" when a part of the route matches the argument. + * ie. "accounts" will match "accounts.index". + * + * @return Twig_SimpleFunction + */ + protected function activeRoutePartial(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'activeRoutePartial', function () : string { + $args = func_get_args(); + $route = $args[0]; // name of the route. + if (!(strpos(Route::getCurrentRoute()->getName(), $route) === false)) { + return 'active'; + } + + return ''; + } + ); + } + + /** + * This function will return "active" when the current route matches the first argument (even partly) + * but, the variable $what has been set and matches the second argument. + * + * @return Twig_SimpleFunction + */ + protected function activeRoutePartialWhat(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'activeRoutePartialWhat', function ($context) : string { + $args = func_get_args(); + $route = $args[1]; // name of the route. + $what = $args[2]; // name of the route. + $activeWhat = $context['what'] ?? false; + + if ($what == $activeWhat && !(strpos(Route::getCurrentRoute()->getName(), $route) === false)) { + return 'active'; + } + + return ''; + }, ['needs_context' => true] + ); + } + + /** + * Will return "active" when the current route matches the given argument + * exactly. + * + * @return Twig_SimpleFunction + */ + protected function activeRouteStrict(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'activeRouteStrict', function () : string { + $args = func_get_args(); + $route = $args[0]; // name of the route. + + if (Route::getCurrentRoute()->getName() == $route) { + return 'active'; + } + + return ''; + } + ); + } + /** * @return Twig_SimpleFilter */ - protected function formatFilesize() + protected function balance(): Twig_SimpleFilter { return new Twig_SimpleFilter( - 'filesize', function ($size) { - $size = intval($size); + 'balance', function (Account $account = null) : string { + if (is_null($account)) { + return 'NULL'; + } + $date = session('end', Carbon::now()->endOfMonth()); + + return app('steam')->balance($account, $date); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function env(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'env', function (string $name, string $default) : string { + return env($name, $default); + } + ); + } + + /** + * + * @return Twig_SimpleFilter + */ + protected function formatAmount(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'formatAmount', function (string $string) : string { + + return app('amount')->format($string); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function formatAmountPlain(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'formatAmountPlain', function (string $string) : string { + + return app('amount')->format($string, false); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function formatFilesize(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'filesize', function (int $size) : string { // less than one GB, more than one MB if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) { @@ -94,10 +217,70 @@ class General extends Twig_Extension /** * @return Twig_SimpleFilter */ - protected function mimeIcon() + protected function formatJournal(): Twig_SimpleFilter { return new Twig_SimpleFilter( - 'mimeIcon', function ($string) { + 'formatJournal', function (TransactionJournal $journal) : string { + return app('amount')->formatJournal($journal); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function formatTransaction(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'formatTransaction', function (Transaction $transaction) : string { + return app('amount')->formatTransaction($transaction); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function getAccountRole(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'getAccountRole', function (string $name) : string { + return Config::get('firefly.accountRoles.' . $name); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function getCurrencyCode(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'getCurrencyCode', function () : string { + return app('amount')->getCurrencyCode(); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function getCurrencySymbol(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'getCurrencySymbol', function () : string { + return app('amount')->getCurrencySymbol(); + } + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function mimeIcon(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'mimeIcon', function (string $string) : string { switch ($string) { default: return 'fa-file-o'; @@ -111,196 +294,16 @@ class General extends Twig_Extension ); } - /** - * @return Twig_SimpleFilter - */ - protected function formatAmount() - { - return new Twig_SimpleFilter( - 'formatAmount', function ($string) { - return app('amount')->format($string); - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function formatTransaction() - { - return new Twig_SimpleFilter( - 'formatTransaction', function (Transaction $transaction) { - return app('amount')->formatTransaction($transaction); - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function formatAmountPlain() - { - return new Twig_SimpleFilter( - 'formatAmountPlain', function ($string) { - return app('amount')->format($string, false); - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function formatJournal() - { - return new Twig_SimpleFilter( - 'formatJournal', function ($journal) { - return app('amount')->formatJournal($journal); - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function balance() - { - return new Twig_SimpleFilter( - 'balance', function (Account $account = null) { - if (is_null($account)) { - return 'NULL'; - } - $date = Session::get('end', Carbon::now()->endOfMonth()); - - return app('steam')->balance($account, $date); - } - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function getAccountRole() - { - return new Twig_SimpleFilter( - 'getAccountRole', function ($name) { - return Config::get('firefly.accountRoles.' . $name); - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - protected function getCurrencyCode() - { - return new Twig_SimpleFunction( - 'getCurrencyCode', function () { - return app('amount')->getCurrencyCode(); - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - protected function getCurrencySymbol() - { - return new Twig_SimpleFunction( - 'getCurrencySymbol', function () { - return app('amount')->getCurrencySymbol(); - } - ); - } - /** * @return Twig_SimpleFunction */ protected function phpdate() { return new Twig_SimpleFunction( - 'phpdate', function ($str) { + 'phpdate', function (string $str) : string { return date($str); } ); } - /** - * @return Twig_SimpleFunction - */ - protected function env() - { - return new Twig_SimpleFunction( - 'env', function ($name, $default) { - return env($name, $default); - } - ); - } - - /** - * Will return "active" when the current route matches the given argument - * exactly. - * - * @return Twig_SimpleFunction - */ - protected function activeRouteStrict() - { - return new Twig_SimpleFunction( - 'activeRouteStrict', function () { - $args = func_get_args(); - $route = $args[0]; // name of the route. - - if (Route::getCurrentRoute()->getName() == $route) { - return 'active'; - } - - return ''; - } - ); - } - - /** - * Will return "active" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - * - * @return Twig_SimpleFunction - */ - protected function activeRoutePartial() - { - return new Twig_SimpleFunction( - 'activeRoutePartial', function () { - $args = func_get_args(); - $route = $args[0]; // name of the route. - if (!(strpos(Route::getCurrentRoute()->getName(), $route) === false)) { - return 'active'; - } - - return ''; - } - ); - } - - /** - * This function will return "active" when the current route matches the first argument (even partly) - * but, the variable $what has been set and matches the second argument. - * - * @return Twig_SimpleFunction - */ - protected function activeRoutePartialWhat() - { - return new Twig_SimpleFunction( - 'activeRoutePartialWhat', function ($context) { - $args = func_get_args(); - $route = $args[1]; // name of the route. - $what = $args[2]; // name of the route. - $activeWhat = isset($context['what']) ? $context['what'] : false; - - if ($what == $activeWhat && !(strpos(Route::getCurrentRoute()->getName(), $route) === false)) { - return 'active'; - } - - return ''; - }, ['needs_context' => true] - ); - } - } diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 29b9c2d31f..3218a5f843 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -1,18 +1,15 @@ typeIcon()]; @@ -33,11 +30,10 @@ class Journal extends Twig_Extension /** * @return array */ - public function getFunctions() + public function getFunctions(): array { $functions = [ $this->invalidJournal(), - $this->relevantTags(), ]; return $functions; @@ -48,25 +44,34 @@ class Journal extends Twig_Extension * * @return string The extension name */ - public function getName() + public function getName(): string { return 'FireflyIII\Support\Twig\Journals'; } + /** + * @return Twig_SimpleFunction + */ + protected function invalidJournal(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'invalidJournal', function (TransactionJournal $journal): bool { + if (!isset($journal->transactions[1]) || !isset($journal->transactions[0])) { + return true; + } + + return false; + } + ); + } + /** * @return Twig_SimpleFilter */ - protected function typeIcon() + protected function typeIcon(): Twig_SimpleFilter { return new Twig_SimpleFilter( - 'typeIcon', function (TransactionJournal $journal) { - - $cache = new CacheProperties(); - $cache->addProperty($journal->id); - $cache->addProperty('typeIcon'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } + 'typeIcon', function (TransactionJournal $journal): string { switch (true) { case $journal->isWithdrawal(): @@ -85,157 +90,9 @@ class Journal extends Twig_Extension $txt = ''; break; } - $cache->store($txt); return $txt; - - }, ['is_safe' => ['html']] ); } - - /** - * @return Twig_SimpleFunction - */ - protected function invalidJournal() - { - return new Twig_SimpleFunction( - 'invalidJournal', function (TransactionJournal $journal) { - if (!isset($journal->transactions[1]) || !isset($journal->transactions[0])) { - return true; - } - - return false; - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - protected function relevantTags() - { - return new Twig_SimpleFunction( - 'relevantTags', function (TransactionJournal $journal) { - $cache = new CacheProperties; - $cache->addProperty('relevantTags'); - $cache->addProperty($journal->id); - - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - $count = $journal->tags->count(); - $string = ''; - - if ($count === 0) { - $string = $this->relevantTagsNoTags($journal); - } - - if ($count === 1) { - $string = $this->relevantTagsSingle($journal); - } - - if ($count > 1) { - $string = $this->relevantTagsMulti($journal); - } - - $cache->store($string); - - return $string; - } - ); - } - - /** - * @param TransactionJournal $journal - * - * @return string - */ - protected function relevantTagsNoTags(TransactionJournal $journal) - { - return app('amount')->formatJournal($journal); - } - - /** - * @param TransactionJournal $journal - * - * @return string - */ - protected function relevantTagsSingle(TransactionJournal $journal) - { - $tag = $journal->tags()->first(); - - return $this->formatJournalByTag($journal, $tag); - } - - /** - * @param TransactionJournal $journal - * @param Tag $tag - * - * @return string - */ - protected function formatJournalByTag(TransactionJournal $journal, Tag $tag) - { - if ($tag->tagMode == 'balancingAct') { - // return tag formatted for a "balancing act", even if other - // tags are present. - $amount = app('amount')->format($journal->amount_positive, false); - $string = ' ' . $tag->tag . ''; - - return $string; - } - - if ($tag->tagMode == 'advancePayment') { - if ($journal->isDeposit()) { - $amount = app('amount')->formatJournal($journal, false); - $string = ' ' . $tag->tag . ''; - - return $string; - } - - /* - * AdvancePayment with a withdrawal will show the amount with a link to - * the tag. The TransactionJournal should properly calculate the amount. - */ - if ($journal->isWithdrawal()) { - $amount = app('amount')->formatJournal($journal); - - $string = '' . $amount . ''; - - return $string; - } - } - - - return $this->relevantTagsNoTags($journal); - } - - /** - * If a transaction journal has multiple tags, we'll have to gamble. FF3 - * does not yet block adding multiple 'special' tags so we must wing it. - * - * We grab the first special tag (for advancePayment and for balancingAct - * and try to format those. If they're not present (it's all normal tags), - * we can format like any other journal. - * - * @param TransactionJournal $journal - * - * @return string - */ - protected function relevantTagsMulti(TransactionJournal $journal) - { - $firstBalancingAct = $journal->tags()->where('tagMode', 'balancingAct')->first(); - if ($firstBalancingAct) { - return $this->formatJournalByTag($journal, $firstBalancingAct); - } - - $firstAdvancePayment = $journal->tags()->where('tagMode', 'advancePayment')->first(); - if ($firstAdvancePayment) { - return $this->formatJournalByTag($journal, $firstAdvancePayment); - } - - return $this->relevantTagsNoTags($journal); - } } diff --git a/app/Support/Twig/PiggyBank.php b/app/Support/Twig/PiggyBank.php index 9ea1ff759b..dd5d341ffa 100644 --- a/app/Support/Twig/PiggyBank.php +++ b/app/Support/Twig/PiggyBank.php @@ -1,4 +1,5 @@ hasMany('FireflyIII\Models\Account'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * Alias to eloquent many-to-many relation's attach() method. + * + * Full credit goes to: https://github.com/Zizaco/entrust + * + * @param mixed $role */ - public function attachments() + public function attachRole($role) + { + if (is_object($role)) { + $role = $role->getKey(); + } + + if (is_array($role)) { + $role = $role['id']; + } + + $this->roles()->attach($role); + } + + /** + * @return HasMany + */ + public function attachments(): HasMany { return $this->hasMany('FireflyIII\Models\Attachment'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function bills() + public function bills(): HasMany { return $this->hasMany('FireflyIII\Models\Bill'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function budgets() + public function budgets(): HasMany { return $this->hasMany('FireflyIII\Models\Budget'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function categories() + public function categories(): HasMany { return $this->hasMany('FireflyIII\Models\Category'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return HasMany */ - public function piggyBanks() + public function exportjobs(): HasMany + { + return $this->hasMany('FireflyIII\Models\ExportJob'); + } + + /** + * Checks if the user has a role by its name. + * + * Full credit goes to: https://github.com/Zizaco/entrust + * + * @param string $name + * + * @return bool + */ + public function hasRole(string $name): bool + { + + foreach ($this->roles as $role) { + if ($role->name == $name) { + return true; + } + } + + return false; + } + + /** + * @return HasManyThrough + */ + public function piggyBanks(): HasManyThrough { return $this->hasManyThrough('FireflyIII\Models\PiggyBank', 'FireflyIII\Models\Account'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function preferences() + public function preferences(): HasMany { return $this->hasMany('FireflyIII\Models\Preference'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function ruleGroups() + public function roles(): BelongsToMany + { + return $this->belongsToMany('FireflyIII\Models\Role'); + } + + /** + * @return HasMany + */ + public function ruleGroups(): HasMany { return $this->hasMany('FireflyIII\Models\RuleGroup'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function rules() + public function rules(): HasMany { return $this->hasMany('FireflyIII\Models\Rule'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function tags() + public function tags(): HasMany { return $this->hasMany('FireflyIII\Models\Tag'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function transactionjournals() + public function transactionjournals(): HasMany { return $this->hasMany('FireflyIII\Models\TransactionJournal'); } /** - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + * @return HasManyThrough */ - public function transactions() + public function transactions(): HasManyThrough { return $this->hasManyThrough('FireflyIII\Models\Transaction', 'FireflyIII\Models\TransactionJournal'); } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 5ee847bdf3..1a14187fa3 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -1,4 +1,5 @@ 6) { + return false; + } - $count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where('id', $value)->count(); + $secret = Session::get('two-factor-secret'); + $google2fa = app('PragmaRX\Google2FA\Google2FA'); + + return $google2fa->verifyKey($secret, $value); + } + + /** + * @param $attribute + * @param $value + * @param $parameters + * + * @return bool + */ + public function validateBelongsToUser($attribute, $value, $parameters): bool + { + $field = $parameters[1] ?? 'id'; + + + $count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where($field, $value)->count(); if ($count == 1) { return true; } @@ -67,7 +94,7 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateIban($attribute, $value) + public function validateIban($attribute, $value): bool { if (!is_string($value) || is_null($value) || strlen($value) < 6) { return false; @@ -94,7 +121,7 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateRuleActionValue($attribute) + public function validateRuleActionValue($attribute): bool { // get the index from a string like "rule-action-value.2". $parts = explode('.', $attribute); @@ -103,8 +130,8 @@ class FireflyValidator extends Validator // check if rule-action-value matches the thing. if (is_array($this->data['rule-action'])) { - $name = isset($this->data['rule-action'][$index]) ? $this->data['rule-action'][$index] : 'invalid'; - $value = isset($this->data['rule-action-value'][$index]) ? $this->data['rule-action-value'][$index] : false; + $name = $this->data['rule-action'][$index] ?? 'invalid'; + $value = $this->data['rule-action-value'][$index] ?? false; switch ($name) { default: Log::debug(' (' . $attribute . ') (index:' . $index . ') Name is "' . $name . '" so no action is taken.'); @@ -136,7 +163,7 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateRuleTriggerValue($attribute) + public function validateRuleTriggerValue($attribute): bool { // get the index from a string like "rule-trigger-value.2". $parts = explode('.', $attribute); @@ -147,18 +174,34 @@ class FireflyValidator extends Validator if (is_array($this->data['rule-trigger'])) { $name = $this->getRuleTriggerName($index); $value = $this->getRuleTriggerValue($index); + + // break on some easy checks: switch ($name) { - default: - return true; case 'amount_less': - return is_numeric($value); + $result = is_numeric($value); + if ($result === false) { + return false; + } + break; case 'transaction_type': $count = TransactionType::where('type', $value)->count(); - - return $count === 1; + if (!($count === 1)) { + return false; + } + break; case 'invalid': return false; } + // still a special case where the trigger is + // triggered in such a way that it would trigger ANYTHING. We can check for such things + // with function willmatcheverything + // we know which class it is so dont bother checking that. + $classes = Config::get('firefly.rule-triggers'); + /** @var TriggerInterface $class */ + $class = $classes[$name]; + + return !($class::willMatchEverything($value)); + } return false; @@ -173,7 +216,7 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateUniqueAccountForUser($attribute, $value, $parameters) + public function validateUniqueAccountForUser($attribute, $value, $parameters): bool { // because a user does not have to be logged in (tests and what-not). if (!Auth::check()) { @@ -195,6 +238,40 @@ class FireflyValidator extends Validator return false; } + /** + * @param $attribute + * @param $value + * @param $parameters + * + * @return bool + */ + public function validateUniqueAccountNumberForUser($attribute, $value, $parameters): bool + { + $accountId = $this->data['id'] ?? 0; + + $query = AccountMeta:: + leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') + ->where('accounts.user_id', Auth::user()->id) + ->where('account_meta.name', 'accountNumber'); + + if (intval($accountId) > 0) { + // exclude current account from check. + $query->where('account_meta.account_id', '!=', intval($accountId)); + } + $set = $query->get(['account_meta.*']); + + /** @var AccountMeta $entry */ + foreach ($set as $entry) { + if ($entry->data == $value) { + + return false; + } + } + + return true; + + } + /** * @param $attribute * @param $value @@ -204,7 +281,7 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateUniqueForUser($attribute, $value, $parameters) + public function validateUniqueForUser($attribute, $value, $parameters): bool { $query = DB::table($parameters[0])->where($parameters[1], $value); $query->where('user_id', Auth::user()->id); @@ -235,13 +312,13 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateUniqueObjectForUser($attribute, $value, $parameters) + public function validateUniqueObjectForUser($attribute, $value, $parameters): bool { $value = $this->tryDecrypt($value); // exclude? $table = $parameters[0]; $field = $parameters[1]; - $exclude = isset($parameters[2]) ? intval($parameters[2]) : 0; + $exclude = $parameters[2] ?? 0; // get entries from table $set = DB::table($table)->where('user_id', Auth::user()->id) @@ -268,9 +345,9 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateUniquePiggyBankForUser($attribute, $value, $parameters) + public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool { - $exclude = isset($parameters[0]) ? $parameters[0] : null; + $exclude = $parameters[0] ?? null; $query = DB::table('piggy_banks')->whereNull('piggy_banks.deleted_at') ->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', Auth::user()->id); if (!is_null($exclude)) { @@ -289,12 +366,33 @@ class FireflyValidator extends Validator return true; } + /** + * @param int $index + * + * @return string + */ + private function getRuleTriggerName($index): string + { + return $this->data['rule-trigger'][$index] ?? 'invalid'; + + } + + /** + * @param int $index + * + * @return string + */ + private function getRuleTriggerValue($index): string + { + return $this->data['rule-trigger-value'][$index] ?? ''; + } + /** * @param $value * * @return mixed */ - protected function tryDecrypt($value) + private function tryDecrypt($value) { try { $value = Crypt::decrypt($value); @@ -308,7 +406,7 @@ class FireflyValidator extends Validator /** * @return bool */ - protected function validateAccountAnonymously() + private function validateAccountAnonymously(): bool { if (!isset($this->data['user_id'])) { return false; @@ -337,7 +435,7 @@ class FireflyValidator extends Validator * @internal param $parameters * */ - protected function validateByAccountId($value) + private function validateByAccountId($value): bool { /** @var Account $existingAccount */ $existingAccount = Account::find($this->data['id']); @@ -364,10 +462,10 @@ class FireflyValidator extends Validator * * @return bool */ - protected function validateByAccountTypeId($value, $parameters) + private function validateByAccountTypeId($value, $parameters): bool { $type = AccountType::find($this->data['account_type_id'])->first(); - $ignore = isset($parameters[0]) ? intval($parameters[0]) : 0; + $ignore = $parameters[0] ?? 0; $value = $this->tryDecrypt($value); $set = Auth::user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get(); @@ -388,11 +486,11 @@ class FireflyValidator extends Validator * * @return bool */ - protected function validateByAccountTypeString($value, $parameters) + private function validateByAccountTypeString($value, $parameters): bool { $search = Config::get('firefly.accountTypeByIdentifier.' . $this->data['what']); $type = AccountType::whereType($search)->first(); - $ignore = isset($parameters[0]) ? intval($parameters[0]) : 0; + $ignore = $parameters[0] ?? 0; $set = Auth::user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get(); /** @var Account $entry */ @@ -404,26 +502,5 @@ class FireflyValidator extends Validator return true; } - - /** - * @param int $index - * - * @return string - */ - private function getRuleTriggerName($index) - { - return isset($this->data['rule-trigger'][$index]) ? $this->data['rule-trigger'][$index] : 'invalid'; - - } - - /** - * @param int $index - * - * @return string - */ - private function getRuleTriggerValue($index) - { - return isset($this->data['rule-trigger-value'][$index]) ? $this->data['rule-trigger-value'][$index] : ''; - } } diff --git a/bootstrap/app.php b/bootstrap/app.php index ebd712abe1..500021fb9a 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,6 +11,9 @@ | */ +bcscale(4); + + $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); @@ -26,6 +29,7 @@ $app = new Illuminate\Foundation\Application( | */ + $app->singleton( Illuminate\Contracts\Http\Kernel::class, FireflyIII\Http\Kernel::class diff --git a/composer.json b/composer.json index 7dbfee8eb0..98a2dd3ee7 100644 --- a/composer.json +++ b/composer.json @@ -20,18 +20,21 @@ "doctrine/dbal": "~2.5", "league/commonmark": "~0.7", "rcrowe/twigbridge": "~0.9", - "zizaco/entrust": "dev-laravel-5", "league/csv": "^7.1", - "laravelcollective/html": "^5.2" + "laravelcollective/html": "^5.2", + "rmccue/requests": "^1.6", + "pragmarx/google2fa": "^0.7.1" }, "require-dev": { "fzaninotto/faker": "~1.4", - "mockery/mockery": "0.9.*", + "mockery/mockery": "dev-master", "phpunit/phpunit": "~4.0", "symfony/css-selector": "2.8.*|3.0.*", "symfony/dom-crawler": "2.8.*|3.0.*", "barryvdh/laravel-debugbar": "@stable", - "barryvdh/laravel-ide-helper": "~2.0" + "barryvdh/laravel-ide-helper": "~2.0", + "johnkary/phpunit-speedtrap": "^1.0", + "hamcrest/hamcrest-php": "^2.0@dev" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 5f29ed5b9e..dad174cd40 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,105 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "df00127da416acad2a36b6128b82fdd9", - "content-hash": "28b178f07a713b4db441e7e1f380916e", + "hash": "c1638ed0abc18262bab1f791b48af111", + "content-hash": "3551da50dc493828d3ae5d73c10984f0", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "031a2ce68c5794064b49d11775b2daf45c96e21c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/031a2ce68c5794064b49d11775b2daf45c96e21c", + "reference": "031a2ce68c5794064b49d11775b2daf45c96e21c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2016-01-09 22:55:35" + }, + { + "name": "christian-riesen/base32", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/ChristianRiesen/base32.git", + "reference": "fbe67d49d45dc789f942ef828c787550ebb894bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/fbe67d49d45dc789f942ef828c787550ebb894bc", + "reference": "fbe67d49d45dc789f942ef828c787550ebb894bc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "0.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Base32\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "description": "Base32 encoder/decoder according to RFC 4648", + "homepage": "https://github.com/ChristianRiesen/base32", + "keywords": [ + "base32", + "decode", + "encode", + "rfc4648" + ], + "time": "2015-09-27 23:45:02" + }, { "name": "classpreloader/classpreloader", "version": "3.0.0", @@ -759,16 +855,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.12", + "version": "v5.2.23", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7" + "reference": "87c090845f135ca94eba903f1c8462e60e3a6e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", + "url": "https://api.github.com/repos/laravel/framework/zipball/87c090845f135ca94eba903f1c8462e60e3a6e36", + "reference": "87c090845f135ca94eba903f1c8462e60e3a6e36", "shasum": "" }, "require": { @@ -783,7 +879,7 @@ "nesbot/carbon": "~1.20", "paragonie/random_compat": "~1.1", "php": ">=5.5.9", - "psy/psysh": "0.6.*", + "psy/psysh": "0.7.*", "swiftmailer/swiftmailer": "~5.1", "symfony/console": "2.8.*|3.0.*", "symfony/debug": "2.8.*|3.0.*", @@ -883,7 +979,7 @@ "framework", "laravel" ], - "time": "2016-01-26 04:15:37" + "time": "2016-03-14 14:22:33" }, { "name": "laravelcollective/html", @@ -941,16 +1037,16 @@ }, { "name": "league/commonmark", - "version": "0.13.0", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "a4e93bc4fd1a8ff8f534040c4a07371ea5f4b484" + "reference": "2c10de455649e3a544d45016d9df457248bcd37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a4e93bc4fd1a8ff8f534040c4a07371ea5f4b484", - "reference": "a4e93bc4fd1a8ff8f534040c4a07371ea5f4b484", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/2c10de455649e3a544d45016d9df457248bcd37f", + "reference": "2c10de455649e3a544d45016d9df457248bcd37f", "shasum": "" }, "require": { @@ -964,10 +1060,10 @@ "erusev/parsedown": "~1.0", "jgm/commonmark": "0.24", "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "~1.1.0", + "mikehaertl/php-shellcommand": "~1.2.0", "phpunit/phpunit": "~4.3|~5.0", - "scrutinizer/ocular": "^1.1", - "symfony/finder": "~2.3" + "scrutinizer/ocular": "~1.1", + "symfony/finder": "~2.3|~3.0" }, "suggest": { "league/commonmark-extras": "Library of useful extensions including smart punctuation" @@ -1005,7 +1101,7 @@ "markdown", "parser" ], - "time": "2016-01-14 04:29:54" + "time": "2016-03-09 15:20:24" }, { "name": "league/csv", @@ -1066,16 +1162,16 @@ }, { "name": "league/flysystem", - "version": "1.0.16", + "version": "1.0.20", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", "shasum": "" }, "require": { @@ -1088,8 +1184,7 @@ "ext-fileinfo": "*", "mockery/mockery": "~0.9", "phpspec/phpspec": "^2.2", - "phpspec/prophecy-phpunit": "~1.0", - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "~4.8 || ~5.0" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -1146,20 +1241,20 @@ "sftp", "storage" ], - "time": "2015-12-19 20:16:43" + "time": "2016-03-14 21:54:11" }, { "name": "monolog/monolog", - "version": "1.17.2", + "version": "1.18.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" + "reference": "a5f2734e8c16f3aa21b3da09715d10e15b4d2d45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a5f2734e8c16f3aa21b3da09715d10e15b4d2d45", + "reference": "a5f2734e8c16f3aa21b3da09715d10e15b4d2d45", "shasum": "" }, "require": { @@ -1188,6 +1283,7 @@ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongo": "Allow sending log messages to a MongoDB server", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", "php-console/php-console": "Allow sending log messages to Google Chrome", "raven/raven": "Allow sending log messages to a Sentry server", "rollbar/rollbar": "Allow sending log messages to Rollbar", @@ -1197,7 +1293,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.16.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1223,7 +1319,7 @@ "logging", "psr-3" ], - "time": "2015-10-14 12:51:02" + "time": "2016-03-13 16:08:35" }, { "name": "mtdowling/cron-expression", @@ -1318,16 +1414,16 @@ }, { "name": "nikic/php-parser", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01" + "reference": "ce5be709d59b32dd8a88c80259028759991a4206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c542e5d86a9775abd1021618eb2430278bfc1e01", - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce5be709d59b32dd8a88c80259028759991a4206", + "reference": "ce5be709d59b32dd8a88c80259028759991a4206", "shasum": "" }, "require": { @@ -1365,20 +1461,20 @@ "parser", "php" ], - "time": "2015-12-04 15:28:43" + "time": "2016-02-28 19:48:28" }, { "name": "paragonie/random_compat", - "version": "1.1.6", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0" + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/e6f80ab77885151908d0ec743689ca700886e8b0", - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774", + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774", "shasum": "" }, "require": { @@ -1413,7 +1509,61 @@ "pseudorandom", "random" ], - "time": "2016-01-29 16:19:52" + "time": "2016-03-18 20:34:03" + }, + { + "name": "pragmarx/google2fa", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "908678ba9b26cf8ecd7ddca6bfd86afc5b4874df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/908678ba9b26cf8ecd7ddca6bfd86afc5b4874df", + "reference": "908678ba9b26cf8ecd7ddca6bfd86afc5b4874df", + "shasum": "" + }, + "require": { + "christian-riesen/base32": "~1.0", + "php": ">=5.3.7", + "simplesoftwareio/simple-qrcode": "1.3.*" + }, + "require-dev": { + "phpspec/phpspec": "~2.1" + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ] + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2015-11-07 13:57:42" }, { "name": "psr/log", @@ -1455,16 +1605,16 @@ }, { "name": "psy/psysh", - "version": "v0.6.1", + "version": "v0.7.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed" + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0f04df0b23663799a8941fae13cd8e6299bde3ed", - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", "shasum": "" }, "require": { @@ -1493,7 +1643,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.7.x-dev" + "dev-develop": "0.8.x-dev" } }, "autoload": { @@ -1523,20 +1673,20 @@ "interactive", "shell" ], - "time": "2015-11-12 16:18:56" + "time": "2016-03-09 05:03:14" }, { "name": "rcrowe/twigbridge", - "version": "v0.9.1", + "version": "v0.9.2", "source": { "type": "git", "url": "https://github.com/rcrowe/TwigBridge.git", - "reference": "634c4d12fc3dc633d202341b7a29a107f77a67da" + "reference": "78a4b7da75042660258a3269751c259eee81c34a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/634c4d12fc3dc633d202341b7a29a107f77a67da", - "reference": "634c4d12fc3dc633d202341b7a29a107f77a67da", + "url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/78a4b7da75042660258a3269751c259eee81c34a", + "reference": "78a4b7da75042660258a3269751c259eee81c34a", "shasum": "" }, "require": { @@ -1587,7 +1737,107 @@ "laravel", "twig" ], - "time": "2015-12-21 22:03:34" + "time": "2016-02-22 07:48:48" + }, + { + "name": "rmccue/requests", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/rmccue/Requests.git", + "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea", + "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/rmccue/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2014-05-18 04:59:02" + }, + { + "name": "simplesoftwareio/simple-qrcode", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git", + "reference": "17c5e45c79c40f717d4bc08cf5e568f29ebf9333" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/17c5e45c79c40f717d4bc08cf5e568f29ebf9333", + "reference": "17c5e45c79c40f717d4bc08cf5e568f29ebf9333", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "1.0.*", + "ext-gd": "*", + "illuminate/support": ">=4.2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "SimpleSoftwareIO\\QrCode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simple Software LLC", + "email": "support@simplesoftware.io" + } + ], + "description": "Simple QrCode is a QR code generator made for Laravel.", + "homepage": "http://www.simplesoftware.io", + "keywords": [ + "Simple", + "generator", + "laravel", + "qrcode", + "wrapper" + ], + "time": "2016-01-31 02:09:25" }, { "name": "swiftmailer/swiftmailer", @@ -1644,16 +1894,16 @@ }, { "name": "symfony/console", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" + "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", + "url": "https://api.github.com/repos/symfony/console/zipball/2ed5e2706ce92313d120b8fe50d1063bcfd12e04", + "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04", "shasum": "" }, "require": { @@ -1700,20 +1950,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-12-22 10:39:06" + "time": "2016-02-28 16:24:34" }, { "name": "symfony/debug", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a" + "reference": "29606049ced1ec715475f88d1bbe587252a3476e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/73612266ac709769effdbfc0762e5b07cfd2ac2a", - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a", + "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e", + "reference": "29606049ced1ec715475f88d1bbe587252a3476e", "shasum": "" }, "require": { @@ -1757,20 +2007,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:39:53" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/event-dispatcher", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", "shasum": "" }, "require": { @@ -1817,20 +2067,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 23:35:59" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/finder", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8617895eb798b6bdb338321ce19453dc113e5675" + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", - "reference": "8617895eb798b6bdb338321ce19453dc113e5675", + "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", "shasum": "" }, "require": { @@ -1866,20 +2116,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-12-05 11:13:14" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/http-foundation", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5" + "reference": "52065702c71743c05d415a8facfcad6d4257e8d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/52065702c71743c05d415a8facfcad6d4257e8d7", + "reference": "52065702c71743c05d415a8facfcad6d4257e8d7", "shasum": "" }, "require": { @@ -1918,20 +2168,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-12-18 15:43:53" + "time": "2016-02-28 16:24:34" }, { "name": "symfony/http-kernel", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db" + "reference": "59c0a1972e9aad87b7a56bbe1ccee26b7535a0db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f7933e9f19e26e7baba7ec04735b466fedd3a6db", - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/59c0a1972e9aad87b7a56bbe1ccee26b7535a0db", + "reference": "59c0a1972e9aad87b7a56bbe1ccee26b7535a0db", "shasum": "" }, "require": { @@ -2000,11 +2250,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-12-26 16:46:13" + "time": "2016-02-28 21:33:13" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2063,7 +2313,7 @@ }, { "name": "symfony/polyfill-php56", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", @@ -2119,7 +2369,7 @@ }, { "name": "symfony/polyfill-util", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", @@ -2171,16 +2421,16 @@ }, { "name": "symfony/process", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3" + "reference": "dfecef47506179db2501430e732adbf3793099c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f4794f1d00f0746621be3020ffbd8c5e0b217ee3", - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3", + "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8", + "reference": "dfecef47506179db2501430e732adbf3793099c8", "shasum": "" }, "require": { @@ -2216,20 +2466,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-12-23 11:04:02" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/routing", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59" + "reference": "fa1e9a8173cf0077dd995205da453eacd758fdf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", + "url": "https://api.github.com/repos/symfony/routing/zipball/fa1e9a8173cf0077dd995205da453eacd758fdf6", + "reference": "fa1e9a8173cf0077dd995205da453eacd758fdf6", "shasum": "" }, "require": { @@ -2252,6 +2502,7 @@ "symfony/config": "For using the all-in-one router or any loader", "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" }, "type": "library", @@ -2290,20 +2541,20 @@ "uri", "url" ], - "time": "2015-12-23 08:00:11" + "time": "2016-02-04 13:53:13" }, { "name": "symfony/translation", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dff0867826a7068d673801b7522f8e2634016ef9" + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dff0867826a7068d673801b7522f8e2634016ef9", - "reference": "dff0867826a7068d673801b7522f8e2634016ef9", + "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", "shasum": "" }, "require": { @@ -2354,20 +2605,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:45:07" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/var-dumper", - "version": "v3.0.1", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0" + "reference": "9a6a883c48acb215d4825ce9de61dccf93d62074" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/87db8700deb12ba2b65e858f656a1f885530bcb0", - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a6a883c48acb215d4825ce9de61dccf93d62074", + "reference": "9a6a883c48acb215d4825ce9de61dccf93d62074", "shasum": "" }, "require": { @@ -2417,7 +2668,7 @@ "debug", "dump" ], - "time": "2015-12-05 11:13:14" + "time": "2016-02-13 09:23:44" }, { "name": "twig/twig", @@ -2533,16 +2784,16 @@ }, { "name": "watson/validating", - "version": "2.0.1", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/dwightwatson/validating.git", - "reference": "85e31d1f62ebf9dd9b63cf30f7318f379cc36179" + "reference": "6acfec57bd480701b467570c4c08dcbfd6a9ea36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dwightwatson/validating/zipball/85e31d1f62ebf9dd9b63cf30f7318f379cc36179", - "reference": "85e31d1f62ebf9dd9b63cf30f7318f379cc36179", + "url": "https://api.github.com/repos/dwightwatson/validating/zipball/6acfec57bd480701b467570c4c08dcbfd6a9ea36", + "reference": "6acfec57bd480701b467570c4c08dcbfd6a9ea36", "shasum": "" }, "require": { @@ -2584,1535 +2835,16 @@ "laravel", "validation" ], - "time": "2016-01-27 00:10:53" - }, - { - "name": "zizaco/entrust", - "version": "dev-laravel-5", - "source": { - "type": "git", - "url": "https://github.com/Zizaco/entrust.git", - "reference": "be28aa634c44551ae5187894c903094a8ce8baee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Zizaco/entrust/zipball/be28aa634c44551ae5187894c903094a8ce8baee", - "reference": "be28aa634c44551ae5187894c903094a8ce8baee", - "shasum": "" - }, - "require": { - "illuminate/console": "~5.0", - "illuminate/support": "~5.0", - "php": ">=5.4.0" - }, - "require-dev": { - "illuminate/database": "~5.0", - "mockery/mockery": "dev-master", - "phpunit/phpunit": "~4.1", - "sami/sami": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/commands" - ], - "psr-4": { - "Zizaco\\Entrust\\": "src/Entrust/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Andrew Elkins", - "homepage": "http://andrewelkins.com" - }, - { - "name": "Zizaco Zizuini", - "email": "zizaco@gmail.com" - }, - { - "name": "Ben Batschelet", - "homepage": "http://github.com/bbatsche" - }, - { - "name": "Michele Angioni", - "email": "michele.angioni@gmail.com" - } - ], - "description": "This package provides a flexible way to add Role-based Permissions to Laravel", - "keywords": [ - "acl", - "auth", - "illuminate", - "laravel", - "permission", - "roles" - ], - "time": "2015-11-12 17:38:37" - } - ], - "packages-dev": [ - { - "name": "barryvdh/laravel-debugbar", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "974fd16e328ca851a081449100d9509af59cf0ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/974fd16e328ca851a081449100d9509af59cf0ff", - "reference": "974fd16e328ca851a081449100d9509af59cf0ff", - "shasum": "" - }, - "require": { - "illuminate/support": "~5.0.17|5.1.*|5.2.*", - "maximebf/debugbar": "~1.11.0", - "php": ">=5.4.0", - "symfony/finder": "~2.6|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2-dev" - } - }, - "autoload": { - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], - "time": "2015-12-22 06:22:38" - }, - { - "name": "barryvdh/laravel-ide-helper", - "version": "v2.1.2", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", - "shasum": "" - }, - "require": { - "illuminate/console": "5.0.x|5.1.x|5.2.x", - "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", - "illuminate/support": "5.0.x|5.1.x|5.2.x", - "php": ">=5.4.0", - "phpdocumentor/reflection-docblock": "2.0.4", - "symfony/class-loader": "~2.3" - }, - "require-dev": { - "doctrine/dbal": "~2.3" - }, - "suggest": { - "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "Barryvdh\\LaravelIdeHelper\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", - "keywords": [ - "autocomplete", - "codeintel", - "helper", - "ide", - "laravel", - "netbeans", - "phpdoc", - "phpstorm", - "sublime" - ], - "time": "2015-12-21 19:48:06" - }, - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14 21:17:01" - }, - { - "name": "fzaninotto/faker", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d0190b156bcca848d401fb80f31f504f37141c8d", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "suggest": { - "ext-intl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2015-05-29 06:29:14" - }, - { - "name": "hamcrest/hamcrest-php", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" - }, - "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "satooshi/php-coveralls": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "hamcrest" - ], - "files": [ - "hamcrest/Hamcrest.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" - ], - "time": "2015-05-11 14:41:42" - }, - { - "name": "maximebf/debugbar", - "version": "v1.11.0", - "source": { - "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "07741d84d39d10f00551c94284cdefcc69703e77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77", - "reference": "07741d84d39d10f00551c94284cdefcc69703e77", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "time": "2015-12-10 09:50:24" - }, - { - "name": "mockery/mockery", - "version": "0.9.4", - "source": { - "type": "git", - "url": "https://github.com/padraic/mockery.git", - "reference": "70bba85e4aabc9449626651f48b9018ede04f86b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", - "reference": "70bba85e4aabc9449626651f48b9018ede04f86b", - "shasum": "" - }, - "require": { - "hamcrest/hamcrest-php": "~1.1", - "lib-pcre": ">=7.0", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.9.x-dev" - } - }, - "autoload": { - "psr-0": { - "Mockery": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" - } - ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/padraic/mockery", - "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" - ], - "time": "2015-04-02 19:54:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2015-02-03 12:10:50" - }, - { - "name": "phpspec/prophecy", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1" - }, - "require-dev": { - "phpspec/phpspec": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2015-08-13 10:07:40" - }, - { - "name": "phpunit/php-code-coverage", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2015-10-06 15:47:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2015-06-21 13:08:43" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21 13:50:34" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2015-06-21 08:01:12" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2015-09-15 10:49:45" - }, - { - "name": "phpunit/phpunit", - "version": "4.8.21", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": ">=1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.8.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2015-12-12 07:45:58" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2015-10-02 06:51:40" - }, - { - "name": "sebastian/comparator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2015-07-26 15:48:44" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08 07:14:41" - }, - { - "name": "sebastian/environment", - "version": "1.3.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6e7133793a8e5a5714a551a8324337374be209df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", - "reference": "6e7133793a8e5a5714a551a8324337374be209df", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2015-12-02 08:37:27" - }, - { - "name": "sebastian/exporter", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2015-06-21 07:55:53" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12 03:26:01" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" - }, - { - "name": "sebastian/version", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" - }, - { - "name": "symfony/class-loader", - "version": "v2.8.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/class-loader.git", - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/98e9089a428ed0e39423b67352c57ef5910a3269", - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "symfony/finder": "~2.0,>=2.0.5|~3.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\ClassLoader\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony ClassLoader Component", - "homepage": "https://symfony.com", - "time": "2016-01-03 15:33:41" - }, - { - "name": "symfony/css-selector", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4613311fd46e146f506403ce2f8a0c71d402d2a3", - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2015-12-05 17:45:07" - }, - { - "name": "symfony/dom-crawler", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2015-12-26 13:42:31" - }, - { - "name": "symfony/yaml", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2015-12-26 13:39:53" + "time": "2016-03-02 00:37:06" } ], + "packages-dev": null, "aliases": [], "minimum-stability": "stable", "stability-flags": { - "zizaco/entrust": 20, - "barryvdh/laravel-debugbar": 0 + "mockery/mockery": 20, + "barryvdh/laravel-debugbar": 0, + "hamcrest/hamcrest-php": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/config/app.php b/config/app.php index aa8b880314..7efa1d2687 100644 --- a/config/app.php +++ b/config/app.php @@ -150,6 +150,22 @@ return [ Collective\Html\HtmlServiceProvider::class, + /* + * More service providers. + */ + FireflyIII\Providers\AccountServiceProvider::class, + FireflyIII\Providers\AttachmentServiceProvider::class, + FireflyIII\Providers\BillServiceProvider::class, + FireflyIII\Providers\BudgetServiceProvider::class, + FireflyIII\Providers\CategoryServiceProvider::class, + FireflyIII\Providers\ExportJobServiceProvider::class, + FireflyIII\Providers\JournalServiceProvider::class, + FireflyIII\Providers\PiggyBankServiceProvider::class, + FireflyIII\Providers\RuleServiceProvider::class, + FireflyIII\Providers\RuleGroupServiceProvider::class, + FireflyIII\Providers\TagServiceProvider::class, + + /* * Application Service Providers... */ @@ -159,10 +175,13 @@ return [ FireflyIII\Providers\RouteServiceProvider::class, FireflyIII\Providers\FireflyServiceProvider::class, + // own stuff: // Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, +// Barryvdh\Debugbar\ServiceProvider::class, 'DaveJamesMiller\Breadcrumbs\ServiceProvider', 'TwigBridge\ServiceProvider', + 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider', ], @@ -179,21 +198,20 @@ return [ 'aliases' => [ - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -221,7 +239,7 @@ return [ 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', 'Entrust' => 'Zizaco\Entrust\EntrustFacade', 'Input' => 'Illuminate\Support\Facades\Input', - + 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', ], diff --git a/config/auth.php b/config/auth.php index e0ad255263..6144aafde0 100644 --- a/config/auth.php +++ b/config/auth.php @@ -24,11 +24,6 @@ return [ 'driver' => 'eloquent', 'model' => FireflyIII\User::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], 'passwords' => [ diff --git a/config/csv.php b/config/csv.php index dfdcc3d26f..39bd78ff6b 100644 --- a/config/csv.php +++ b/config/csv.php @@ -16,187 +16,176 @@ return [ ], 'roles' => [ '_ignore' => [ - 'name' => '(ignore this column)', 'mappable' => false, 'converter' => 'Ignore', 'field' => 'ignored', ], 'bill-id' => [ - 'name' => 'Bill ID (matching Firefly)', 'mappable' => false, 'field' => 'bill', 'converter' => 'BillId', 'mapper' => 'Bill', ], 'bill-name' => [ - 'name' => 'Bill name', 'mappable' => true, 'converter' => 'BillName', 'field' => 'bill', 'mapper' => 'Bill', ], 'currency-id' => [ - 'name' => 'Currency ID (matching Firefly)', 'mappable' => true, 'converter' => 'CurrencyId', 'field' => 'currency', 'mapper' => 'TransactionCurrency' ], 'currency-name' => [ - 'name' => 'Currency name (matching Firefly)', 'mappable' => true, 'converter' => 'CurrencyName', 'field' => 'currency', 'mapper' => 'TransactionCurrency' ], 'currency-code' => [ - 'name' => 'Currency code (ISO 4217)', 'mappable' => true, 'converter' => 'CurrencyCode', 'field' => 'currency', 'mapper' => 'TransactionCurrency' ], 'currency-symbol' => [ - 'name' => 'Currency symbol (matching Firefly)', 'mappable' => true, 'converter' => 'CurrencySymbol', 'field' => 'currency', 'mapper' => 'TransactionCurrency' ], 'description' => [ - 'name' => 'Description', 'mappable' => false, 'converter' => 'Description', 'field' => 'description', ], 'date-transaction' => [ - 'name' => 'Date', 'mappable' => false, 'converter' => 'Date', 'field' => 'date', ], 'date-rent' => [ - 'name' => 'Rent calculation date', 'mappable' => false, 'converter' => 'Date', 'field' => 'date-rent', ], 'budget-id' => [ - 'name' => 'Budget ID (matching Firefly)', 'mappable' => true, 'converter' => 'BudgetId', 'field' => 'budget', 'mapper' => 'Budget', ], 'budget-name' => [ - 'name' => 'Budget name', 'mappable' => true, 'converter' => 'BudgetName', 'field' => 'budget', 'mapper' => 'Budget', ], 'rabo-debet-credit' => [ - 'name' => 'Rabobank specific debet/credit indicator', 'mappable' => false, 'converter' => 'RabobankDebetCredit', 'field' => 'amount-modifier', ], + 'ing-debet-credit' => [ + 'mappable' => false, + 'converter' => 'INGDebetCredit', + 'field' => 'amount-modifier', + ], 'category-id' => [ - 'name' => 'Category ID (matching Firefly)', 'mappable' => true, 'converter' => 'CategoryId', 'field' => 'category', 'mapper' => 'Category', ], 'category-name' => [ - 'name' => 'Category name', 'mappable' => true, 'converter' => 'CategoryName', 'field' => 'category', 'mapper' => 'Category', ], 'tags-comma' => [ - 'name' => 'Tags (comma separated)', 'mappable' => true, 'field' => 'tags', 'converter' => 'TagsComma', 'mapper' => 'Tag', ], 'tags-space' => [ - 'name' => 'Tags (space separated)', 'mappable' => true, 'field' => 'tags', 'converter' => 'TagsSpace', 'mapper' => 'Tag', ], 'account-id' => [ - 'name' => 'Asset account ID (matching Firefly)', 'mappable' => true, 'mapper' => 'AssetAccount', 'field' => 'asset-account-id', 'converter' => 'AccountId' ], 'account-name' => [ - 'name' => 'Asset account name', 'mappable' => true, 'mapper' => 'AssetAccount', 'field' => 'asset-account-name', 'converter' => 'AssetAccountName' ], 'account-iban' => [ - 'name' => 'Asset account IBAN', 'mappable' => true, 'converter' => 'AssetAccountIban', 'field' => 'asset-account-iban', 'mapper' => 'AssetAccount' ], + 'account-number' => [ + 'mappable' => true, + 'converter' => 'AssetAccountNumber', + 'field' => 'asset-account-number', + 'mapper' => 'AssetAccount' + ], 'opposing-id' => [ - 'name' => 'Opposing account account ID (matching Firefly)', 'mappable' => true, 'field' => 'opposing-account-id', 'converter' => 'OpposingAccountId', 'mapper' => 'AnyAccount', ], 'opposing-name' => [ - 'name' => 'Opposing account name', 'mappable' => true, 'field' => 'opposing-account-name', 'converter' => 'OpposingAccountName', 'mapper' => 'AnyAccount', ], 'opposing-iban' => [ - 'name' => 'Opposing account IBAN', 'mappable' => true, 'field' => 'opposing-account-iban', 'converter' => 'OpposingAccountIban', 'mapper' => 'AnyAccount', ], + 'opposing-number' => [ + 'mappable' => true, + 'field' => 'opposing-account-number', + 'converter' => 'OpposingAccountNumber', + 'mapper' => 'AnyAccount', + ], 'amount' => [ - 'name' => 'Amount', 'mappable' => false, 'converter' => 'Amount', 'field' => 'amount', ], 'amount-comma-separated' => [ - 'name' => 'Amount (comma as decimal separator)', 'mappable' => false, 'converter' => 'AmountComma', 'field' => 'amount', ], 'sepa-ct-id' => [ - 'name' => 'SEPA Credit Transfer end-to-end ID', 'mappable' => false, 'converter' => 'Description', 'field' => 'description', ], 'sepa-ct-op' => [ - 'name' => 'SEPA Credit Transfer opposing account', 'mappable' => false, 'converter' => 'Description', 'field' => 'description', ], 'sepa-db' => [ - 'name' => 'SEPA Direct Debet', 'mappable' => false, 'converter' => 'Description', 'field' => 'description', diff --git a/config/filesystems.php b/config/filesystems.php index 3fffcf0a2f..70e2a2c488 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -48,18 +48,20 @@ return [ 'root' => storage_path('app'), ], + 'upload' => [ + 'driver' => 'local', + 'root' => storage_path('upload'), + ], + 'export' => [ + 'driver' => 'local', + 'root' => storage_path('export'), + ], + 'ftp' => [ 'driver' => 'ftp', 'host' => 'ftp.example.com', 'username' => 'your-username', 'password' => 'your-password', - - // Optional FTP Settings... - // 'port' => 21, - // 'root' => '', - // 'passive' => true, - // 'ssl' => true, - // 'timeout' => 30, ], 's3' => [ diff --git a/config/firefly.php b/config/firefly.php index 303dec785b..9d82d5cea3 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -2,17 +2,24 @@ return [ 'chart' => 'chartjs', - 'version' => '3.7.2.3', + 'version' => '3.8.0', 'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'], 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'csv_import_enabled' => true, 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], + + 'export_formats' => [ + 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', + // mt940 FireflyIII Export Exporter MtExporter + ], + 'default_export_format' => 'csv', + 'piggy_bank_periods' => [ 'week' => 'Week', 'month' => 'Month', 'quarter' => 'Quarter', - 'year' => 'Year' + 'year' => 'Year', ], 'periods_to_text' => [ 'weekly' => 'A week', @@ -35,10 +42,10 @@ return [ '1M' => 'month', '3M' => 'three months', '6M' => 'half year', - 'custom' => '(custom)' + 'custom' => '(custom)', ], 'ccTypes' => [ - 'monthlyFull' => 'Full payment every month' + 'monthlyFull' => 'Full payment every month', ], 'range_to_name' => [ '1D' => 'one day', @@ -54,7 +61,7 @@ return [ '1M' => 'monthly', '3M' => 'quarterly', '6M' => 'half-year', - 'custom' => 'monthly' + 'custom' => 'monthly', ], 'subTitlesByIdentifier' => [ @@ -160,6 +167,7 @@ return [ 'tag' => 'FireflyIII\Models\Tag', 'rule' => 'FireflyIII\Models\Rule', 'ruleGroup' => 'FireflyIII\Models\RuleGroup', + 'jobKey' => 'FireflyIII\Models\ExportJob', // lists 'accountList' => 'FireflyIII\Support\Binder\AccountList', 'budgetList' => 'FireflyIII\Support\Binder\BudgetList', @@ -167,7 +175,7 @@ return [ // others 'start_date' => 'FireflyIII\Support\Binder\Date', - 'end_date' => 'FireflyIII\Support\Binder\Date' + 'end_date' => 'FireflyIII\Support\Binder\Date', ], 'rule-triggers' => [ @@ -210,6 +218,12 @@ return [ 'set_description', 'append_description', 'prepend_description', + ], + 'test-triggers' => [ + // The maximum number of transactions shown when testing a list of triggers + 'limit' => 10, + + // The maximum number of transactions to analyse, when testing a list of triggers + 'range' => 200 ] - ]; diff --git a/config/twigbridge.php b/config/twigbridge.php index c4e6d2c4ba..4eea607b35 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -28,7 +28,6 @@ return [ 'debug' => config('app.debug', false), // The charset used by the templates. - // default: utf-8 'charset' => 'utf-8', // The base template class to use for generated templates. diff --git a/config/upgrade.php b/config/upgrade.php index 109125facd..d9962a69a6 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -22,5 +22,6 @@ return [ 'Please follow the instructions on the following page: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.7.0', '3.7.2.3' => 'Because of the upgrade to Laravel 5.2, several manual changes must be made to your Firefly III installation. ' . 'Please follow the instructions on the following page: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.7.0', + '3.8.0' => 'This version of Firefly III requires PHP 7.0.', ], ]; diff --git a/cover.sh b/cover.sh index 3f7cd1b6bf..6807f88c0a 100755 --- a/cover.sh +++ b/cover.sh @@ -9,19 +9,21 @@ cp phpunit.cover.xml phpunit.xml # delete test databases: if [ -f storage/database/testing.db ] then - rm storage/database/testing.db + echo "Will not remove test db" + # rm storage/database/testing.db fi if [ -f storage/database/testing-copy.db ] then - rm storage/database/testing-copy.db + echo "Will not remove test db" + # rm storage/database/testing-copy.db fi # test! if [ -z "$1" ] then echo "Running all tests..." - phpunit --verbose + phpunit fi # test selective.. @@ -37,14 +39,14 @@ then then # run it! echo "Now running $firstFile" - phpunit --verbose $firstFile + phpunit $firstFile result=$? fi if [ -f "$secondFile" ] then # run it! echo "Now running $secondFile" - phpunit --verbose $secondFile + phpunit $secondFile result=$? fi diff --git a/database/migrations/2014_07_09_204843_create_session_table.php b/database/migrations/2014_07_09_204843_create_session_table.php index 240f66ba88..fa39883634 100644 --- a/database/migrations/2014_07_09_204843_create_session_table.php +++ b/database/migrations/2014_07_09_204843_create_session_table.php @@ -32,6 +32,9 @@ class CreateSessionTable extends Migration Schema::create( 'sessions', function (Blueprint $table) { $table->string('id')->unique(); + $table->integer('user_id')->nullable(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); $table->text('payload'); $table->integer('last_activity'); } diff --git a/database/migrations/2014_12_13_190730_changes_for_v321.php b/database/migrations/2014_12_13_190730_changes_for_v321.php index d4784673b2..3a497d4bc7 100644 --- a/database/migrations/2014_12_13_190730_changes_for_v321.php +++ b/database/migrations/2014_12_13_190730_changes_for_v321.php @@ -81,7 +81,7 @@ class ChangesForV321 extends Migration $this->renameBudgetLimits(); // 14. $this->renamePiggyBankEvents(); // 15. $this->renameBudgetLimitToBudgetInRepetitions(); // 16. - // 17, 18, 19 + // 17 and then 18 and then 19 $this->dropFieldsFromCurrencyTable(); // 20. @@ -359,7 +359,6 @@ class ChangesForV321 extends Migration ]; $budget = Budget::firstOrCreate($entry); - Log::debug('Migrated budget #' . $budget->id . ': ' . $budget->name); // create entry in budget_transaction_journal $connections = DB::table('component_transaction_journal')->where('component_id', $c->id)->get(); /** @var \stdClass $connection */ @@ -400,7 +399,6 @@ class ChangesForV321 extends Migration ]; $category = Category::firstOrCreate($entry); - Log::debug('Migrated category #' . $category->id . ': ' . $category->name); // create entry in category_transaction_journal $connections = DB::table('component_transaction_journal')->where('component_id', $c->id)->get(); /** @var \stdClass $connection */ @@ -435,21 +433,13 @@ class ChangesForV321 extends Migration { BudgetLimit::get()->each( function (BudgetLimit $bl) { - Log::debug('Now at budgetLimit #' . $bl->id . ' with component_id: ' . $bl->component_id); $component = Component::find($bl->component_id); if ($component) { - Log::debug('Found component with id #' . $component->id . ' and name ' . $component->name); $budget = Budget::whereName($component->name)->whereUserId($component->user_id)->first(); if ($budget) { - Log::debug('Found a budget with ID #' . $budget->id . ' and name ' . $budget->name); $bl->budget_id = $budget->id; $bl->save(); - Log::debug('Connected budgetLimit #' . $bl->id . ' to budget_id' . $budget->id); - } else { - Log::debug('Could not find a matching budget with name ' . $component->name); } - } else { - Log::debug('Could not find a component with id ' . $bl->component_id); } } ); diff --git a/database/migrations/2016_01_11_193428_changes_for_v370.php b/database/migrations/2016_01_11_193428_changes_for_v370.php index 996bb6ada7..af60fcce40 100644 --- a/database/migrations/2016_01_11_193428_changes_for_v370.php +++ b/database/migrations/2016_01_11_193428_changes_for_v370.php @@ -67,7 +67,6 @@ class ChangesForV370 extends Migration ); - // new table "rules": Schema::create( 'rules', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2016_02_04_144117_changes_for_v380.php b/database/migrations/2016_02_04_144117_changes_for_v380.php new file mode 100644 index 0000000000..1e8039cb78 --- /dev/null +++ b/database/migrations/2016_02_04_144117_changes_for_v380.php @@ -0,0 +1,68 @@ +date('process_date')->nullable()->after('book_date'); + } + ); + + // new table "export_jobs" + Schema::create( + 'export_jobs', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->integer('user_id')->unsigned(); + $table->string('key', 12)->unique(); + $table->string('status', 45); + + // connect rule groups to users + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + + } + ); + + // new table for transaction journal meta, "journal_meta" + Schema::create( + 'journal_meta', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->integer('transaction_journal_id')->unsigned(); + $table->string('name'); + $table->text('data'); + + $table->unique(['transaction_journal_id', 'name']); + + // link to transaction journal + $table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('cascade'); + } + ); + } +} diff --git a/database/migrations/2016_02_24_172426_create_jobs_table.php b/database/migrations/2016_02_24_172426_create_jobs_table.php new file mode 100644 index 0000000000..9005b7dae7 --- /dev/null +++ b/database/migrations/2016_02_24_172426_create_jobs_table.php @@ -0,0 +1,40 @@ +bigIncrements('id'); + $table->string('queue'); + $table->longText('payload'); + $table->tinyInteger('attempts')->unsigned(); + $table->tinyInteger('reserved')->unsigned(); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('jobs'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 69a846e857..bf4ec69932 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -23,14 +23,9 @@ class DatabaseSeeder extends Seeder $this->call('PermissionSeeder'); // set up basic test data (as little as possible): - if (App::environment() == 'testing') { + if (App::environment() == 'testing' || App::environment() == 'local') { $this->call('TestDataSeeder'); } - - // this one is reserved for more extensive testing. - if (App::environment() == 'local') { - $this->call('VisualTestDataSeeder'); - } } } diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index 05795f4679..ffa2dd627f 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -1,16 +1,15 @@ start = Carbon::create()->subYear()->startOfYear(); + $this->start = Carbon::create()->subYears(2)->startOfYear(); + $this->end = Carbon::now(); } @@ -37,249 +39,56 @@ class TestDataSeeder extends Seeder */ public function run() { - $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'thegrumpydictator+empty@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); + // start by creating all users: + // method will return the first user. + $user = TestData::createUsers(); - - $admin = Role::where('name', 'owner')->first(); - $user->attachRole($admin); - - - // create asset accounts for user #1. + // create all kinds of static data: TestData::createAssetAccounts($user); - - // create bills for user #1 TestData::createBills($user); - - // create some budgets for user #1 - $this->createBudgets($user); - - // create some categories for user #1 - $this->createCategories($user); - - // create some piggy banks for user #1 + TestData::createBudgets($user); + TestData::createCategories($user); TestData::createPiggybanks($user); + TestData::createExpenseAccounts($user); + TestData::createRevenueAccounts($user); + TestData::createAttachments($user, $this->start); + TestData::openingBalanceSavings($user, $this->start); + TestData::createRules($user); - // create some expense accounts for user #1 - $this->createExpenseAccounts($user); + // loop from start to end, create dynamic info. + $current = clone $this->start; + while ($current < $this->end) { + $month = $current->format('F Y'); + // create salaries: + TestData::createIncome($user, 'Salary ' . $month, $current, strval(rand(2000, 2100))); - // create some revenue accounts for user #1 - $this->createRevenueAccounts($user); + // pay bills: + TestData::createRent($user, 'Rent for ' . $month, $current, '800'); + TestData::createWater($user, 'Water bill for ' . $month, $current, '15'); + TestData::createTV($user, 'TV bill for ' . $month, $current, '60'); + TestData::createPower($user, 'Power bill for ' . $month, $current, '120'); - // create journal + attachment: - $this->createAttachments($user); + // pay daily groceries: + TestData::createGroceries($user, $current); - // create opening balance for savings account: - $this->openingBalanceSavings($user); - } + // create tag (each type of tag, for date): + TestData::createTags($user, $current); - /** - * @param User $user - */ - private function createAttachments(User $user) - { + // go out for drinks: + TestData::createDrinksAndOthers($user, $current); - $toAccount = TestData::findAccount($user, 'TestData Checking Account'); - $fromAccount = TestData::findAccount($user, 'Job'); + // save money every month: + TestData::createSavings($user, $current); - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 2, - 'transaction_currency_id' => 1, - 'description' => 'Some journal for attachment', - 'completed' => 1, - 'date' => new Carbon, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => -100, + // buy gas for the car every month: + TestData::createCar($user, $current); - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => 100, + // create budget limits. + TestData::createBudgetLimit($user, $current, 'Groceries', '400'); + TestData::createBudgetLimit($user, $current, 'Bills', '1000'); + TestData::createBudgetLimit($user, $current, 'Car', '100'); - ] - ); - - // and now attachments - $encrypted = Crypt::encrypt('I are secret'); - Attachment::create( - [ - 'attachable_id' => $journal->id, - 'attachable_type' => 'FireflyIII\Models\TransactionJournal', - 'user_id' => $user->id, - 'md5' => md5('Hallo'), - 'filename' => 'empty-file.txt', - 'title' => 'Empty file', - 'description' => 'This file is empty', - 'notes' => 'What notes', - 'mime' => 'text/plain', - 'size' => strlen($encrypted), - 'uploaded' => 1, - ] - ); - - - // and now attachment. - Attachment::create( - [ - 'attachable_id' => $journal->id, - 'attachable_type' => 'FireflyIII\Models\TransactionJournal', - 'user_id' => $user->id, - 'md5' => md5('Ook hallo'), - 'filename' => 'empty-file-2.txt', - 'title' => 'Empty file 2', - 'description' => 'This file is empty too', - 'notes' => 'What notes do', - 'mime' => 'text/plain', - 'size' => strlen($encrypted), - 'uploaded' => 1, - ] - ); - // echo crypted data to the file. - file_put_contents(storage_path('upload/at-1.data'), $encrypted); - file_put_contents(storage_path('upload/at-2.data'), $encrypted); - - } - - /** - * @param $user - */ - private function createBudgets($user) - { - $set = [ - Budget::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]), - Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $user->id]), - ]; - $current = new Carbon; - /** @var Budget $budget */ - foreach ($set as $budget) { - - // some budget limits: - $start = clone $current; - $end = clone $current; - $start->startOfMonth(); - $end->endOfMonth(); - - BudgetLimit::create( - [ - 'budget_id' => $budget->id, - 'startdate' => $start->format('Y-m-d'), - 'amount' => 500, - 'repeats' => 0, - 'repeat_freq' => 'monthly', - ] - ); + $current->addMonth(); } } - - /** - * @param User $user - */ - private function createCategories(User $user) - { - Category::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); - Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); - } - - /** - * @param User $user - */ - private function createExpenseAccounts(User $user) - { - $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl', - 'coolblue', 'Shell', - 'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord']; - foreach ($expenses as $name) { - // create account: - Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 4, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - - } - - /** - * @param User $user - */ - private function createRevenueAccounts(User $user) - { - $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google']; - foreach ($revenues as $name) { - // create account: - Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 5, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - } - - /** - * @param User $user - */ - private function openingBalanceSavings(User $user) - { - // opposing account for opening balance: - $opposing = Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 6, - 'name' => 'Opposing for savings', - 'active' => 1, - 'encrypted' => 1, - ] - ); - - // savings - $savings = TestData::findAccount($user, 'TestData Savings'); - - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 4, - 'transaction_currency_id' => 1, - 'description' => 'Opening balance for savings account', - 'completed' => 1, - 'date' => $this->start->format('Y-m-d'), - ] - ); - - // transactions - Transaction::create( - [ - 'account_id' => $opposing->id, - 'transaction_journal_id' => $journal->id, - 'amount' => -10000, - ] - ); - - Transaction::create( - [ - 'account_id' => $savings->id, - 'transaction_journal_id' => $journal->id, - 'amount' => 10000, - ] - ); - - - } } diff --git a/database/seeds/VisualTestDataSeeder.php b/database/seeds/VisualTestDataSeeder.php deleted file mode 100644 index 6c6810d137..0000000000 --- a/database/seeds/VisualTestDataSeeder.php +++ /dev/null @@ -1,1179 +0,0 @@ -user()->associate($this->user); - $ruleGroup->order = 1; - $ruleGroup->active = 1; - $ruleGroup->title = 'Default rules'; - $ruleGroup->description = 'All your rules not in a particular group.'; - $ruleGroup->save(); - unset($ruleGroup); - - $ruleGroup = new RuleGroup; - $ruleGroup->user()->associate($this->user); - $ruleGroup->order = 2; - $ruleGroup->active = 1; - $ruleGroup->title = 'Empty rule group'; - $ruleGroup->description = 'Intentionally has no rules.'; - $ruleGroup->save(); - unset($ruleGroup); - - - $ruleGroup = new RuleGroup; - $ruleGroup->user()->associate($this->user); - $ruleGroup->order = 3; - $ruleGroup->active = 1; - $ruleGroup->title = 'Rules for bills'; - $ruleGroup->description = 'All rules for bills and recurring costs.'; - $ruleGroup->save(); - unset($ruleGroup); - - // move groceries to correct budget/category - $rule = new Rule; - $rule->user()->associate($this->user); - $rule->ruleGroup()->associate(RuleGroup::find(1)); - $rule->order = 1; - $rule->active = 1; - $rule->stop_processing = 0; - $rule->title = 'Move groceries'; - $rule->description = 'Move groceries to correct category and budget.'; - - $rule->save(); - - // initial trigger for this rule: - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 1; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'user_action'; - $ruleTrigger->trigger_value = 'store-journal'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // content trigger for this rule. - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 2; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'description_contains'; - $ruleTrigger->trigger_value = 'groceries'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 3; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'from_account_is'; - $ruleTrigger->trigger_value = 'TestData Checking Account'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // actions for this rule. one, set category - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 1; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_category'; - $ruleAction->action_value = 'Groceries'; - $ruleAction->save(); - unset($ruleAction); - - // actions for this rule. one, set budget - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 2; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_budget'; - $ruleAction->action_value = 'Groceries'; - $ruleAction->save(); - unset($ruleAction); - - - // move "gas" to "Car" and "Car" - $rule = new Rule; - $rule->user()->associate($this->user); - $rule->ruleGroup()->associate(RuleGroup::find(1)); - $rule->order = 2; - $rule->active = 1; - $rule->stop_processing = 0; - $rule->title = 'Move gas'; - $rule->description = null; - - $rule->save(); - - // initial trigger for this rule: - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 1; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'user_action'; - $ruleTrigger->trigger_value = 'store-journal'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // content trigger for this rule. - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 2; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'description_contains'; - $ruleTrigger->trigger_value = 'gas'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 3; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'from_account_is'; - $ruleTrigger->trigger_value = 'TestData Checking Account'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // actions for this rule. one, set category - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 1; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_category'; - $ruleAction->action_value = 'Car'; - $ruleAction->save(); - unset($ruleAction); - - // actions for this rule. one, set budget - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 2; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_budget'; - $ruleAction->action_value = 'Car'; - $ruleAction->save(); - unset($ruleAction); - - // move savings to money management - $rule = new Rule; - $rule->user()->associate($this->user); - $rule->ruleGroup()->associate(RuleGroup::find(1)); - $rule->order = 3; - $rule->active = 1; - $rule->stop_processing = 0; - $rule->title = 'Move savings'; - $rule->description = null; - - $rule->save(); - - // initial trigger for this rule: - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 1; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'user_action'; - $ruleTrigger->trigger_value = 'store-journal'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // is transfer - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 2; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'transaction_type'; - $ruleTrigger->trigger_value = 'Transfer'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // content trigger for this rule. - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 3; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'description_is'; - $ruleTrigger->trigger_value = 'Save money'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 4; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'from_account_is'; - $ruleTrigger->trigger_value = 'TestData Checking Account'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 5; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'to_account_is'; - $ruleTrigger->trigger_value = 'TestData Savings'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // actions for this rule. one, set category - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 1; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_category'; - $ruleAction->action_value = 'Money Management'; - $ruleAction->save(); - unset($ruleAction); - - // move TV bill to "Bills" and "House" - $rule = new Rule; - $rule->user()->associate($this->user); - $rule->ruleGroup()->associate(RuleGroup::find(3)); - $rule->order = 1; - $rule->active = 1; - $rule->stop_processing = 0; - $rule->title = 'TV Bill'; - $rule->description = null; - - $rule->save(); - - // initial trigger for this rule: - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 1; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'user_action'; - $ruleTrigger->trigger_value = 'store-journal'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // content trigger for this rule. - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 2; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'description_contains'; - $ruleTrigger->trigger_value = 'tv bill'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 3; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'from_account_is'; - $ruleTrigger->trigger_value = 'TestData Checking Account'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // actions for this rule. one, set category - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 1; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_category'; - $ruleAction->action_value = 'House'; - $ruleAction->save(); - unset($ruleAction); - - // actions for this rule. one, set budget - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 2; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_budget'; - $ruleAction->action_value = 'Bills'; - $ruleAction->save(); - unset($ruleAction); - - // move rent to bills, rent. - $rule = new Rule; - $rule->user()->associate($this->user); - $rule->ruleGroup()->associate(RuleGroup::find(3)); - $rule->order = 2; - $rule->active = 1; - $rule->stop_processing = 1; - $rule->title = 'Rent'; - $rule->description = 'Do something with rent.'; - - $rule->save(); - - // initial trigger for this rule: - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 1; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'user_action'; - $ruleTrigger->trigger_value = 'update-journal'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // content trigger for this rule. - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 2; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 0; - $ruleTrigger->trigger_type = 'description_contains'; - $ruleTrigger->trigger_value = 'rent'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // another - $ruleTrigger = new RuleTrigger; - $ruleTrigger->rule()->associate($rule); - $ruleTrigger->order = 3; - $ruleTrigger->active = 1; - $ruleTrigger->stop_processing = 1; - $ruleTrigger->trigger_type = 'from_account_is'; - $ruleTrigger->trigger_value = 'TestData Checking Account'; - - $ruleTrigger->save(); - unset($ruleTrigger); - - // actions for this rule. one, set category - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 1; - $ruleAction->active = 1; - $ruleAction->stop_processing = 0; - $ruleAction->action_type = 'set_category'; - $ruleAction->action_value = 'House'; - $ruleAction->save(); - unset($ruleAction); - - // actions for this rule. one, set budget - $ruleAction = new RuleAction; - $ruleAction->rule()->associate($rule); - $ruleAction->order = 2; - $ruleAction->active = 1; - $ruleAction->stop_processing = 1; - $ruleAction->action_type = 'set_budget'; - $ruleAction->action_value = 'Bills'; - $ruleAction->save(); - unset($ruleAction); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function run() - { - $this->createUsers(); - - // create accounts: - TestData::createAssetAccounts($this->user); - $this->createExpenseAccounts(); - $this->createRevenueAccounts(); - TestData::createBills($this->user); - TestData::createPiggybanks($this->user); - - $this->createRules(); - - // preference to only see account #1 on frontpage. - $this->createPreferences(); - - // dates: - $start = Carbon::now()->subYears(2)->startOfMonth(); - $end = Carbon::now()->endOfDay(); - - - $current = clone $start; - while ($current < $end) { - $month = $current->format('F Y'); - // create salaries: - $this->createIncome('Salary ' . $month, $current, rand(2000, 2100)); - - // pay bills: - $this->createRent('Rent for ' . $month, $current, 800); - $this->createWater('Water bill for ' . $month, $current, 15); - $this->createTV('TV bill for ' . $month, $current, 60); - $this->createPower('Power bill for ' . $month, $current, 120); - - - // pay daily groceries: - $this->createGroceries($current); - - // create tag (each type of tag, for date): - $this->createTags($current); - - // go out for drinks: - $this->createDrinksAndOthers($current); - - // save money every month: - $this->createSavings($current); - - // buy gas for the car every month: - $this->createCar($current); - - // budget limit for this month, on "Groceries". - $this->createBudgetLimit($current, 'Groceries', 400); - $this->createBudgetLimit($current, 'Bills', 1000); - $this->createBudgetLimit($current, 'Car', 100); - - echo 'Created test data for ' . $month . "\n"; - $current->addMonth(); - } - - } - - /** - * @param Carbon $current - * @param $name - * @param $amount - */ - protected function createBudgetLimit(Carbon $current, $name, $amount) - { - $start = clone $current; - $end = clone $current; - $budget = $this->findBudget($name); - $start->startOfMonth(); - $end->endOfMonth(); - - BudgetLimit::create( - [ - 'budget_id' => $budget->id, - 'startdate' => $start->format('Y-m-d'), - 'amount' => $amount, - 'repeats' => 0, - 'repeat_freq' => 'monthly', - ] - ); - } - - /** - * @param $date - * - * @return static - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function createCar($date) - { - // twice: - $date = new Carbon($date->format('Y-m') . '-10'); // paid on 10th - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'Shell'); - $category = Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $this->user->id]); - $amount = rand(4000, 5000) / 100; - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => 'Bought gas', - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - // and again! - $date = new Carbon($date->format('Y-m') . '-20'); // paid on 20th - $amount = rand(4000, 5000) / 100; - - - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => 'Gas for car', - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - - // and again! - - return $journal; - } - - /** - * @param Carbon $date - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function createDrinksAndOthers(Carbon $date) - { - $start = clone $date; - $end = clone $date; - $today = new Carbon; - $start->startOfMonth(); - $end->endOfMonth(); - $current = clone $start; - while ($current < $end && $current < $today) { - - // weekly drink: - $thisDate = clone $current; - $thisDate->addDay(); - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'Cafe Central'); - $category = Category::firstOrCreateEncrypted(['name' => 'Drinks', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Going out', 'user_id' => $this->user->id]); - $amount = rand(1500, 3600) / 100; - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => 'Going out for drinks', - 'completed' => 1, - 'date' => $thisDate, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - // shopping at some (online) shop: - - - $current->addWeek(); - } - } - - protected function createExpenseAccounts() - { - $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl', - 'coolblue', 'Shell', - 'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord']; - foreach ($expenses as $name) { - // create account: - Account::create( - [ - 'user_id' => $this->user->id, - 'account_type_id' => 4, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - - } - - /** - * @param Carbon $date - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function createGroceries(Carbon $date) - { - $start = clone $date; - $end = clone $date; - $today = new Carbon; - $start->startOfMonth(); - $end->endOfMonth(); - - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $stores = ['Albert Heijn', 'PLUS', 'Bakker']; - $descriptions = ['Groceries', 'Bought some groceries', 'Got groceries']; - $category = Category::firstOrCreateEncrypted(['name' => 'Daily groceries', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $this->user->id]); - - $current = clone $start; - while ($current < $end && $current < $today) { - // daily groceries: - $amount = rand(1500, 2500) / 100; - $toAccount = TestData::findAccount($this->user, $stores[rand(0, count($stores) - 1)]); - - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => $descriptions[rand(0, count($descriptions) - 1)], - 'completed' => 1, - 'date' => $current, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - - $current->addDay(); - } - } - - /** - * @param $description - * @param Carbon $date - * @param $amount - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * - * @return TransactionJournal - */ - protected function createIncome($description, Carbon $date, $amount) - { - $date = new Carbon($date->format('Y-m') . '-23'); // paid on 23rd. - $today = new Carbon; - if ($date >= $today) { - return null; - } - $toAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $fromAccount = TestData::findAccount($this->user, 'Job'); - $category = Category::firstOrCreateEncrypted(['name' => 'Salary', 'user_id' => $this->user->id]); - // create journal: - - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 2, - 'transaction_currency_id' => 1, - 'description' => $description, - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - - return $journal; - - } - - /** - * @param $description - * @param Carbon $date - * @param $amount - * - * @return TransactionJournal - */ - protected function createPower($description, Carbon $date, $amount) - { - $date = new Carbon($date->format('Y-m') . '-06'); // paid on 10th - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'Greenchoice'); - $category = Category::firstOrCreateEncrypted(['name' => 'House', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $this->user->id]); - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => $description, - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - return $journal; - - } - - protected function createPreferences() - { - $preference = new Preference; - $preference->name = 'frontPageAccounts'; - $preference->data = [1]; - $preference->user()->associate($this->user); - $preference->save(); - } - - /** - * @param $description - * @param Carbon $date - * @param $amount - * - * @return TransactionJournal - */ - protected function createRent($description, Carbon $date, $amount) - { - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'Land lord'); - $category = Category::firstOrCreateEncrypted(['name' => 'Rent', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $this->user->id]); - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'bill_id' => 1, - 'description' => $description, - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - return $journal; - - } - - /** - * - */ - protected function createRevenueAccounts() - { - $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google']; - foreach ($revenues as $name) { - // create account: - Account::create( - [ - 'user_id' => $this->user->id, - 'account_type_id' => 5, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - } - - /** - * @param Carbon $date - * - * @return TransactionJournal - */ - protected function createSavings(Carbon $date) - { - $date = new Carbon($date->format('Y-m') . '-24'); // paid on 24th. - $toAccount = TestData::findAccount($this->user, 'TestData Savings'); - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $category = Category::firstOrCreateEncrypted(['name' => 'Money management', 'user_id' => $this->user->id]); - // create journal: - - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 3, - 'transaction_currency_id' => 1, - 'description' => 'Save money', - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => -150, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => 150, - - ] - ); - $journal->categories()->save($category); - - return $journal; - - } - - /** - * @param $description - * @param Carbon $date - * @param $amount - * - * @return TransactionJournal - */ - protected function createTV($description, Carbon $date, $amount) - { - $date = new Carbon($date->format('Y-m') . '-15'); // paid on 10th - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'XS4All'); - $category = Category::firstOrCreateEncrypted(['name' => 'House', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $this->user->id]); - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => $description, - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - return $journal; - - } - - /** - * @param Carbon $date - */ - protected function createTags(Carbon $date) - { - Tag::create( - [ - 'user_id' => $this->user->id, - 'tag' => 'SomeTag' . $date->month . '.' . $date->year . '.nothing', - 'tagMode' => 'nothing', - 'date' => $date->format('Y-m-d'), - - - ] - ); - } - - /** - * - */ - protected function createUsers() - { - User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); - $this->user = User::whereEmail('thegrumpydictator@gmail.com')->first(); - - // create rights: - $role = Role::find(1); - $this->user->roles()->save($role); - - } - - /** - * @param $description - * @param Carbon $date - * @param $amount - * - * @return TransactionJournal - */ - protected function createWater($description, Carbon $date, $amount) - { - $date = new Carbon($date->format('Y-m') . '-10'); // paid on 10th - $fromAccount = TestData::findAccount($this->user, 'TestData Checking Account'); - $toAccount = TestData::findAccount($this->user, 'Vitens'); - $category = Category::firstOrCreateEncrypted(['name' => 'House', 'user_id' => $this->user->id]); - $budget = Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $this->user->id]); - $journal = TransactionJournal::create( - [ - 'user_id' => $this->user->id, - 'transaction_type_id' => 1, - 'transaction_currency_id' => 1, - 'description' => $description, - 'completed' => 1, - 'date' => $date, - ] - ); - Transaction::create( - [ - 'account_id' => $fromAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount * -1, - - ] - ); - Transaction::create( - [ - 'account_id' => $toAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - $journal->categories()->save($category); - $journal->budgets()->save($budget); - - return $journal; - - } - - /** - * @param $name - * - * @return Bill|null - */ - protected function findBill($name) - { - /** @var Bill $bill */ - foreach (Bill::get() as $bill) { - if ($bill->name == $name && $this->user->id == $bill->user_id) { - return $bill; - break; - } - } - - return null; - } - - /** - * @param $name - * - * @return Budget|null - */ - protected function findBudget($name) - { - /** @var Budget $budget */ - foreach (Budget::get() as $budget) { - if ($budget->name == $name && $this->user->id == $budget->user_id) { - return $budget; - break; - } - } - - return null; - } - - /** - * @param $name - * - * @return Category|null - */ - protected function findCategory($name) - { - - /** @var Category $category */ - foreach (Category::get() as $category) { - if ($category->name == $name && $this->user->id == $category->user_id) { - return $category; - break; - } - } - - return null; - } - - /** - * @param $name - * - * @return PiggyBank|null - */ - protected function findPiggyBank($name) - { - - /** @var Budget $budget */ - foreach (PiggyBank::get() as $piggyBank) { - $account = $piggyBank->account()->first(); - if ($piggyBank->name == $name && $this->user->id == $account->user_id) { - return $piggyBank; - break; - } - } - - return null; - } - - /** - * @param $tagName - * - * @return Tag|null - * @internal param $tag - */ - protected function findTag($tagName) - { - /** @var Tag $tag */ - foreach (Tag::get() as $tag) { - if ($tag->tag == $tagName && $this->user->id == $tag->user_id) { - return $tag; - break; - } - } - - return null; - } - - -} diff --git a/gulpfile.js b/gulpfile.js old mode 100755 new mode 100644 diff --git a/phpunit.default.xml b/phpunit.default.xml index cc0841c1d3..31ac89bcfe 100644 --- a/phpunit.default.xml +++ b/phpunit.default.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ @@ -24,4 +24,8 @@ + + + + diff --git a/phpunit.xml b/phpunit.xml index cc0841c1d3..31ac89bcfe 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ @@ -24,4 +24,8 @@ + + + + diff --git a/pu.sh b/pu.sh index f3efd0a96b..e368f8b520 100755 --- a/pu.sh +++ b/pu.sh @@ -1,5 +1,33 @@ #!/bin/bash +searchFile="" +deleteDatabases=false + +while getopts ":nhf:" opt; do + case $opt in + n) + # echo "-n was triggered: new database!" >&2 + deleteDatabases=true + ;; + f) + #echo "-f was triggered: file! $OPTARG" >&2 + searchFile=$OPTARG + ;; + h) + echo "n: new database" >&2 + echo "f: which file to run" >&2 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + # set testing environment cp .env.testing .env @@ -10,20 +38,32 @@ cp phpunit.default.xml phpunit.xml touch storage/upload/at-1.data touch storage/upload/at-2.data +# delete databses: +if [ "$deleteDatabases" = true ] ; then + echo "Will delete and recreate the databases." -# delete test databases: -if [ -f storage/database/testing.db ] -then - rm storage/database/testing.db + # delete test database: + if [ -f storage/database/testing.db ] + then + echo "Deleted testing.db" + rm storage/database/testing.db + fi + + # delete test database copy: + if [ -f storage/database/testing-copy.db ] + then + echo "Delete testing-copy.db" + rm storage/database/testing-copy.db + fi fi -if [ -f storage/database/testing-copy.db ] -then - rm storage/database/testing-copy.db +# do not delete database: +if [ "$deleteDatabases" = false ] ; then + echo "Will not delete databases." fi # test! -if [ -z "$1" ] +if [ "$searchFile" == "" ] then echo "Running all tests..." phpunit @@ -33,32 +73,30 @@ fi # test selective.. dirs=("acceptance/Controllers" "acceptance/Controllers/Auth" "acceptance/Controllers/Chart" "unit") # -if [ ! -z "$1" ] +if [ "$searchFile" != "" ] then + echo "Will run test for '$searchFile'" for i in "${dirs[@]}" do - firstFile="./tests/$i/$1.php" - secondFile="./tests/$i/$1Test.php" + firstFile="./tests/$i/$searchFile.php" + secondFile="./tests/$i/"$searchFile"Test.php" if [ -f "$firstFile" ] then # run it! - echo "Now running $firstFile" + echo "Found file '$firstFile'" phpunit --verbose $firstFile result=$? fi if [ -f "$secondFile" ] then # run it! - echo "Now running $secondFile" + echo "Found file '$secondFile'" phpunit --verbose $secondFile result=$? fi - - done fi - # restore .env file cp .env.local .env diff --git a/public/android-chrome-144x144.png b/public/android-chrome-144x144.png index 5bbc2e7a61..0fbc935ef5 100644 Binary files a/public/android-chrome-144x144.png and b/public/android-chrome-144x144.png differ diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png index 325f76a32c..fb8549caef 100644 Binary files a/public/android-chrome-192x192.png and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-36x36.png b/public/android-chrome-36x36.png index 8bbc22da5c..c101427bd7 100644 Binary files a/public/android-chrome-36x36.png and b/public/android-chrome-36x36.png differ diff --git a/public/android-chrome-48x48.png b/public/android-chrome-48x48.png index 948730fdca..3817a33ab7 100644 Binary files a/public/android-chrome-48x48.png and b/public/android-chrome-48x48.png differ diff --git a/public/android-chrome-72x72.png b/public/android-chrome-72x72.png index 55a7375e8c..92ae9f437b 100644 Binary files a/public/android-chrome-72x72.png and b/public/android-chrome-72x72.png differ diff --git a/public/android-chrome-96x96.png b/public/android-chrome-96x96.png index 7ecebf0a2e..3602979589 100644 Binary files a/public/android-chrome-96x96.png and b/public/android-chrome-96x96.png differ diff --git a/public/apple-touch-icon-114x114.png b/public/apple-touch-icon-114x114.png index aa4623e402..498ea65bd1 100644 Binary files a/public/apple-touch-icon-114x114.png and b/public/apple-touch-icon-114x114.png differ diff --git a/public/apple-touch-icon-120x120.png b/public/apple-touch-icon-120x120.png index 69f186adff..542d5330e4 100644 Binary files a/public/apple-touch-icon-120x120.png and b/public/apple-touch-icon-120x120.png differ diff --git a/public/apple-touch-icon-144x144.png b/public/apple-touch-icon-144x144.png index b02e2e380d..d4aab1e910 100644 Binary files a/public/apple-touch-icon-144x144.png and b/public/apple-touch-icon-144x144.png differ diff --git a/public/apple-touch-icon-152x152.png b/public/apple-touch-icon-152x152.png index 927af49f12..9e51ddfb64 100644 Binary files a/public/apple-touch-icon-152x152.png and b/public/apple-touch-icon-152x152.png differ diff --git a/public/apple-touch-icon-180x180.png b/public/apple-touch-icon-180x180.png index f3eb5fb0e0..c0f50f32f5 100644 Binary files a/public/apple-touch-icon-180x180.png and b/public/apple-touch-icon-180x180.png differ diff --git a/public/apple-touch-icon-57x57.png b/public/apple-touch-icon-57x57.png index f2141ad174..414e32f2ee 100644 Binary files a/public/apple-touch-icon-57x57.png and b/public/apple-touch-icon-57x57.png differ diff --git a/public/apple-touch-icon-60x60.png b/public/apple-touch-icon-60x60.png index dd7b9d7f8c..e05470b64b 100644 Binary files a/public/apple-touch-icon-60x60.png and b/public/apple-touch-icon-60x60.png differ diff --git a/public/apple-touch-icon-72x72.png b/public/apple-touch-icon-72x72.png index 360ad6b514..d2080d08e2 100644 Binary files a/public/apple-touch-icon-72x72.png and b/public/apple-touch-icon-72x72.png differ diff --git a/public/apple-touch-icon-76x76.png b/public/apple-touch-icon-76x76.png index 25cd70ee50..47bd65a56d 100644 Binary files a/public/apple-touch-icon-76x76.png and b/public/apple-touch-icon-76x76.png differ diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png index d37f2828ac..0b6bb71456 100644 Binary files a/public/apple-touch-icon-precomposed.png and b/public/apple-touch-icon-precomposed.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index f3eb5fb0e0..c0f50f32f5 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml index d01e2c2194..6d6066d3f2 100644 --- a/public/browserconfig.xml +++ b/public/browserconfig.xml @@ -2,11 +2,11 @@ - - - - - #2d89ef + + + + + #da532c diff --git a/public/css/firefly.css b/public/css/firefly.css index 898a85a3be..4ce8b95c80 100644 --- a/public/css/firefly.css +++ b/public/css/firefly.css @@ -13,7 +13,30 @@ } +.ff-error-page { + width: 1000px; + margin: 20px auto 0 auto; +} +.ff-error-page > .headline { + float: left; + font-size: 100px; + font-weight: 300; +} +.ff-error-page > .error-content { + margin-left: 190px; + display: block; +} +.ff-error-page > .error-content > h3 { + font-weight: 300; + font-size: 25px; +} +.ff-error-box { + width: 460px; + margin: 7% auto; +} /* cursors */ .rule-triggers {cursor:move;} -.rule-actions {cursor:move;} \ No newline at end of file +.rule-actions {cursor:move;} + +#testTriggerModal .modal-body { max-height: 500px; overflow-y: scroll; } diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png index 03953f23ac..496df9f39a 100644 Binary files a/public/favicon-16x16.png and b/public/favicon-16x16.png differ diff --git a/public/favicon-194x194.png b/public/favicon-194x194.png new file mode 100644 index 0000000000..25d5884833 Binary files /dev/null and b/public/favicon-194x194.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png index 80c79d7ae2..c729437261 100644 Binary files a/public/favicon-32x32.png and b/public/favicon-32x32.png differ diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png index 7ecebf0a2e..555cb24669 100644 Binary files a/public/favicon-96x96.png and b/public/favicon-96x96.png differ diff --git a/public/images/loading-wide.gif b/public/images/loading-wide.gif new file mode 100644 index 0000000000..135987902d Binary files /dev/null and b/public/images/loading-wide.gif differ diff --git a/public/js/accounts/show.js b/public/js/accounts/show.js index fb0a5d9ea2..f913802ab3 100644 --- a/public/js/accounts/show.js +++ b/public/js/accounts/show.js @@ -79,7 +79,7 @@ function sortStop(event, ui) { }); // do extra animation when done? - $.post('/transaction/reorder', {items: submit, date: thisDate, _token: token}); + $.post('transaction/reorder', {items: submit, date: thisDate, _token: token}); current.animate({backgroundColor: "#5cb85c"}, 200, function () { $(this).animate({backgroundColor: originalBG}, 200); diff --git a/public/js/budgets.js b/public/js/budgets.js index 635942d1df..c8ca145734 100644 --- a/public/js/budgets.js +++ b/public/js/budgets.js @@ -62,7 +62,7 @@ function updateBudgetedAmounts(e) { drawBudgetedBar(); // send a post to Firefly to update the amount: - $.post('budgets/amount/' + id, {amount: value, _token: token}).success(function (data) { + $.post('budgets/amount/' + id, {amount: value, _token: token}).done(function (data) { // update the link if relevant: if (data.repetition > 0) { $('.budget-link[data-id="' + id + '"]').attr('href', 'budgets/show/' + id + '/' + data.repetition); diff --git a/public/js/charts.js b/public/js/charts.js index b4ad2736c9..e65b106e55 100644 --- a/public/js/charts.js +++ b/public/js/charts.js @@ -92,10 +92,10 @@ var defaultLineOptions = { datasetFill: false, scaleFontSize: 10, responsive: false, - scaleLabel: "<%= '" + currencySymbol + " ' + Number(value).toFixed(0).replace('.', ',') %>", + scaleLabel: " <%= accounting.formatMoney(value) %>", tooltipFillColor: "rgba(0,0,0,0.5)", - tooltipTemplate: "<%if (label){%><%=label%>: <%}%>" + currencySymbol + " <%= value %>", - multiTooltipTemplate: "<%=datasetLabel%>: <%= '" + currencySymbol + " ' + Number(value).toFixed(2).replace('.', ',') %>" + tooltipTemplate: "<%if (label){%><%=label%>: <%}%> <%= accounting.formatMoney(value) %>", + multiTooltipTemplate: "<%=datasetLabel%>: <%= accounting.formatMoney(value) %>" }; var defaultColumnOptions = { @@ -136,7 +136,7 @@ var defaultStackedColumnOptions = { */ function lineChart(URL, container, options) { "use strict"; - $.getJSON(URL).success(function (data) { + $.getJSON(URL).done(function (data) { var ctx = document.getElementById(container).getContext("2d"); var newData = {}; newData.datasets = []; @@ -170,7 +170,7 @@ function lineChart(URL, container, options) { function areaChart(URL, container, options) { "use strict"; - $.getJSON(URL).success(function (data) { + $.getJSON(URL).done(function (data) { var ctx = document.getElementById(container).getContext("2d"); var newData = {}; newData.datasets = []; @@ -206,7 +206,7 @@ function columnChart(URL, container, options) { options = options || {}; - $.getJSON(URL).success(function (data) { + $.getJSON(URL).done(function (data) { var result = true; if (options.beforeDraw) { @@ -252,7 +252,7 @@ function stackedColumnChart(URL, container, options) { options = options || {}; - $.getJSON(URL).success(function (data) { + $.getJSON(URL).done(function (data) { var result = true; if (options.beforeDraw) { @@ -295,7 +295,7 @@ function stackedColumnChart(URL, container, options) { function pieChart(URL, container, options) { "use strict"; - $.getJSON(URL).success(function (data) { + $.getJSON(URL).done(function (data) { var ctx = document.getElementById(container).getContext("2d"); new Chart(ctx).Pie(data, defaultPieOptions); diff --git a/public/js/export/index.js b/public/js/export/index.js new file mode 100644 index 0000000000..0e29d12da5 --- /dev/null +++ b/public/js/export/index.js @@ -0,0 +1,126 @@ +/* globals token, jobKey */ + +/* + * index.js + * Copyright (C) 2016 Sander Dorigo + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +var intervalId = 0; + +$(function () { + "use strict"; + // on click of export button: + // - hide form + // - post export command + // - start polling progress. + // - return false, + + $('#export').submit(startExport); + } +); + +function startExport() { + "use strict"; + console.log('Start export...'); + hideForm(); + showLoading(); + hideError(); + + // do export + callExport(); + + return false; +} + +function hideError() { + "use strict"; + $('#export-error').hide(); +} + +function hideForm() { + "use strict"; + $('#form-body').hide(); + $('#do-export-button').hide(); +} + +function showForm() { + "use strict"; + $('#form-body').show(); + $('#do-export-button').show(); +} + +function showLoading() { + "use strict"; + $('#export-loading').show(); +} + +function hideLoading() { + "use strict"; + $('#export-loading').hide(); +} + +function showDownload() { + "use strict"; + $('#export-download').show(); +} + +function showError(text) { + "use strict"; + $('#export-error').show(); + $('#export-error>p').text(text); +} + +function callExport() { + "use strict"; + console.log('Start callExport()...') + var data = $('#export').serialize(); + + // call status, keep calling it until response is "finished"? + intervalId = window.setInterval(checkStatus, 500); + + $.post('export/submit', data).done(function (data) { + console.log('Export hath succeeded!'); + + // stop polling: + window.clearTimeout(intervalId); + + // call it one last time: + window.setTimeout(checkStatus, 500); + + // somewhere here is a download link. + + // keep the loading thing, for debug. + hideLoading(); + + // show download + showDownload(); + + }).fail(function () { + // show error. + // show form again. + showError('The export failed. Please check the log files to find out why.'); + + // stop polling: + window.clearTimeout(intervalId); + + hideLoading(); + showForm(); + + }); +} + +function checkStatus() { + "use strict"; + console.log('get status...'); + $.getJSON('export/status/' + jobKey).done(function (data) { + putStatusText(data.status); + }); +} + +function putStatusText(status) { + "use strict"; + $('#status-message').text(status); +} \ No newline at end of file diff --git a/public/js/firefly.js b/public/js/firefly.js index 019726a578..3130714722 100644 --- a/public/js/firefly.js +++ b/public/js/firefly.js @@ -47,7 +47,7 @@ $(function () { end: end.format('YYYY-MM-DD'), label: label, _token: token - }).success(function () { + }).done(function () { console.log('Succesfully sent new date range.'); window.location.reload(true); }).fail(function () { diff --git a/public/js/guest.js b/public/js/guest.js new file mode 100644 index 0000000000..7ba3e6bed9 --- /dev/null +++ b/public/js/guest.js @@ -0,0 +1,6 @@ +$(function () { + "use strict"; + + // Focus first visible form element. + $("form input:enabled:visible:first").first().select() +}); \ No newline at end of file diff --git a/public/js/help.js b/public/js/help.js index fab1272029..e5d8fc59ae 100644 --- a/public/js/help.js +++ b/public/js/help.js @@ -16,7 +16,7 @@ function showHelp(e) { $('#helpTitle').html('Please hold...'); $('#helpModal').modal('show'); - $.getJSON('help/' + encodeURI(route)).success(function (data) { + $.getJSON('help/' + encodeURI(route)).done(function (data) { $('#helpBody').html(data.text); $('#helpTitle').html(data.title); }).fail(function () { diff --git a/public/js/index.js b/public/js/index.js index 87bae2bf0f..faeed6f099 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -5,7 +5,7 @@ $(function () { // do chart JS stuff. drawChart(); if (showTour) { - $.getJSON('json/tour').success(function (data) { + $.getJSON('json/tour').done(function (data) { var tour = new Tour( { steps: data.steps, @@ -70,7 +70,7 @@ function getBoxAmounts() { var boxes = ['in', 'out', 'bills-unpaid', 'bills-paid']; for (var x in boxes) { var box = boxes[x]; - $.getJSON('json/box/' + box).success(putData).fail(failData); + $.getJSON('json/box/' + box).done(putData).fail(failData); } } diff --git a/public/js/piggy-banks/index.js b/public/js/piggy-banks/index.js index 9c2464df39..de6931be65 100644 --- a/public/js/piggy-banks/index.js +++ b/public/js/piggy-banks/index.js @@ -79,7 +79,7 @@ function stopSorting() { var id = holder.data('id'); order.push(id); }); - $.post('/piggy-banks/sort', {_token: token, order: order}).success(function () { + $.post('piggy-banks/sort', {_token: token, order: order}).done(function () { $('.loadSpin').removeClass('fa fa-refresh fa-spin'); }); } \ No newline at end of file diff --git a/public/js/reports/default/month.js b/public/js/reports/default/month.js index be66324dff..22bb92dbc9 100644 --- a/public/js/reports/default/month.js +++ b/public/js/reports/default/month.js @@ -17,7 +17,7 @@ function drawChart() { // month view: // draw account chart - lineChart('/chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart'); + lineChart('chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart'); } diff --git a/public/js/reports/default/multi-year.js b/public/js/reports/default/multi-year.js index 61931c1d78..984d0e4a72 100644 --- a/public/js/reports/default/multi-year.js +++ b/public/js/reports/default/multi-year.js @@ -17,6 +17,7 @@ function drawChart() { "use strict"; // income and expense over multi year: + lineChart('chart/report/net-worth/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'net-worth'); columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart'); columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart'); diff --git a/public/js/reports/default/reports.js b/public/js/reports/default/reports.js index 23adac5fcd..8a42a47788 100644 --- a/public/js/reports/default/reports.js +++ b/public/js/reports/default/reports.js @@ -116,7 +116,7 @@ function drawChart() { } if (typeof lineChart !== 'undefined' && typeof accountIds !== 'undefined') { - lineChart('/chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart'); + lineChart('chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart'); } } @@ -126,7 +126,7 @@ function openModal(e) { var target = $(e.target).parent(); var URL = target.attr('href'); - $.get(URL).success(function (data) { + $.get(URL).done(function (data) { $('#defaultModal').empty().html(data).modal('show'); }).fail(function () { diff --git a/public/js/reports/default/year.js b/public/js/reports/default/year.js index 12fbfe7e13..8a39be3ef3 100644 --- a/public/js/reports/default/year.js +++ b/public/js/reports/default/year.js @@ -15,6 +15,7 @@ $(function () { function drawChart() { "use strict"; + lineChart('chart/report/net-worth/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'net-worth'); columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart'); columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart'); stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets'); diff --git a/public/js/reports/index.js b/public/js/reports/index.js index 009be936b6..0419062234 100644 --- a/public/js/reports/index.js +++ b/public/js/reports/index.js @@ -34,8 +34,8 @@ $(function () { var startStr = readCookie('report-start'); var endStr = readCookie('report-end'); if (startStr !== null && endStr !== null && startStr.length == 8 && endStr.length == 8) { - var startDate = moment(startStr, "YYYYMMDD"); - var endDate = moment(endStr, "YYYYMMDD"); + var startDate = moment(startStr, "YYYY-MM-DD"); + var endDate = moment(endStr, "YYYY-MM-DD"); var datePicker = $('#inputDateRange').data('daterangepicker'); datePicker.setStartDate(startDate); datePicker.setEndDate(endDate); diff --git a/public/js/rules/create-edit.js b/public/js/rules/create-edit.js index 13c4027f10..9a4c0c812c 100644 --- a/public/js/rules/create-edit.js +++ b/public/js/rules/create-edit.js @@ -11,8 +11,7 @@ var actionCount = 0; $(function () { "use strict"; - console.log("create-edit"); - + console.log('edit-create'); }); @@ -20,22 +19,96 @@ function addNewTrigger() { "use strict"; triggerCount++; - $.getJSON('json/trigger', {count: triggerCount}).success(function (data) { - //console.log(data.html); + $.getJSON('json/trigger', {count: triggerCount}).done(function (data) { $('tbody.rule-trigger-tbody').append(data.html); + + $('.remove-trigger').unbind('click').click(function (e) { + removeTrigger(e); + + return false; + }); + }).fail(function () { alert('Cannot get a new trigger.'); }); + } function addNewAction() { "use strict"; actionCount++; - $.getJSON('json/action', {count: actionCount}).success(function (data) { + $.getJSON('json/action', {count: actionCount}).done(function (data) { //console.log(data.html); $('tbody.rule-action-tbody').append(data.html); + + // add action things. + $('.remove-action').unbind('click').click(function (e) { + removeAction(e); + + return false; + }); + }).fail(function () { alert('Cannot get a new action.'); }); +} + +function removeTrigger(e) { + "use strict"; + var target = $(e.target); + if(target.prop("tagName") == "I") { + target = target.parent(); + } + // remove grand parent: + target.parent().parent().remove(); + + // if now at zero, immediatly add one again: + if($('.rule-trigger-tbody tr').length == 0) { + addNewTrigger(); + } +} + +function removeAction(e) { + "use strict"; + var target = $(e.target); + if(target.prop("tagName") == "I") { + target = target.parent(); + } + // remove grand parent: + target.parent().parent().remove(); + + // if now at zero, immediatly add one again: + if($('.rule-action-tbody tr').length == 0) { + addNewAction(); + } +} + +function testRuleTriggers() { + "use strict"; + + // Serialize all trigger data + var triggerData = $( ".rule-trigger-tbody" ).find( "input[type=text], input[type=checkbox], select" ).serializeArray(); + + // Find a list of existing transactions that match these triggers + $.get('rules/rules/test_triggers', triggerData).done(function (data) { + var modal = $( "#testTriggerModal" ); + var numTriggers = $( ".rule-trigger-body > tr" ).length; + + // Set title and body + modal.find( ".transactions-list" ).html(data.html); + + // Show warning if appropriate + if( data.warning ) { + modal.find( ".transaction-warning .warning-contents" ).text(data.warning); + modal.find( ".transaction-warning" ).show(); + } else { + modal.find( ".transaction-warning" ).hide(); + } + + // Show the modal dialog + $( "#testTriggerModal" ).modal(); + }).fail(function () { + alert('Cannot get transactions for given triggers.'); + }); } \ No newline at end of file diff --git a/public/js/rules/create.js b/public/js/rules/create.js index e9bf3776bc..4910378c74 100644 --- a/public/js/rules/create.js +++ b/public/js/rules/create.js @@ -11,7 +11,7 @@ $(function () { "use strict"; - console.log("edit"); + console.log("create"); if (triggerCount === 0) { addNewTrigger(); } @@ -31,4 +31,10 @@ $(function () { return false; }); + + $('.test_rule_triggers').click(function () { + testRuleTriggers(); + + return false; + }); }); diff --git a/public/js/rules/edit.js b/public/js/rules/edit.js new file mode 100644 index 0000000000..39876d8c2c --- /dev/null +++ b/public/js/rules/edit.js @@ -0,0 +1,51 @@ +/* + * edit.js + * Copyright (C) 2016 Sander Dorigo + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +$(function () { + "use strict"; + console.log("edit"); + + if (triggerCount === 0) { + addNewTrigger(); + } + if (actionCount === 0) { + addNewAction(); + } + + + $('.add_rule_trigger').click(function () { + addNewTrigger(); + + return false; + }); + + $('.test_rule_triggers').click(function () { + testRuleTriggers(); + + return false; + }); + + $('.add_rule_action').click(function () { + addNewAction(); + + return false; + }); + + $('.remove-trigger').unbind('click').click(function (e) { + removeTrigger(e); + + return false; + }); + + // add action things. + $('.remove-action').unbind('click').click(function (e) { + removeAction(e); + + return false; + }); +}); \ No newline at end of file diff --git a/public/js/transactions/create-edit.js b/public/js/transactions/create-edit.js index 974a2b14de..9adef04a04 100644 --- a/public/js/transactions/create-edit.js +++ b/public/js/transactions/create-edit.js @@ -9,13 +9,13 @@ $(document).ready(function () { "use strict"; if ($('input[name="expense_account"]').length > 0) { - $.getJSON('json/expense-accounts').success(function (data) { + $.getJSON('json/expense-accounts').done(function (data) { $('input[name="expense_account"]').typeahead({source: data}); }); } if ($('input[name="tags"]').length > 0) { - $.getJSON('json/tags').success(function (data) { + $.getJSON('json/tags').done(function (data) { var opt = { typeahead: { source: data @@ -28,19 +28,19 @@ $(document).ready(function () { } if ($('input[name="revenue_account"]').length > 0) { - $.getJSON('json/revenue-accounts').success(function (data) { + $.getJSON('json/revenue-accounts').done(function (data) { $('input[name="revenue_account"]').typeahead({source: data}); }); } if ($('input[name="description"]').length > 0 && what !== undefined) { - $.getJSON('json/transaction-journals/' + what).success(function (data) { + $.getJSON('json/transaction-journals/' + what).done(function (data) { $('input[name="description"]').typeahead({source: data}); }); } if ($('input[name="category"]').length > 0) { - $.getJSON('json/categories').success(function (data) { + $.getJSON('json/categories').done(function (data) { $('input[name="category"]').typeahead({source: data}); }); } diff --git a/public/manifest.json b/public/manifest.json index ba55871a27..5881f59e5c 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,41 +1,45 @@ { - "name": "geld.nder.be", - "icons": [ - { - "src": "\/android-chrome-36x36.png?v=Lb54KlrQnz", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/android-chrome-48x48.png?v=Lb54KlrQnz", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/android-chrome-72x72.png?v=Lb54KlrQnz", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/android-chrome-96x96.png?v=Lb54KlrQnz", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/android-chrome-144x144.png?v=Lb54KlrQnz", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "\/android-chrome-192x192.png?v=Lb54KlrQnz", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - } - ] + "name": "Firefly III", + "short_name": "Firefly III", + "icons": [ + { + "src": "\/android-chrome-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": 0.75 + }, + { + "src": "\/android-chrome-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": 1 + }, + { + "src": "\/android-chrome-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": 1.5 + }, + { + "src": "\/android-chrome-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": 2 + }, + { + "src": "\/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": 3 + }, + { + "src": "\/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": 4 + } + ], + "start_url": "/", + "display": "standalone", + "orientation": "portrait" } diff --git a/public/mstile-144x144.png b/public/mstile-144x144.png index 9b58f4fff1..d27ae1b177 100644 Binary files a/public/mstile-144x144.png and b/public/mstile-144x144.png differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png index f8a3ee894d..e03a93a3fc 100644 Binary files a/public/mstile-150x150.png and b/public/mstile-150x150.png differ diff --git a/public/mstile-310x150.png b/public/mstile-310x150.png index 8293e6f1ab..2c17b9eee8 100644 Binary files a/public/mstile-310x150.png and b/public/mstile-310x150.png differ diff --git a/public/mstile-310x310.png b/public/mstile-310x310.png index ef0c267960..ef719f84ca 100644 Binary files a/public/mstile-310x310.png and b/public/mstile-310x310.png differ diff --git a/public/mstile-70x70.png b/public/mstile-70x70.png index 9f99178f61..3f334b60a3 100644 Binary files a/public/mstile-70x70.png and b/public/mstile-70x70.png differ diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 0000000000..5803c22658 --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,48 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/resources/lang/en_US/breadcrumbs.php b/resources/lang/en_US/breadcrumbs.php index 3ba7372b38..998f2fd788 100644 --- a/resources/lang/en_US/breadcrumbs.php +++ b/resources/lang/en_US/breadcrumbs.php @@ -1,60 +1,45 @@ 'Home', - - // accounts 'cash_accounts' => 'Cash accounts', 'edit_account' => 'Edit account ":name"', - - // currencies 'edit_currency' => 'Edit currencies ":name"', 'delete_currency' => 'Delete currencies ":name"', - - // piggy banks 'newPiggyBank' => 'Create a new piggy bank', 'edit_piggyBank' => 'Edit piggy bank ":name"', - - // top menu 'preferences' => 'Preferences', 'profile' => 'Profile', 'changePassword' => 'Change your password', - - // bills 'bills' => 'Bills', 'newBill' => 'New bill', 'edit_bill' => 'Edit bill ":name"', 'delete_bill' => 'Delete bill ":name"', - - // reports 'reports' => 'Reports', 'monthly_report' => 'Monthly report for :date', 'monthly_report_shared' => 'Monthly report for :date (including shared accounts)', 'yearly_report' => 'Yearly report for :date', 'yearly_report_shared' => 'Yearly report for :date (including shared accounts)', 'budget_report' => 'Budget report for :date', - - // search 'searchResult' => 'Search for ":query"', - - // transaction lists. 'withdrawal_list' => 'Expenses', 'deposit_list' => 'Revenue, income and deposits', 'transfer_list' => 'Transfers', 'transfers_list' => 'Transfers', - - // create transactions 'create_withdrawal' => 'Create new withdrawal', 'create_deposit' => 'Create new deposit', 'create_transfer' => 'Create new transfer', - - // edit transactions 'edit_journal' => 'Edit transaction ":description"', 'delete_journal' => 'Delete transaction ":description"', - - // tags 'tags' => 'Tags', 'createTag' => 'Create new tag', 'edit_tag' => 'Edit tag ":tag"', 'delete_tag' => 'Delete tag ":tag"', - ]; diff --git a/resources/lang/en_US/config.php b/resources/lang/en_US/config.php index 6a4b42f53e..9c74ef9af6 100644 --- a/resources/lang/en_US/config.php +++ b/resources/lang/en_US/config.php @@ -1,8 +1,21 @@ 'en, English, en_US, en_US.utf8', - 'month' => '%B %Y', - 'month_and_day' => '%B %e, %Y', + 'locale' => 'en, English, en_US, en_US.utf8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Week %W, %Y', + 'quarter_of_year' => '%B %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 21ec2c6f79..7f81a49fee 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1,111 +1,177 @@ 'This language is not yet fully translated', - 'test' => 'You have selected English.', - 'close' => 'Close', - 'pleaseHold' => 'Please hold...', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'total_sum' => 'Total sum', - 'period_sum' => 'Sum for period', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', - 'bounced_error' => 'The message sent to :email bounced, so no access for you.', - 'deleted_error' => 'These credentials do not match our records.', - 'general_blocked_error' => 'Your account has been disabled, so you cannot login.', - 'removed_amount' => 'Removed :amount', - 'added_amount' => 'Added :amount', - 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', - 'Opening balance' => 'Opening balance', - 'create_new_stuff' => 'Create new stuff', - 'new_withdrawal' => 'New withdrawal', - 'new_deposit' => 'New deposit', - 'new_transfer' => 'New transfer', - 'new_asset_account' => 'New asset account', - 'new_expense_account' => 'New expense account', - 'new_revenue_account' => 'New revenue account', - 'new_budget' => 'New budget', - 'new_bill' => 'New bill', + 'language_incomplete' => 'This language is not yet fully translated', + 'test' => 'You have selected English.', + 'close' => 'Close', + 'pleaseHold' => 'Please hold...', + 'actions' => 'Actions', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'welcomeBack' => 'What\'s playing?', + 'everything' => 'Everything', + 'customRange' => 'Custom range', + 'apply' => 'Apply', + 'cancel' => 'Cancel', + 'from' => 'From', + 'to' => 'To', + 'total_sum' => 'Total sum', + 'period_sum' => 'Sum for period', + 'showEverything' => 'Show everything', + 'never' => 'Never', + 'search_results_for' => 'Search results for ":query"', + 'bounced_error' => 'The message sent to :email bounced, so no access for you.', + 'deleted_error' => 'These credentials do not match our records.', + 'general_blocked_error' => 'Your account has been disabled, so you cannot login.', + 'removed_amount' => 'Removed :amount', + 'added_amount' => 'Added :amount', + 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', + 'Opening balance' => 'Opening balance', + 'create_new_stuff' => 'Create new stuff', + 'new_withdrawal' => 'New withdrawal', + 'new_deposit' => 'New deposit', + 'new_transfer' => 'New transfer', + 'new_asset_account' => 'New asset account', + 'new_expense_account' => 'New expense account', + 'new_revenue_account' => 'New revenue account', + 'new_budget' => 'New budget', + 'new_bill' => 'New bill', + 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', + 'flash_success' => 'Success!', + 'flash_info' => 'Message', + 'flash_warning' => 'Warning!', + 'flash_error' => 'Error!', + 'flash_info_multiple' => 'There is one message|There are :count messages', + 'flash_error_multiple' => 'There is one error|There are :count errors', + 'net_worth' => 'Net worth', + 'route_has_no_help' => 'There is no help for this route, or there is no help available in your language.', + 'two_factor_welcome' => 'Hello, :user!', + 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', + 'two_factor_code_here' => 'Enter code here', + 'authenticate' => 'Authenticate', + 'two_factor_forgot' => 'I forgot my two-factor thing.', + 'two_factor_lost_header' => 'Lost your two factor authentication?', + 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions.', + 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', + + // export data: + 'import_and_export' => 'Import and export', + 'export_data' => 'Export data', + 'export_data_intro' => 'For backup purposes, when migrating to another system or when migrating to another Firefly III installation.', + 'export_format' => 'Export format', + 'export_format_csv' => 'Comma separated values (CSV file)', + 'export_format_mt940' => 'MT940 compatible format', + 'export_included_accounts' => 'Export transactions from these accounts', + 'include_config_help' => 'For easy re-import into Firefly III', + 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', + 'do_export' => 'Export', + 'export_status_never_started' => 'The export has not started yet', + 'export_status_make_exporter' => 'Creating exporter thing...', + 'export_status_collecting_journals' => 'Collecting your transactions...', + 'export_status_collected_journals' => 'Collected your transactions!', + 'export_status_converting_to_export_format' => 'Converting your transactions...', + 'export_status_converted_to_export_format' => 'Converted your transactions!', + 'export_status_creating_journal_file' => 'Creating the export file...', + 'export_status_created_journal_file' => 'Created the export file!', + 'export_status_collecting_attachments' => 'Collecting all your attachments...', + 'export_status_collected_attachments' => 'Collected all your attachments!', + 'export_status_collecting_old_uploads' => 'Collecting all your previous uploads...', + 'export_status_collected_old_uploads' => 'Collected all your previous uploads!', + 'export_status_creating_config_file' => 'Creating a configuration file...', + 'export_status_created_config_file' => 'Created a configuration file!', + 'export_status_creating_zip_file' => 'Creating a zip file...', + 'export_status_created_zip_file' => 'Created a zip file!', + 'export_status_finished' => 'Export has succesfully finished! Yay!', + 'export_data_please_wait' => 'Please wait...', + 'attachment_explanation' => 'The file called \':attachment_name\' (#:attachment_id) was originally uploaded to :type \':description\' (#:journal_id) dated :date for the amount of :amount.', // rules - 'rules' => 'Rules', - 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', - 'rule_name' => 'Name of rule', - 'rule_triggers' => 'Rule triggers when', - 'rule_actions' => 'Rule will', - 'new_rule' => 'New rule', - 'new_rule_group' => 'New rule group', - 'rule_priority_up' => 'Give rule more priority', - 'rule_priority_down' => 'Give rule less priority', - 'make_new_rule_group' => 'Make new rule group', - 'store_new_rule_group' => 'Store new rule group', - 'created_new_rule_group' => 'New rule group ":title" stored!', - 'updated_rule_group' => 'Successfully updated rule group ":title".', - 'edit_rule_group' => 'Edit rule group ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - 'deleted_rule_group' => 'Deleted rule group ":title"', - 'update_rule_group' => 'Update rule group', - 'no_rules_in_group' => 'There are no rules in this group', - 'move_rule_group_up' => 'Move rule group up', - 'move_rule_group_down' => 'Move rule group down', - 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', - 'make_new_rule' => 'Make new rule in rule group ":title"', - 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', - 'rule_help_active' => 'Inactive rules will never fire.', - 'stored_new_rule' => 'Stored new rule with title ":title"', - 'deleted_rule' => 'Deleted rule with title ":title"', - 'store_new_rule' => 'Store new rule', - 'updated_rule' => 'Updated rule with title ":title"', - 'default_rule_group_name' => 'Default rules', - 'default_rule_group_description' => 'All your rules not in a particular group.', - 'default_rule_name' => 'Your first default rule', - 'default_rule_description' => 'This rule is an example. You can safely delete it.', - 'default_rule_trigger_description' => 'The Man Who Sold the World', - 'default_rule_trigger_from_account' => 'David Bowie', - 'default_rule_action_prepend' => 'Bought the world from ', - 'default_rule_action_set_category' => 'Large expenses', - - 'trigger' => 'Trigger', - 'trigger_value' => 'Trigger on value', - 'stop_processing_other_triggers' => 'Stop processing other triggers', - 'add_rule_trigger' => 'Add new trigger', - 'action' => 'Action', - 'action_value' => 'Action value', - 'stop_executing_other_actions' => 'Stop executing other actions', - 'add_rule_action' => 'Add new action', - 'edit_rule' => 'Edit rule ":title"', - 'update_rule' => 'Update rule', + 'rules' => 'Rules', + 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', + 'rule_name' => 'Name of rule', + 'rule_triggers' => 'Rule triggers when', + 'rule_actions' => 'Rule will', + 'new_rule' => 'New rule', + 'new_rule_group' => 'New rule group', + 'rule_priority_up' => 'Give rule more priority', + 'rule_priority_down' => 'Give rule less priority', + 'make_new_rule_group' => 'Make new rule group', + 'store_new_rule_group' => 'Store new rule group', + 'created_new_rule_group' => 'New rule group ":title" stored!', + 'updated_rule_group' => 'Successfully updated rule group ":title".', + 'edit_rule_group' => 'Edit rule group ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'deleted_rule_group' => 'Deleted rule group ":title"', + 'update_rule_group' => 'Update rule group', + 'no_rules_in_group' => 'There are no rules in this group', + 'move_rule_group_up' => 'Move rule group up', + 'move_rule_group_down' => 'Move rule group down', + 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', + 'make_new_rule' => 'Make new rule in rule group ":title"', + 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', + 'rule_help_active' => 'Inactive rules will never fire.', + 'stored_new_rule' => 'Stored new rule with title ":title"', + 'deleted_rule' => 'Deleted rule with title ":title"', + 'store_new_rule' => 'Store new rule', + 'updated_rule' => 'Updated rule with title ":title"', + 'default_rule_group_name' => 'Default rules', + 'default_rule_group_description' => 'All your rules not in a particular group.', + 'default_rule_name' => 'Your first default rule', + 'default_rule_description' => 'This rule is an example. You can safely delete it.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Large expenses', + 'trigger' => 'Trigger', + 'trigger_value' => 'Trigger on value', + 'stop_processing_other_triggers' => 'Stop processing other triggers', + 'add_rule_trigger' => 'Add new trigger', + 'action' => 'Action', + 'action_value' => 'Action value', + 'stop_executing_other_actions' => 'Stop executing other actions', + 'add_rule_action' => 'Add new action', + 'edit_rule' => 'Edit rule ":title"', + 'delete_rule' => 'Delete rule ":title"', + 'update_rule' => 'Update rule', + 'test_rule_triggers' => 'See matching transactions', + 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions', + 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', + 'warning_no_valid_triggers' => 'No valid triggers provided.', + 'execute_on_existing_transactions' => 'Execute for existing transactions', + 'execute_on_existing_transactions_intro' => 'When a rule or group has been changed or added, you can execute it for existing transactions', + 'execute_on_existing_transactions_short' => 'Existing transactions', + 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', + 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions', + 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'execute' => 'Execute', // actions and triggers - 'rule_trigger_user_action' => 'User action is ":trigger_value"', - 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', - 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', - 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', - 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', - 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', - 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', - 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', - 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', - 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', - 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', - 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', - 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', - 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', - 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', - 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', - 'rule_trigger_description_is' => 'Description is ":trigger_value"', - + 'rule_trigger_user_action' => 'User action is ":trigger_value"', + 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', + 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', + 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', + 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', + 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', + 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', + 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', + 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', + 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', + 'rule_trigger_description_is' => 'Description is ":trigger_value"', 'rule_trigger_from_account_starts_choice' => 'Source account starts with..', 'rule_trigger_from_account_ends_choice' => 'Source account ends with..', 'rule_trigger_from_account_is_choice' => 'Source account is..', @@ -122,486 +188,498 @@ return [ 'rule_trigger_description_ends_choice' => 'Description ends with..', 'rule_trigger_description_contains_choice' => 'Description contains..', 'rule_trigger_description_is_choice' => 'Description is..', - - 'rule_trigger_store_journal' => 'When a journal is created', - 'rule_trigger_update_journal' => 'When a journal is updated', - - 'rule_action_set_category' => 'Set category to ":action_value"', - 'rule_action_clear_category' => 'Clear category', - 'rule_action_set_budget' => 'Set budget to ":action_value"', - 'rule_action_clear_budget' => 'Clear budget', - 'rule_action_add_tag' => 'Add tag ":action_value"', - 'rule_action_remove_tag' => 'Remove tag ":action_value"', - 'rule_action_remove_all_tags' => 'Remove all tags', - 'rule_action_set_description' => 'Set description to ":action_value"', - 'rule_action_append_description' => 'Append description with ":action_value"', - 'rule_action_prepend_description' => 'Prepend description with ":action_value"', - - 'rule_action_set_category_choice' => 'Set category to..', - 'rule_action_clear_category_choice' => 'Clear any category', - 'rule_action_set_budget_choice' => 'Set budget to..', - 'rule_action_clear_budget_choice' => 'Clear any budget', - 'rule_action_add_tag_choice' => 'Add tag..', - 'rule_action_remove_tag_choice' => 'Remove tag..', - 'rule_action_remove_all_tags_choice' => 'Remove all tags', - 'rule_action_set_description_choice' => 'Set description to..', - 'rule_action_append_description_choice' => 'Append description with..', - 'rule_action_prepend_description_choice' => 'Prepend description with..', + 'rule_trigger_store_journal' => 'When a journal is created', + 'rule_trigger_update_journal' => 'When a journal is updated', + 'rule_action_set_category' => 'Set category to ":action_value"', + 'rule_action_clear_category' => 'Clear category', + 'rule_action_set_budget' => 'Set budget to ":action_value"', + 'rule_action_clear_budget' => 'Clear budget', + 'rule_action_add_tag' => 'Add tag ":action_value"', + 'rule_action_remove_tag' => 'Remove tag ":action_value"', + 'rule_action_remove_all_tags' => 'Remove all tags', + 'rule_action_set_description' => 'Set description to ":action_value"', + 'rule_action_append_description' => 'Append description with ":action_value"', + 'rule_action_prepend_description' => 'Prepend description with ":action_value"', + 'rule_action_set_category_choice' => 'Set category to..', + 'rule_action_clear_category_choice' => 'Clear any category', + 'rule_action_set_budget_choice' => 'Set budget to..', + 'rule_action_clear_budget_choice' => 'Clear any budget', + 'rule_action_add_tag_choice' => 'Add tag..', + 'rule_action_remove_tag_choice' => 'Remove tag..', + 'rule_action_remove_all_tags_choice' => 'Remove all tags', + 'rule_action_set_description_choice' => 'Set description to..', + 'rule_action_append_description_choice' => 'Append description with..', + 'rule_action_prepend_description_choice' => 'Prepend description with..', // tags - 'store_new_tag' => 'Store new tag', - 'update_tag' => 'Update tag', - 'no_location_set' => 'No location set.', - 'meta_data' => 'Meta data', - 'location' => 'Location', + 'store_new_tag' => 'Store new tag', + 'update_tag' => 'Update tag', + 'no_location_set' => 'No location set.', + 'meta_data' => 'Meta data', + 'location' => 'Location', // preferences - 'pref_home_screen_accounts' => 'Home screen accounts', - 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', - 'pref_budget_settings' => 'Budget settings', - 'pref_budget_settings_help' => 'What\'s the maximum amount of money a budget envelope may contain?', - 'pref_view_range' => 'View range', - 'pref_view_range_help' => 'Some charts are automatically grouped in periods. What period would you prefer?', - 'pref_1D' => 'One day', - 'pref_1W' => 'One week', - 'pref_1M' => 'One month', - 'pref_3M' => 'Three months (quarter)', - 'pref_6M' => 'Six months', - 'pref_languages' => 'Languages', - 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', - 'pref_custom_fiscal_year' => 'Fiscal year settings', - 'pref_custom_fiscal_year_label' => 'Enabled', - 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', - 'pref_fiscal_year_start_label' => 'Fiscal year start date', - 'pref_save_settings' => 'Save settings', + 'pref_home_screen_accounts' => 'Home screen accounts', + 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', + 'pref_budget_settings' => 'Budget settings', + 'pref_budget_settings_help' => 'What\'s the maximum amount of money a budget envelope may contain?', + 'pref_view_range' => 'View range', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. What period would you prefer?', + 'pref_1D' => 'One day', + 'pref_1W' => 'One week', + 'pref_1M' => 'One month', + 'pref_3M' => 'Three months (quarter)', + 'pref_6M' => 'Six months', + 'pref_languages' => 'Languages', + 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', + 'pref_custom_fiscal_year' => 'Fiscal year settings', + 'pref_custom_fiscal_year_label' => 'Enabled', + 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', + 'pref_fiscal_year_start_label' => 'Fiscal year start date', + 'pref_two_factor_auth' => '2-step verification', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', + 'pref_two_factor_auth_code' => 'Verify code', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Reset verification code', + 'pref_two_factor_auth_remove_code' => 'Remove verification code', + 'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)', + 'pref_save_settings' => 'Save settings', // profile: - 'change_your_password' => 'Change your password', - 'delete_account' => 'Delete account', - 'current_password' => 'Current password', - 'new_password' => 'New password', - 'new_password_again' => 'New password (again)', - 'delete_your_account' => 'Delete your account', - 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', - 'delete_your_account_password' => 'Enter your password to continue.', - 'password' => 'Password', - 'are_you_sure' => 'Are you sure? You cannot undo this.', - 'delete_account_button' => 'DELETE your account', - 'invalid_current_password' => 'Invalid current password!', - 'password_changed' => 'Password changed!', - 'should_change' => 'The idea is to change your password.', - 'invalid_password' => 'Invalid password!', + 'change_your_password' => 'Change your password', + 'delete_account' => 'Delete account', + 'current_password' => 'Current password', + 'new_password' => 'New password', + 'new_password_again' => 'New password (again)', + 'delete_your_account' => 'Delete your account', + 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', + 'delete_your_account_password' => 'Enter your password to continue.', + 'password' => 'Password', + 'are_you_sure' => 'Are you sure? You cannot undo this.', + 'delete_account_button' => 'DELETE your account', + 'invalid_current_password' => 'Invalid current password!', + 'password_changed' => 'Password changed!', + 'should_change' => 'The idea is to change your password.', + 'invalid_password' => 'Invalid password!', // attachments - 'nr_of_attachments' => 'One attachment|:count attachments', - 'attachments' => 'Attachments', - 'edit_attachment' => 'Edit attachment ":name"', - 'update_attachment' => 'Update attachment', - 'delete_attachment' => 'Delete attachment ":name"', - 'attachment_deleted' => 'Deleted attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', + 'nr_of_attachments' => 'One attachment|:count attachments', + 'attachments' => 'Attachments', + 'edit_attachment' => 'Edit attachment ":name"', + 'update_attachment' => 'Update attachment', + 'delete_attachment' => 'Delete attachment ":name"', + 'attachment_deleted' => 'Deleted attachment ":name"', + 'upload_max_file_size' => 'Maximum file size: :size', // tour: - 'prev' => 'Prev', - 'next' => 'Next', - 'end-tour' => 'End tour', - 'pause' => 'Pause', + 'prev' => 'Prev', + 'next' => 'Next', + 'end-tour' => 'End tour', + 'pause' => 'Pause', // transaction index - 'title_expenses' => 'Expenses', - 'title_withdrawal' => 'Expenses', - 'title_revenue' => 'Revenue / income', - 'title_deposit' => 'Revenue / income', - 'title_transfer' => 'Transfers', - 'title_transfers' => 'Transfers', + 'title_expenses' => 'Expenses', + 'title_withdrawal' => 'Expenses', + 'title_revenue' => 'Revenue / income', + 'title_deposit' => 'Revenue / income', + 'title_transfer' => 'Transfers', + 'title_transfers' => 'Transfers', // csv import: - 'csv_import' => 'Import CSV file', - 'csv' => 'CSV', - 'csv_index_title' => 'Upload and import a CSV file', - 'csv_define_column_roles' => 'Define column roles', - 'csv_map_values' => 'Map found values to existing values', - 'csv_download_config' => 'Download CSV configuration file.', - 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', - 'csv_index_beta_warning' => 'This tool is very much in beta. Please proceed with caution', - 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', - 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', - 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', - 'csv_upload_button' => 'Start importing CSV', - 'csv_column_roles_title' => 'Define column roles', - 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', - 'csv_column_roles_table' => 'Column roles', - 'csv_column' => 'CSV column', - 'csv_column_name' => 'CSV column name', - 'csv_column_example' => 'Column example data', - 'csv_column_role' => 'Column contains?', - 'csv_do_map_value' => 'Map value?', - 'csv_continue' => 'Continue to the next step', - 'csv_go_back' => 'Go back to the previous step', - 'csv_map_title' => 'Map found values to existing values', - 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', - 'csv_field_value' => 'Field value from CSV', - 'csv_field_mapped_to' => 'Must be mapped to...', - 'csv_do_not_map' => 'Do not map this value', - 'csv_download_config_title' => 'Download CSV configuration', - 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', - 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', - 'csv_do_download_config' => 'Download configuration file.', - 'csv_empty_description' => '(empty description)', - 'csv_upload_form' => 'CSV upload form', - 'csv_index_unsupported_warning' => 'The CSV importer is yet incapable of doing the following:', - 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', - 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', - 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', - 'csv_process_title' => 'CSV import finished!', - 'csv_process_text' => 'The CSV importer has finished and has processed :rows rows', - 'csv_row' => 'Row', - 'csv_import_with_errors' => 'There was one error.|There were :errors errors.', - 'csv_error_see_logs' => 'Check the log files to see details.', - 'csv_process_new_entries' => 'Firefly has created :imported new transaction(s).', - 'csv_start_over' => 'Import again', - 'csv_to_index' => 'Back home', - 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', - 'csv_column__ignore' => '(ignore this column)', - 'csv_column_account-iban' => 'Asset account (IBAN)', - 'csv_column_account-id' => 'Asset account ID (matching Firefly)', - 'csv_column_account-name' => 'Asset account (name)', - 'csv_column_amount' => 'Amount', - 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'csv_column_bill-id' => 'Bill ID (matching Firefly)', - 'csv_column_bill-name' => 'Bill name', - 'csv_column_budget-id' => 'Budget ID (matching Firefly)', - 'csv_column_budget-name' => 'Budget name', - 'csv_column_category-id' => 'Category ID (matching Firefly)', - 'csv_column_category-name' => 'Category name', - 'csv_column_currency-code' => 'Currency code (ISO 4217)', - 'csv_column_currency-id' => 'Currency ID (matching Firefly)', - 'csv_column_currency-name' => 'Currency name (matching Firefly)', - 'csv_column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'csv_column_date-rent' => 'Rent calculation date', - 'csv_column_date-transaction' => 'Date', - 'csv_column_description' => 'Description', - 'csv_column_opposing-iban' => 'Opposing account (IBAN)', - 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'csv_column_opposing-name' => 'Opposing account (name)', - 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'csv_column_sepa-db' => 'SEPA Direct Debet', - 'csv_column_tags-comma' => 'Tags (comma separated)', - 'csv_column_tags-space' => 'Tags (space separated)', - 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', - 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', - 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', - 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', + 'csv_import' => 'Import CSV file', + 'csv' => 'CSV', + 'csv_index_title' => 'Upload and import a CSV file', + 'csv_define_column_roles' => 'Define column roles', + 'csv_map_values' => 'Map found values to existing values', + 'csv_download_config' => 'Download CSV configuration file.', + 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', + 'csv_index_beta_warning' => 'This tool is very much in beta. Please proceed with caution', + 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', + 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', + 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', + 'csv_upload_button' => 'Start importing CSV', + 'csv_column_roles_title' => 'Define column roles', + 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', + 'csv_column_roles_table' => 'Column roles', + 'csv_column' => 'CSV column', + 'csv_column_name' => 'CSV column name', + 'csv_column_example' => 'Column example data', + 'csv_column_role' => 'Column contains?', + 'csv_do_map_value' => 'Map value?', + 'csv_continue' => 'Continue to the next step', + 'csv_go_back' => 'Go back to the previous step', + 'csv_map_title' => 'Map found values to existing values', + 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', + 'csv_field_value' => 'Field value from CSV', + 'csv_field_mapped_to' => 'Must be mapped to...', + 'csv_do_not_map' => 'Do not map this value', + 'csv_download_config_title' => 'Download CSV configuration', + 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', + 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', + 'csv_do_download_config' => 'Download configuration file.', + 'csv_empty_description' => '(empty description)', + 'csv_upload_form' => 'CSV upload form', + 'csv_index_unsupported_warning' => 'The CSV importer is yet incapable of doing the following:', + 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', + 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', + 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', + 'csv_process_title' => 'CSV import finished!', + 'csv_process_text' => 'The CSV importer has finished and has processed :rows rows', + 'csv_row' => 'Row', + 'csv_import_with_errors' => 'There was one error.|There were :errors errors.', + 'csv_error_see_logs' => 'Check the log files to see details.', + 'csv_process_new_entries' => 'Firefly has created :imported new transaction(s).', + 'csv_start_over' => 'Import again', + 'csv_to_index' => 'Back home', + 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', + 'csv_column__ignore' => '(ignore this column)', + 'csv_column_account-iban' => 'Asset account (IBAN)', + 'csv_column_account-id' => 'Asset account ID (matching Firefly)', + 'csv_column_account-name' => 'Asset account (name)', + 'csv_column_amount' => 'Amount', + 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'csv_column_bill-id' => 'Bill ID (matching Firefly)', + 'csv_column_bill-name' => 'Bill name', + 'csv_column_budget-id' => 'Budget ID (matching Firefly)', + 'csv_column_budget-name' => 'Budget name', + 'csv_column_category-id' => 'Category ID (matching Firefly)', + 'csv_column_category-name' => 'Category name', + 'csv_column_currency-code' => 'Currency code (ISO 4217)', + 'csv_column_currency-id' => 'Currency ID (matching Firefly)', + 'csv_column_currency-name' => 'Currency name (matching Firefly)', + 'csv_column_currency-symbol' => 'Currency symbol (matching Firefly)', + 'csv_column_date-rent' => 'Rent calculation date', + 'csv_column_date-transaction' => 'Date', + 'csv_column_description' => 'Description', + 'csv_column_opposing-iban' => 'Opposing account (IBAN)', + 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', + 'csv_column_opposing-name' => 'Opposing account (name)', + 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', + 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator', + 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', + 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', + 'csv_column_sepa-db' => 'SEPA Direct Debet', + 'csv_column_tags-comma' => 'Tags (comma separated)', + 'csv_column_tags-space' => 'Tags (space separated)', + 'csv_column_account-number' => 'Asset account (account number)', + 'csv_column_opposing-number' => 'Opposing account (account number)', + 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', + 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', + 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', + 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', // create new stuff: - 'create_new_withdrawal' => 'Create new withdrawal', - 'create_new_deposit' => 'Create new deposit', - 'create_new_transfer' => 'Create new transfer', - 'create_new_asset' => 'Create new asset account', - 'create_new_expense' => 'Create new expense account', - 'create_new_revenue' => 'Create new revenue account', - 'create_new_piggy_bank' => 'Create new piggy bank', - 'create_new_bill' => 'Create new bill', + 'create_new_withdrawal' => 'Create new withdrawal', + 'create_new_deposit' => 'Create new deposit', + 'create_new_transfer' => 'Create new transfer', + 'create_new_asset' => 'Create new asset account', + 'create_new_expense' => 'Create new expense account', + 'create_new_revenue' => 'Create new revenue account', + 'create_new_piggy_bank' => 'Create new piggy bank', + 'create_new_bill' => 'Create new bill', // currencies: - 'create_currency' => 'Create a new currency', - 'edit_currency' => 'Edit currency ":name"', - 'store_currency' => 'Store new currency', - 'update_currency' => 'Update currency', - 'new_default_currency' => ':name is now the default currency.', - 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', - 'deleted_currency' => 'Currency :name deleted', - 'created_currency' => 'Currency :name created', - 'updated_currency' => 'Currency :name updated', - 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', - 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', - 'make_default_currency' => 'make default', - 'default_currency' => 'default', + 'create_currency' => 'Create a new currency', + 'edit_currency' => 'Edit currency ":name"', + 'store_currency' => 'Store new currency', + 'update_currency' => 'Update currency', + 'new_default_currency' => ':name is now the default currency.', + 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', + 'deleted_currency' => 'Currency :name deleted', + 'created_currency' => 'Currency :name created', + 'updated_currency' => 'Currency :name updated', + 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', + 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', + 'make_default_currency' => 'make default', + 'default_currency' => 'default', // new user: - 'submit' => 'Submit', - 'getting_started' => 'Getting started', - 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', - 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', - 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', + 'submit' => 'Submit', + 'getting_started' => 'Getting started', + 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', + 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', + 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - 'something' => 'Something!', + 'mandatoryFields' => 'Mandatory fields', + 'optionalFields' => 'Optional fields', + 'options' => 'Options', + 'something' => 'Something!', // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => ' Store new budget', - 'availableIn' => 'Available in :date', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', - 'createBudget' => 'New budget', - 'inactiveBudgets' => 'Inactive budgets', - 'without_budget_between' => 'Transactions without a budget between :start and :end', - 'budget_in_month' => ':name in :month', - 'delete_budget' => 'Delete budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', + 'create_new_budget' => 'Create a new budget', + 'store_new_budget' => ' Store new budget', + 'availableIn' => 'Available in :date', + 'transactionsWithoutBudget' => 'Expenses without budget', + 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', + 'createBudget' => 'New budget', + 'inactiveBudgets' => 'Inactive budgets', + 'without_budget_between' => 'Transactions without a budget between :start and :end', + 'budget_in_month' => ':name in :month', + 'delete_budget' => 'Delete budget ":name"', + 'edit_budget' => 'Edit budget ":name"', + 'update_amount' => 'Update amount', + 'update_budget' => 'Update budget', // bills: - 'delete_bill' => 'Delete bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'update_bill' => 'Update bill', - 'store_new_bill' => 'Store new bill', + 'delete_bill' => 'Delete bill ":name"', + 'edit_bill' => 'Edit bill ":name"', + 'update_bill' => 'Update bill', + 'store_new_bill' => 'Store new bill', // accounts: - 'details_for_asset' => 'Details for asset account ":name"', - 'details_for_expense' => 'Details for expense account ":name"', - 'details_for_revenue' => 'Details for revenue account ":name"', - 'details_for_cash' => 'Details for cash account ":name"', - 'store_new_asset_account' => 'Store new asset account', - 'store_new_expense_account' => 'Store new expense account', - 'store_new_revenue_account' => 'Store new revenue account', - 'edit_asset_account' => 'Edit asset account ":name"', - 'edit_expense_account' => 'Edit expense account ":name"', - 'edit_revenue_account' => 'Edit revenue account ":name"', - 'delete_asset_account' => 'Delete asset account ":name"', - 'delete_expense_account' => 'Delete expense account ":name"', - 'delete_revenue_account' => 'Delete revenue account ":name"', - 'asset_deleted' => 'Successfully deleted asset account ":name"', - 'expense_deleted' => 'Successfully deleted expense account ":name"', - 'revenue_deleted' => 'Successfully deleted revenue account ":name"', - 'update_asset_account' => 'Update asset account', - 'update_expense_account' => 'Update expense account', - 'update_revenue_account' => 'Update revenue account', - 'make_new_asset_account' => 'Create a new asset account', - 'make_new_expense_account' => 'Create a new expense account', - 'make_new_revenue_account' => 'Create a new revenue account', - 'asset_accounts' => 'Asset accounts', - 'expense_accounts' => 'Expense accounts', - 'revenue_accounts' => 'Revenue accounts', - 'accountExtraHelp_asset' => '', - 'accountExtraHelp_expense' => '', - 'accountExtraHelp_revenue' => '', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', + 'details_for_asset' => 'Details for asset account ":name"', + 'details_for_expense' => 'Details for expense account ":name"', + 'details_for_revenue' => 'Details for revenue account ":name"', + 'details_for_cash' => 'Details for cash account ":name"', + 'store_new_asset_account' => 'Store new asset account', + 'store_new_expense_account' => 'Store new expense account', + 'store_new_revenue_account' => 'Store new revenue account', + 'edit_asset_account' => 'Edit asset account ":name"', + 'edit_expense_account' => 'Edit expense account ":name"', + 'edit_revenue_account' => 'Edit revenue account ":name"', + 'delete_asset_account' => 'Delete asset account ":name"', + 'delete_expense_account' => 'Delete expense account ":name"', + 'delete_revenue_account' => 'Delete revenue account ":name"', + 'asset_deleted' => 'Successfully deleted asset account ":name"', + 'expense_deleted' => 'Successfully deleted expense account ":name"', + 'revenue_deleted' => 'Successfully deleted revenue account ":name"', + 'update_asset_account' => 'Update asset account', + 'update_expense_account' => 'Update expense account', + 'update_revenue_account' => 'Update revenue account', + 'make_new_asset_account' => 'Create a new asset account', + 'make_new_expense_account' => 'Create a new expense account', + 'make_new_revenue_account' => 'Create a new revenue account', + 'asset_accounts' => 'Asset accounts', + 'expense_accounts' => 'Expense accounts', + 'revenue_accounts' => 'Revenue accounts', + 'accountExtraHelp_asset' => '', + 'accountExtraHelp_expense' => '', + 'accountExtraHelp_revenue' => '', + 'account_type' => 'Account type', + 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Wijzig categorie', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'store_category' => 'Store new category', - 'without_category_between' => 'Without category between :start and :end', + 'new_category' => 'New category', + 'create_new_category' => 'Create a new category', + 'without_category' => 'Without a category', + 'update_category' => 'Wijzig categorie', + 'categories' => 'Categories', + 'edit_category' => 'Edit category ":name"', + 'no_category' => '(no category)', + 'category' => 'Category', + 'delete_category' => 'Delete category ":name"', + 'store_category' => 'Store new category', + 'without_category_between' => 'Without category between :start and :end', // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', + 'update_withdrawal' => 'Update withdrawal', + 'update_deposit' => 'Update deposit', + 'update_transfer' => 'Update transfer', + 'delete_withdrawal' => 'Delete withdrawal ":description"', + 'delete_deposit' => 'Delete deposit ":description"', + 'delete_transfer' => 'Delete transfer ":description"', // new user: - 'welcome' => 'Welcome to Firefly!', - 'createNewAsset' => 'Create a new asset account to get started. ' . - 'This will allow you to create transactions and start your financial management', - 'createNewAssetButton' => 'Create new asset account', + 'welcome' => 'Welcome to Firefly!', + 'createNewAsset' => 'Create a new asset account to get started. ' . + 'This will allow you to create transactions and start your financial management', + 'createNewAssetButton' => 'Create new asset account', // home page: - 'yourAccounts' => 'Your accounts', - 'budgetsAndSpending' => 'Budgets and spending', - 'savings' => 'Savings', - 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', - 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', - 'newWithdrawal' => 'New expense', - 'newDeposit' => 'New deposit', - 'newTransfer' => 'New transfer', - 'moneyIn' => 'Money in', - 'moneyOut' => 'Money out', - 'billsToPay' => 'Bills to pay', - 'billsPaid' => 'Bills paid', - 'viewDetails' => 'View details', - 'divided' => 'divided', - 'toDivide' => 'left to divide', + 'yourAccounts' => 'Your accounts', + 'budgetsAndSpending' => 'Budgets and spending', + 'savings' => 'Savings', + 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', + 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', + 'newWithdrawal' => 'New expense', + 'newDeposit' => 'New deposit', + 'newTransfer' => 'New transfer', + 'moneyIn' => 'Money in', + 'moneyOut' => 'Money out', + 'billsToPay' => 'Bills to pay', + 'billsPaid' => 'Bills paid', + 'viewDetails' => 'View details', + 'divided' => 'divided', + 'toDivide' => 'left to divide', // menu and titles, should be recycled as often as possible: - 'toggleNavigation' => 'Toggle navigation', - 'currency' => 'Currency', - 'preferences' => 'Preferences', - 'logout' => 'Logout', - 'searchPlaceholder' => 'Search...', - 'dashboard' => 'Dashboard', - 'currencies' => 'Currencies', - 'accounts' => 'Accounts', - 'Asset account' => 'Asset account', - 'Default account' => 'Asset account', - 'Expense account' => 'Expense account', - 'Revenue account' => 'Revenue account', - 'Initial balance account' => 'Initial balance account', - 'budgets' => 'Budgets', - 'tags' => 'Tags', - 'reports' => 'Reports', - 'transactions' => 'Transactions', - 'expenses' => 'Expenses', - 'income' => 'Revenue / income', - 'transfers' => 'Transfers', - 'moneyManagement' => 'Money management', - 'piggyBanks' => 'Piggy banks', - 'bills' => 'Bills', - 'createNew' => 'Create new', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'newBalance' => 'New balance', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'weekly' => 'Weekly', - 'monthly' => 'Monthly', - 'quarterly' => 'Quarterly', - 'half-year' => 'Every six months', - 'yearly' => 'Yearly', - 'profile' => 'Profile', + 'toggleNavigation' => 'Toggle navigation', + 'currency' => 'Currency', + 'preferences' => 'Preferences', + 'logout' => 'Logout', + 'searchPlaceholder' => 'Search...', + 'dashboard' => 'Dashboard', + 'currencies' => 'Currencies', + 'accounts' => 'Accounts', + 'Asset account' => 'Asset account', + 'Default account' => 'Asset account', + 'Expense account' => 'Expense account', + 'Revenue account' => 'Revenue account', + 'Initial balance account' => 'Initial balance account', + 'budgets' => 'Budgets', + 'tags' => 'Tags', + 'reports' => 'Reports', + 'transactions' => 'Transactions', + 'expenses' => 'Expenses', + 'income' => 'Revenue / income', + 'transfers' => 'Transfers', + 'moneyManagement' => 'Money management', + 'piggyBanks' => 'Piggy banks', + 'bills' => 'Bills', + 'createNew' => 'Create new', + 'withdrawal' => 'Withdrawal', + 'deposit' => 'Deposit', + 'account' => 'Account', + 'transfer' => 'Transfer', + 'Withdrawal' => 'Withdrawal', + 'Deposit' => 'Deposit', + 'Transfer' => 'Transfer', + 'bill' => 'Bill', + 'yes' => 'Yes', + 'no' => 'No', + 'amount' => 'Amount', + 'newBalance' => 'New balance', + 'overview' => 'Overview', + 'saveOnAccount' => 'Save on account', + 'unknown' => 'Unknown', + 'daily' => 'Daily', + 'weekly' => 'Weekly', + 'monthly' => 'Monthly', + 'quarterly' => 'Quarterly', + 'half-year' => 'Every six months', + 'yearly' => 'Yearly', + 'profile' => 'Profile', // reports: - 'report_default' => 'Default financial report for :start until :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'report_this_month_quick' => 'Current month, all accounts', - 'report_this_year_quick' => 'Current year, all accounts', - 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', - 'report_all_time_quick' => 'All-time, all accounts', - 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', - 'incomeVsExpenses' => 'Income vs. expenses', - 'accountBalances' => 'Account balances', - 'balanceStartOfYear' => 'Balance at start of year', - 'balanceEndOfYear' => 'Balance at end of year', - 'balanceStartOfMonth' => 'Balance at start of month', - 'balanceEndOfMonth' => 'Balance at end of month', - 'balanceStart' => 'Balance at start of period', - 'balanceEnd' => 'Balance at end of period', - 'reportsOwnAccounts' => 'Reports for your own accounts', - 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', - 'splitByAccount' => 'Split by account', - 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', - 'coveredWithTags' => 'Covered with tags', - 'leftUnbalanced' => 'Left unbalanced', - 'expectedBalance' => 'Expected balance', - 'outsideOfBudgets' => 'Outside of budgets', - 'leftInBudget' => 'Left in budget', - 'sumOfSums' => 'Sum of sums', - 'noCategory' => '(no category)', - 'notCharged' => 'Not charged (yet)', - 'inactive' => 'Inactive', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'showTheRest' => 'Show everything', - 'hideTheRest' => 'Show only the top :number', - 'sum_of_year' => 'Sum of year', - 'sum_of_years' => 'Sum of years', - 'average_of_year' => 'Average of year', - 'average_of_years' => 'Average of years', - 'categories_earned_in_year' => 'Categories (by earnings)', - 'categories_spent_in_year' => 'Categories (by spendings)', - 'report_type' => 'Report type', - 'report_type_default' => 'Default financial report', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', + 'report_default' => 'Default financial report for :start until :end', + 'report_audit' => 'Transaction history overview for :start until :end', + 'quick_link_reports' => 'Quick links', + 'quick_link_default_report' => 'Default financial report', + 'report_this_month_quick' => 'Current month, all accounts', + 'report_this_year_quick' => 'Current year, all accounts', + 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', + 'report_all_time_quick' => 'All-time, all accounts', + 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', + 'incomeVsExpenses' => 'Income vs. expenses', + 'accountBalances' => 'Account balances', + 'balanceStartOfYear' => 'Balance at start of year', + 'balanceEndOfYear' => 'Balance at end of year', + 'balanceStartOfMonth' => 'Balance at start of month', + 'balanceEndOfMonth' => 'Balance at end of month', + 'balanceStart' => 'Balance at start of period', + 'balanceEnd' => 'Balance at end of period', + 'reportsOwnAccounts' => 'Reports for your own accounts', + 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', + 'splitByAccount' => 'Split by account', + 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', + 'coveredWithTags' => 'Covered with tags', + 'leftUnbalanced' => 'Left unbalanced', + 'expectedBalance' => 'Expected balance', + 'outsideOfBudgets' => 'Outside of budgets', + 'leftInBudget' => 'Left in budget', + 'sumOfSums' => 'Sum of sums', + 'noCategory' => '(no category)', + 'notCharged' => 'Not charged (yet)', + 'inactive' => 'Inactive', + 'difference' => 'Difference', + 'in' => 'In', + 'out' => 'Out', + 'topX' => 'top :number', + 'showTheRest' => 'Show everything', + 'hideTheRest' => 'Show only the top :number', + 'sum_of_year' => 'Sum of year', + 'sum_of_years' => 'Sum of years', + 'average_of_year' => 'Average of year', + 'average_of_years' => 'Average of years', + 'categories_earned_in_year' => 'Categories (by earnings)', + 'categories_spent_in_year' => 'Categories (by spendings)', + 'report_type' => 'Report type', + 'report_type_default' => 'Default financial report', + 'report_type_audit' => 'Transaction history overview (audit)', + 'report_included_accounts' => 'Included accounts', + 'report_date_range' => 'Date range', + 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', + 'report_preset_ranges' => 'Pre-set ranges', + 'shared' => 'Shared', + 'fiscal_year' => 'Fiscal year', // charts: - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'noBudget' => '(no budget)', - 'maxAmount' => 'Maximum amount', - 'minAmount' => 'Minumum amount', - 'billEntry' => 'Current bill entry', - 'name' => 'Name', - 'date' => 'Date', - 'paid' => 'Paid', - 'unpaid' => 'Unpaid', - 'day' => 'Day', - 'budgeted' => 'Budgeted', - 'period' => 'Period', - 'balance' => 'Balance', - 'summary' => 'Summary', - 'sum' => 'Sum', - 'average' => 'Average', - 'balanceFor' => 'Balance for :name', + 'dayOfMonth' => 'Day of the month', + 'month' => 'Month', + 'budget' => 'Budget', + 'spent' => 'Spent', + 'earned' => 'Earned', + 'overspent' => 'Overspent', + 'left' => 'Left', + 'noBudget' => '(no budget)', + 'maxAmount' => 'Maximum amount', + 'minAmount' => 'Minumum amount', + 'billEntry' => 'Current bill entry', + 'name' => 'Name', + 'date' => 'Date', + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + 'day' => 'Day', + 'budgeted' => 'Budgeted', + 'period' => 'Period', + 'balance' => 'Balance', + 'summary' => 'Summary', + 'sum' => 'Sum', + 'average' => 'Average', + 'balanceFor' => 'Balance for :name', // piggy banks: - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'Create new piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'account_status' => 'Account status', - 'left_for_piggy_banks' => 'Left for piggy banks', - 'sum_of_piggy_banks' => 'Sum of piggy banks', - 'saved_so_far' => 'Saved so far', - 'left_to_save' => 'Left to save', - 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', - 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', - 'add' => 'Add', - 'remove' => 'Remove', - 'max_amount_add' => 'The maximum amount you can add is', - 'max_amount_remove' => 'The maximum amount you can remove is', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'details' => 'Details', - 'events' => 'Events', - 'target_amount' => 'Target amount', - 'start_date' => 'Start date', - 'target_date' => 'Target date', - 'no_target_date' => 'No target date', - 'todo' => 'to do', - 'table' => 'Table', - 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', - 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', - 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', - 'delete_piggy_bank' => 'Delete piggy bank ":name"', + 'piggy_bank' => 'Piggy bank', + 'new_piggy_bank' => 'Create new piggy bank', + 'store_piggy_bank' => 'Store new piggy bank', + 'account_status' => 'Account status', + 'left_for_piggy_banks' => 'Left for piggy banks', + 'sum_of_piggy_banks' => 'Sum of piggy banks', + 'saved_so_far' => 'Saved so far', + 'left_to_save' => 'Left to save', + 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', + 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', + 'add' => 'Add', + 'remove' => 'Remove', + 'max_amount_add' => 'The maximum amount you can add is', + 'max_amount_remove' => 'The maximum amount you can remove is', + 'update_piggy_button' => 'Update piggy bank', + 'update_piggy_title' => 'Update piggy bank ":name"', + 'details' => 'Details', + 'events' => 'Events', + 'target_amount' => 'Target amount', + 'start_date' => 'Start date', + 'target_date' => 'Target date', + 'no_target_date' => 'No target date', + 'todo' => 'to do', + 'table' => 'Table', + 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', + 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', + 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', + 'delete_piggy_bank' => 'Delete piggy bank ":name"', // tags - 'regular_tag' => 'Just a regular tag.', - 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', - 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', - 'delete_tag' => 'Delete tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'no_year' => 'No year set', - 'no_month' => 'No month set', - 'tag_title_nothing' => 'Default tags', - 'tag_title_balancingAct' => 'Balancing act tags', - 'tag_title_advancePayment' => 'Advance payment tags', - 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', - 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', - 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', + 'regular_tag' => 'Just a regular tag.', + 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', + 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', + 'delete_tag' => 'Delete tag ":tag"', + 'new_tag' => 'Make new tag', + 'edit_tag' => 'Edit tag ":tag"', + 'no_year' => 'No year set', + 'no_month' => 'No month set', + 'tag_title_nothing' => 'Default tags', + 'tag_title_balancingAct' => 'Balancing act tags', + 'tag_title_advancePayment' => 'Advance payment tags', + 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', + 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index d1d398ebc2..5361c8daac 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -1,4 +1,12 @@ 'Revenue account', 'amount' => 'Amount', 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'category' => 'Category', 'tags' => 'Tags', 'deletePermanently' => 'Delete permanently', @@ -47,6 +58,7 @@ return [ 'symbol' => 'Symbol', 'code' => 'Code', 'iban' => 'IBAN', + 'accountNumber' => 'Account number', 'csv' => 'CSV file', 'has_headers' => 'Headers', 'date_format' => 'Date format', @@ -70,40 +82,44 @@ return [ 'size' => 'Size', 'trigger' => 'Trigger', 'stop_processing' => 'Stop processing', - - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - - - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'delete_budget' => 'Delete budget ":name"', - 'delete_category' => 'Delete category ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'delete_journal' => 'Delete transaction with description ":description"', - 'delete_attachment' => 'Delete attachment ":name"', - 'delete_rule' => 'Delete rule ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - - 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', - 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', - 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', - 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', - 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', - 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', - 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', - 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', - 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', - 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', - 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', - 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_config' => 'Include configuration file', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'csv_comma' => 'A comma (,)', + 'csv_semicolon' => 'A semicolon (;)', + 'csv_tab' => 'A tab (invisible)', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Delete bill ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Delete attachment ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', ]; diff --git a/resources/lang/en_US/help.php b/resources/lang/en_US/help.php index 358db49713..4f5b30adc1 100644 --- a/resources/lang/en_US/help.php +++ b/resources/lang/en_US/help.php @@ -1,25 +1,31 @@ 'Welcome to Firefly III', - 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', - 'sidebar-toggle-title' => 'Sidebar to create stuff', - 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', - 'account-menu-title' => 'All your accounts', - 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', - 'budget-menu-title' => 'Budgets', - 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', - 'report-menu-title' => 'Reports', - 'report-menu-text' => 'Check this out when you want a solid overview of your fiances.', - 'transaction-menu-title' => 'Transactions', - 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', - 'option-menu-title' => 'Options', - 'option-menu-text' => 'This is pretty self-explanatory.', - 'main-content-end-title' => 'The end!', - 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', - - + 'main-content-title' => 'Welcome to Firefly III', + 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', + 'sidebar-toggle-title' => 'Sidebar to create stuff', + 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', + 'account-menu-title' => 'All your accounts', + 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', + 'budget-menu-title' => 'Budgets', + 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', + 'report-menu-title' => 'Reports', + 'report-menu-text' => 'Check this out when you want a solid overview of your fiances.', + 'transaction-menu-title' => 'Transactions', + 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', + 'option-menu-title' => 'Options', + 'option-menu-text' => 'This is pretty self-explanatory.', + 'main-content-end-title' => 'The end!', + 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', 'index' => 'index', 'home' => 'home', 'accounts-index' => 'accounts.index', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 4bc0b61bc9..29ebb0b6ca 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -1,6 +1,11 @@ 'Name', @@ -19,6 +24,9 @@ return [ 'description' => 'Description', 'amount' => 'Amount', 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'from' => 'From', 'to' => 'To', 'budget' => 'Budget', diff --git a/resources/lang/en_US/pagination.php b/resources/lang/en_US/pagination.php index fcab34b253..d65fb9c5c0 100644 --- a/resources/lang/en_US/pagination.php +++ b/resources/lang/en_US/pagination.php @@ -1,19 +1,13 @@ '« Previous', 'next' => 'Next »', - ]; diff --git a/resources/lang/en_US/passwords.php b/resources/lang/en_US/passwords.php index 125f093fa8..8690468e9e 100644 --- a/resources/lang/en_US/passwords.php +++ b/resources/lang/en_US/passwords.php @@ -1,22 +1,17 @@ "Passwords must be at least six characters and match the confirmation.", - "user" => "We can't find a user with that e-mail address.", - "token" => "This password reset token is invalid.", - "sent" => "We have e-mailed your password reset link!", - "reset" => "Your password has been reset!", + 'password' => 'Passwords must be at least six characters and match the confirmation.', + 'user' => 'We can\'t find a user with that e-mail address.', + 'token' => 'This password reset token is invalid.', + 'sent' => 'We have e-mailed your password reset link!', + 'reset' => 'Your password has been reset!', 'blocked' => 'Nice try though.', - ]; diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 88e58d3a19..7889d6d01f 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -1,69 +1,79 @@ 'This value is invalid for the selected trigger.', - 'rule_action_value' => 'This value is invalid for the selected action.', - 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', - 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', - 'file_attached' => 'Succesfully uploaded file ":name".', - 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', - 'file_too_large' => 'File ":name" is too large.', - "accepted" => "The :attribute must be accepted.", - "active_url" => "The :attribute is not a valid URL.", - "after" => "The :attribute must be a date after :date.", - "alpha" => "The :attribute may only contain letters.", - "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", - "alpha_num" => "The :attribute may only contain letters and numbers.", - "array" => "The :attribute must be an array.", - "unique_for_user" => "There already is an entry with this :attribute.", - "before" => "The :attribute must be a date before :date.", - 'unique_object_for_user' => 'This name is already in use', - 'unique_account_for_user' => 'This account name is already in use', - "between.numeric" => "The :attribute must be between :min and :max.", - "between.file" => "The :attribute must be between :min and :max kilobytes.", - "between.string" => "The :attribute must be between :min and :max characters.", - "between.array" => "The :attribute must have between :min and :max items.", - "boolean" => "The :attribute field must be true or false.", - "confirmed" => "The :attribute confirmation does not match.", - "date" => "The :attribute is not a valid date.", - "date_format" => "The :attribute does not match the format :format.", - "different" => "The :attribute and :other must be different.", - "digits" => "The :attribute must be :digits digits.", - "digits_between" => "The :attribute must be between :min and :max digits.", - "email" => "The :attribute must be a valid email address.", - "filled" => "The :attribute field is required.", - "exists" => "The selected :attribute is invalid.", - "image" => "The :attribute must be an image.", - "in" => "The selected :attribute is invalid.", - "integer" => "The :attribute must be an integer.", - "ip" => "The :attribute must be a valid IP address.", - 'json' => 'The :attribute must be a valid JSON string.', - "max.numeric" => "The :attribute may not be greater than :max.", - "max.file" => "The :attribute may not be greater than :max kilobytes.", - "max.string" => "The :attribute may not be greater than :max characters.", - "max.array" => "The :attribute may not have more than :max items.", - "mimes" => "The :attribute must be a file of type: :values.", - "min.numeric" => "The :attribute must be at least :min.", - "min.file" => "The :attribute must be at least :min kilobytes.", - "min.string" => "The :attribute must be at least :min characters.", - "min.array" => "The :attribute must have at least :min items.", - "not_in" => "The selected :attribute is invalid.", - "numeric" => "The :attribute must be a number.", - "regex" => "The :attribute format is invalid.", - "required" => "The :attribute field is required.", - "required_if" => "The :attribute field is required when :other is :value.", - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - "required_with" => "The :attribute field is required when :values is present.", - "required_with_all" => "The :attribute field is required when :values is present.", - "required_without" => "The :attribute field is required when :values is not present.", - "required_without_all" => "The :attribute field is required when none of :values are present.", - "same" => "The :attribute and :other must match.", - "size.numeric" => "The :attribute must be :size.", - "size.file" => "The :attribute must be :size kilobytes.", - "size.string" => "The :attribute must be :size characters.", - "size.array" => "The :attribute must contain :size items.", - "unique" => "The :attribute has already been taken.", - 'string' => 'The :attribute must be a string.', - "url" => "The :attribute format is invalid.", - "timezone" => "The :attribute must be a valid zone.", + 'iban' => 'This is not a valid IBAN.', + 'unique_account_number_for_user' => 'It looks like this account number is already in use.', + 'rule_trigger_value' => 'This value is invalid for the selected trigger.', + 'rule_action_value' => 'This value is invalid for the selected action.', + 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', + 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', + 'file_attached' => 'Succesfully uploaded file ":name".', + 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', + 'file_too_large' => 'File ":name" is too large.', + 'accepted' => 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'unique_for_user' => 'There already is an entry with this :attribute.', + 'before' => 'The :attribute must be a date before :date.', + 'unique_object_for_user' => 'This name is already in use', + 'unique_account_for_user' => 'This account name is already in use', + 'between.numeric' => 'The :attribute must be between :min and :max.', + 'between.file' => 'The :attribute must be between :min and :max kilobytes.', + 'between.string' => 'The :attribute must be between :min and :max characters.', + 'between.array' => 'The :attribute must have between :min and :max items.', + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'email' => 'The :attribute must be a valid email address.', + 'filled' => 'The :attribute field is required.', + 'exists' => 'The selected :attribute is invalid.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max.numeric' => 'The :attribute may not be greater than :max.', + 'max.file' => 'The :attribute may not be greater than :max kilobytes.', + 'max.string' => 'The :attribute may not be greater than :max characters.', + 'max.array' => 'The :attribute may not have more than :max items.', + 'mimes' => 'The :attribute must be a file of type: :values.', + 'min.numeric' => 'The :attribute must be at least :min.', + 'min.file' => 'The :attribute must be at least :min kilobytes.', + 'min.string' => 'The :attribute must be at least :min characters.', + 'min.array' => 'The :attribute must have at least :min items.', + 'not_in' => 'The selected :attribute is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size.numeric' => 'The :attribute must be :size.', + 'size.file' => 'The :attribute must be :size kilobytes.', + 'size.string' => 'The :attribute must be :size characters.', + 'size.array' => 'The :attribute must contain :size items.', + 'unique' => 'The :attribute has already been taken.', + 'string' => 'The :attribute must be a string.', + 'url' => 'The :attribute format is invalid.', + 'timezone' => 'The :attribute must be a valid zone.', + '2fa_code' => 'The :attribute field is invalid.', ]; diff --git a/resources/lang/fr_FR/breadcrumbs.php b/resources/lang/fr_FR/breadcrumbs.php old mode 100644 new mode 100755 index 9de636cc33..62e2727e55 --- a/resources/lang/fr_FR/breadcrumbs.php +++ b/resources/lang/fr_FR/breadcrumbs.php @@ -1,60 +1,45 @@ 'Accueil', - - // accounts 'cash_accounts' => 'Cash accounts', 'edit_account' => 'Editer le compte : ":name"', - - // currencies 'edit_currency' => 'Editer la devise : ";name"', 'delete_currency' => 'Supprimer la devise ":name"', - - // piggy banks 'newPiggyBank' => 'Create a new piggy bank', 'edit_piggyBank' => 'Edit piggy bank ":name"', - - // top menu 'preferences' => 'Preferences', 'profile' => 'Profil', 'changePassword' => 'Modifier le mot de passe', - - // bills 'bills' => 'Factures', 'newBill' => 'Nouvelle facture', 'edit_bill' => 'Editer la facture : ":name"', 'delete_bill' => 'Supprimer la facture ":name"', - - // reports 'reports' => 'Rapport', 'monthly_report' => 'Rapport mensuel pour :date', 'monthly_report_shared' => 'Rapport mensuel pour :date (avec les comptes joints)', 'yearly_report' => 'Rapport annuel pour :date', 'yearly_report_shared' => 'Rapport annuel pour :date (avec les comptes joints)', 'budget_report' => 'Rapport budgetaire pour :date', - - // search 'searchResult' => 'Resultat de recherche pour ":query"', - - // transaction lists. 'withdrawal_list' => 'Dépenses', 'deposit_list' => 'Revenue, Salaire et depots ', 'transfer_list' => 'Transferts', 'transfers_list' => 'Transferts', - - // create transactions 'create_withdrawal' => 'Creer un nouveau retrait', 'create_deposit' => 'Create new deposit', 'create_transfer' => 'Creer un nouveau transfert', - - // edit transactions 'edit_journal' => 'Editer la transaction ":description"', 'delete_journal' => 'Supprimer la transaction ":description"', - - // tags 'tags' => 'Tags', 'createTag' => 'Créer un nouveau tag', 'edit_tag' => 'Editer le tag ":tag"', 'delete_tag' => 'Supprimer le tag ":tag"', - ]; diff --git a/resources/lang/fr_FR/config.php b/resources/lang/fr_FR/config.php old mode 100644 new mode 100755 index 6aa208d339..8d326a4ad2 --- a/resources/lang/fr_FR/config.php +++ b/resources/lang/fr_FR/config.php @@ -1,8 +1,20 @@ 'fr, French, fr_FR, fr_FR.utf8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - + 'locale' => 'fr, French, fr_FR, fr_FR.utf8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Week %W, %Y', + 'quarter_of_year' => '%B %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', ]; diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php old mode 100644 new mode 100755 index 502db22d64..6b32e593ca --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -1,111 +1,167 @@ 'Cette langue n\'est pas encore complètement traduite', - 'test' => 'Vous avez choisi Anglais', - 'close' => 'Fermer', - 'pleaseHold' => 'Veuillew patienter...', - 'actions' => 'Actions', - 'edit' => 'Editer', - 'delete' => 'Supprimer', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Tout', - 'customRange' => 'Plage personnalisée', - 'apply' => 'Appliquer', - 'cancel' => 'Annuler', - 'from' => 'Depuis', - 'to' => 'To', - 'total_sum' => 'Montant total ', - 'period_sum' => 'Somme pour la période', - 'showEverything' => 'Tout Afficher', - 'never' => 'Jamais', - 'search_results_for' => 'Résultats de recherche pour ":query"', - 'bounced_error' => 'Le message envoyé à :email a été rejeté, donc pas d\'accès pour vous.', - 'deleted_error' => 'These credentials do not match our records.', - 'general_blocked_error' => 'Your account has been disabled, so you cannot login.', - 'removed_amount' => 'Supprimé :amount', - 'added_amount' => 'Ajouté :amount', - 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', - 'Opening balance' => 'Solde initial', - 'create_new_stuff' => 'Create new stuff', - 'new_withdrawal' => 'Nouveau retrait', - 'new_deposit' => 'Nouveau dépôt', - 'new_transfer' => 'Nouveau transfert', - 'new_asset_account' => 'New asset account', - 'new_expense_account' => 'New expense account', - 'new_revenue_account' => 'Nouveau compte de recettes', - 'new_budget' => 'Nouveau budget', - 'new_bill' => 'Nouvelle facture', + 'language_incomplete' => 'Cette langue n\'est pas encore complètement traduite', + 'test' => 'Vous avez choisi Anglais', + 'close' => 'Fermer', + 'pleaseHold' => 'Veuillew patienter...', + 'actions' => 'Actions', + 'edit' => 'Editer', + 'delete' => 'Supprimer', + 'welcomeBack' => 'What\'s playing?', + 'everything' => 'Tout', + 'customRange' => 'Plage personnalisée', + 'apply' => 'Appliquer', + 'cancel' => 'Annuler', + 'from' => 'Depuis', + 'to' => 'To', + 'total_sum' => 'Montant total ', + 'period_sum' => 'Somme pour la période', + 'showEverything' => 'Tout Afficher', + 'never' => 'Jamais', + 'search_results_for' => 'Résultats de recherche pour ":query"', + 'bounced_error' => 'Le message envoyé à :email a été rejeté, donc pas d\'accès pour vous.', + 'deleted_error' => 'These credentials do not match our records.', + 'general_blocked_error' => 'Your account has been disabled, so you cannot login.', + 'removed_amount' => 'Supprimé :amount', + 'added_amount' => 'Ajouté :amount', + 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', + 'Opening balance' => 'Solde initial', + 'create_new_stuff' => 'Create new stuff', + 'new_withdrawal' => 'Nouveau retrait', + 'new_deposit' => 'Nouveau dépôt', + 'new_transfer' => 'Nouveau transfert', + 'new_asset_account' => 'New asset account', + 'new_expense_account' => 'New expense account', + 'new_revenue_account' => 'Nouveau compte de recettes', + 'new_budget' => 'Nouveau budget', + 'new_bill' => 'Nouvelle facture', + 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', + 'flash_success' => 'Success!', + 'flash_info' => 'Message', + 'flash_warning' => 'Warning!', + 'flash_error' => 'Error!', + 'flash_info_multiple' => 'There is one message|There are :count messages', + 'flash_error_multiple' => 'There is one error|There are :count errors', + 'net_worth' => 'Net worth', + + // export data: + 'import_and_export' => 'Import and export', + 'export_data' => 'Export data', + 'export_data_intro' => 'For backup purposes, when migrating to another system or when migrating to another Firefly III installation.', + 'export_format' => 'Export format', + 'export_format_csv' => 'Comma separated values (CSV file)', + 'export_format_mt940' => 'MT940 compatible format', + 'export_included_accounts' => 'Export transactions from these accounts', + 'include_config_help' => 'For easy re-import into Firefly III', + 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', + 'do_export' => 'Export', + 'export_status_never_started' => 'The export has not started yet', + 'export_status_make_exporter' => 'Creating exporter thing...', + 'export_status_collecting_journals' => 'Collecting your transactions...', + 'export_status_collected_journals' => 'Collected your transactions!', + 'export_status_converting_to_export_format' => 'Converting your transactions...', + 'export_status_converted_to_export_format' => 'Converted your transactions!', + 'export_status_creating_journal_file' => 'Creating the export file...', + 'export_status_created_journal_file' => 'Created the export file!', + 'export_status_collecting_attachments' => 'Collecting all your attachments...', + 'export_status_collected_attachments' => 'Collected all your attachments!', + 'export_status_collecting_old_uploads' => 'Collecting all your previous uploads...', + 'export_status_collected_old_uploads' => 'Collected all your previous uploads!', + 'export_status_creating_config_file' => 'Creating a configuration file...', + 'export_status_created_config_file' => 'Created a configuration file!', + 'export_status_creating_zip_file' => 'Creating a zip file...', + 'export_status_created_zip_file' => 'Created a zip file!', + 'export_status_finished' => 'Export has succesfully finished! Yay!', + 'export_data_please_wait' => 'Please wait...', + 'attachment_explanation' => 'The file called \':attachment_name\' (#:attachment_id) was originally uploaded to :type \':description\' (#:journal_id) dated :date for the amount of :amount.', // rules - 'rules' => 'Rules', - 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', - 'rule_name' => 'Name of rule', - 'rule_triggers' => 'Rule triggers when', - 'rule_actions' => 'Rule will', - 'new_rule' => 'New rule', - 'new_rule_group' => 'New rule group', - 'rule_priority_up' => 'Give rule more priority', - 'rule_priority_down' => 'Give rule less priority', - 'make_new_rule_group' => 'Make new rule group', - 'store_new_rule_group' => 'Store new rule group', - 'created_new_rule_group' => 'New rule group ":title" stored!', - 'updated_rule_group' => 'Successfully updated rule group ":title".', - 'edit_rule_group' => 'Edit rule group ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - 'deleted_rule_group' => 'Deleted rule group ":title"', - 'update_rule_group' => 'Update rule group', - 'no_rules_in_group' => 'There are no rules in this group', - 'move_rule_group_up' => 'Move rule group up', - 'move_rule_group_down' => 'Move rule group down', - 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', - 'make_new_rule' => 'Make new rule in rule group ":title"', - 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', - 'rule_help_active' => 'Inactive rules will never fire.', - 'stored_new_rule' => 'Stored new rule with title ":title"', - 'deleted_rule' => 'Deleted rule with title ":title"', - 'store_new_rule' => 'Store new rule', - 'updated_rule' => 'Updated rule with title ":title"', - 'default_rule_group_name' => 'Default rules', - 'default_rule_group_description' => 'All your rules not in a particular group.', - 'default_rule_name' => 'Your first default rule', - 'default_rule_description' => 'This rule is an example. You can safely delete it.', - 'default_rule_trigger_description' => 'The Man Who Sold the World', - 'default_rule_trigger_from_account' => 'David Bowie', - 'default_rule_action_prepend' => 'Bought the world from ', - 'default_rule_action_set_category' => 'Large expenses', - - 'trigger' => 'Trigger', - 'trigger_value' => 'Trigger on value', - 'stop_processing_other_triggers' => 'Stop processing other triggers', - 'add_rule_trigger' => 'Add new trigger', - 'action' => 'Action', - 'action_value' => 'Action value', - 'stop_executing_other_actions' => 'Stop executing other actions', - 'add_rule_action' => 'Add new action', - 'edit_rule' => 'Edit rule ":title"', - 'update_rule' => 'Update rule', + 'rules' => 'Rules', + 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', + 'rule_name' => 'Name of rule', + 'rule_triggers' => 'Rule triggers when', + 'rule_actions' => 'Rule will', + 'new_rule' => 'New rule', + 'new_rule_group' => 'New rule group', + 'rule_priority_up' => 'Give rule more priority', + 'rule_priority_down' => 'Give rule less priority', + 'make_new_rule_group' => 'Make new rule group', + 'store_new_rule_group' => 'Store new rule group', + 'created_new_rule_group' => 'New rule group ":title" stored!', + 'updated_rule_group' => 'Successfully updated rule group ":title".', + 'edit_rule_group' => 'Edit rule group ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'deleted_rule_group' => 'Deleted rule group ":title"', + 'update_rule_group' => 'Update rule group', + 'no_rules_in_group' => 'There are no rules in this group', + 'move_rule_group_up' => 'Move rule group up', + 'move_rule_group_down' => 'Move rule group down', + 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', + 'make_new_rule' => 'Make new rule in rule group ":title"', + 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', + 'rule_help_active' => 'Inactive rules will never fire.', + 'stored_new_rule' => 'Stored new rule with title ":title"', + 'deleted_rule' => 'Deleted rule with title ":title"', + 'store_new_rule' => 'Store new rule', + 'updated_rule' => 'Updated rule with title ":title"', + 'default_rule_group_name' => 'Default rules', + 'default_rule_group_description' => 'All your rules not in a particular group.', + 'default_rule_name' => 'Your first default rule', + 'default_rule_description' => 'This rule is an example. You can safely delete it.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Large expenses', + 'trigger' => 'Trigger', + 'trigger_value' => 'Trigger on value', + 'stop_processing_other_triggers' => 'Stop processing other triggers', + 'add_rule_trigger' => 'Add new trigger', + 'action' => 'Action', + 'action_value' => 'Action value', + 'stop_executing_other_actions' => 'Stop executing other actions', + 'add_rule_action' => 'Add new action', + 'edit_rule' => 'Edit rule ":title"', + 'delete_rule' => 'Delete rule ":title"', + 'update_rule' => 'Update rule', + 'test_rule_triggers' => 'See matching transactions', + 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions', + 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', + 'warning_no_valid_triggers' => 'No valid triggers provided.', + 'execute_on_existing_transactions' => 'Execute for existing transactions', + 'execute_on_existing_transactions_intro' => 'When a rule or group has been changed or added, you can execute it for existing transactions', + 'execute_on_existing_transactions_short' => 'Existing transactions', + 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', + 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions', + 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'execute' => 'Execute', // actions and triggers - 'rule_trigger_user_action' => 'User action is ":trigger_value"', - 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', - 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', - 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', - 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', - 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', - 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', - 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', - 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', - 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', - 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', - 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', - 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', - 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', - 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', - 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', - 'rule_trigger_description_is' => 'Description is ":trigger_value"', - + 'rule_trigger_user_action' => 'User action is ":trigger_value"', + 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', + 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', + 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', + 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', + 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', + 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', + 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', + 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', + 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', + 'rule_trigger_description_is' => 'Description is ":trigger_value"', 'rule_trigger_from_account_starts_choice' => 'Source account starts with..', 'rule_trigger_from_account_ends_choice' => 'Source account ends with..', 'rule_trigger_from_account_is_choice' => 'Source account is..', @@ -122,486 +178,497 @@ return [ 'rule_trigger_description_ends_choice' => 'Description ends with..', 'rule_trigger_description_contains_choice' => 'Description contains..', 'rule_trigger_description_is_choice' => 'Description is..', - - 'rule_trigger_store_journal' => 'When a journal is created', - 'rule_trigger_update_journal' => 'When a journal is updated', - - 'rule_action_set_category' => 'Set category to ":action_value"', - 'rule_action_clear_category' => 'Clear category', - 'rule_action_set_budget' => 'Set budget to ":action_value"', - 'rule_action_clear_budget' => 'Clear budget', - 'rule_action_add_tag' => 'Add tag ":action_value"', - 'rule_action_remove_tag' => 'Remove tag ":action_value"', - 'rule_action_remove_all_tags' => 'Remove all tags', - 'rule_action_set_description' => 'Set description to ":action_value"', - 'rule_action_append_description' => 'Append description with ":action_value"', - 'rule_action_prepend_description' => 'Prepend description with ":action_value"', - - 'rule_action_set_category_choice' => 'Set category to..', - 'rule_action_clear_category_choice' => 'Clear any category', - 'rule_action_set_budget_choice' => 'Set budget to..', - 'rule_action_clear_budget_choice' => 'Clear any budget', - 'rule_action_add_tag_choice' => 'Add tag..', - 'rule_action_remove_tag_choice' => 'Remove tag..', - 'rule_action_remove_all_tags_choice' => 'Remove all tags', - 'rule_action_set_description_choice' => 'Set description to..', - 'rule_action_append_description_choice' => 'Append description with..', - 'rule_action_prepend_description_choice' => 'Prepend description with..', + 'rule_trigger_store_journal' => 'When a journal is created', + 'rule_trigger_update_journal' => 'When a journal is updated', + 'rule_action_set_category' => 'Set category to ":action_value"', + 'rule_action_clear_category' => 'Clear category', + 'rule_action_set_budget' => 'Set budget to ":action_value"', + 'rule_action_clear_budget' => 'Clear budget', + 'rule_action_add_tag' => 'Add tag ":action_value"', + 'rule_action_remove_tag' => 'Remove tag ":action_value"', + 'rule_action_remove_all_tags' => 'Remove all tags', + 'rule_action_set_description' => 'Set description to ":action_value"', + 'rule_action_append_description' => 'Append description with ":action_value"', + 'rule_action_prepend_description' => 'Prepend description with ":action_value"', + 'rule_action_set_category_choice' => 'Set category to..', + 'rule_action_clear_category_choice' => 'Clear any category', + 'rule_action_set_budget_choice' => 'Set budget to..', + 'rule_action_clear_budget_choice' => 'Clear any budget', + 'rule_action_add_tag_choice' => 'Add tag..', + 'rule_action_remove_tag_choice' => 'Remove tag..', + 'rule_action_remove_all_tags_choice' => 'Remove all tags', + 'rule_action_set_description_choice' => 'Set description to..', + 'rule_action_append_description_choice' => 'Append description with..', + 'rule_action_prepend_description_choice' => 'Prepend description with..', // tags - 'store_new_tag' => 'Créer un nouveau tag', - 'update_tag' => 'Mettre à jour le tag', - 'no_location_set' => 'Aucun emplacement défini.', - 'meta_data' => 'Meta-données', - 'location' => 'Emplacement', + 'store_new_tag' => 'Créer un nouveau tag', + 'update_tag' => 'Mettre à jour le tag', + 'no_location_set' => 'Aucun emplacement défini.', + 'meta_data' => 'Meta-données', + 'location' => 'Emplacement', // preferences - 'pref_home_screen_accounts' => 'Home screen accounts', - 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', - 'pref_budget_settings' => 'Paramètres de budget', - 'pref_budget_settings_help' => 'What\'s the maximum amount of money a budget envelope may contain?', - 'pref_view_range' => 'View range', - 'pref_view_range_help' => 'Some charts are automatically grouped in periods. What period would you prefer?', - 'pref_1D' => 'One day', - 'pref_1W' => 'One week', - 'pref_1M' => 'One month', - 'pref_3M' => 'Three months (quarter)', - 'pref_6M' => 'Six months', - 'pref_languages' => 'Languages', - 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', - 'pref_custom_fiscal_year' => 'Fiscal year settings', - 'pref_custom_fiscal_year_label' => 'Enabled', - 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', - 'pref_fiscal_year_start_label' => 'Fiscal year start date', - 'pref_save_settings' => 'Save settings', + 'pref_home_screen_accounts' => 'Home screen accounts', + 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', + 'pref_budget_settings' => 'Paramètres de budget', + 'pref_budget_settings_help' => 'What\'s the maximum amount of money a budget envelope may contain?', + 'pref_view_range' => 'View range', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. What period would you prefer?', + 'pref_1D' => 'One day', + 'pref_1W' => 'One week', + 'pref_1M' => 'One month', + 'pref_3M' => 'Three months (quarter)', + 'pref_6M' => 'Six months', + 'pref_languages' => 'Languages', + 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', + 'pref_custom_fiscal_year' => 'Fiscal year settings', + 'pref_custom_fiscal_year_label' => 'Enabled', + 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', + 'pref_fiscal_year_start_label' => 'Fiscal year start date', + 'pref_two_factor_auth' => '2-step verification', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', + 'pref_two_factor_auth_code' => 'Verify code', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Reset verification code', + 'pref_two_factor_auth_remove_code' => 'Remove verification code', + 'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)', + 'pref_save_settings' => 'Save settings', // profile: - 'change_your_password' => 'Change your password', - 'delete_account' => 'Delete account', - 'current_password' => 'Current password', - 'new_password' => 'New password', - 'new_password_again' => 'New password (again)', - 'delete_your_account' => 'Delete your account', - 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', - 'delete_your_account_password' => 'Enter your password to continue.', - 'password' => 'Password', - 'are_you_sure' => 'Are you sure? You cannot undo this.', - 'delete_account_button' => 'DELETE your account', - 'invalid_current_password' => 'Invalid current password!', - 'password_changed' => 'Password changed!', - 'should_change' => 'The idea is to change your password.', - 'invalid_password' => 'Invalid password!', - + 'change_your_password' => 'Change your password', + 'delete_account' => 'Delete account', + 'current_password' => 'Current password', + 'new_password' => 'New password', + 'new_password_again' => 'New password (again)', + 'delete_your_account' => 'Delete your account', + 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', + 'delete_your_account_password' => 'Enter your password to continue.', + 'password' => 'Password', + 'are_you_sure' => 'Are you sure? You cannot undo this.', + 'delete_account_button' => 'DELETE your account', + 'invalid_current_password' => 'Invalid current password!', + 'password_changed' => 'Password changed!', + 'should_change' => 'The idea is to change your password.', + 'invalid_password' => 'Invalid password!', // attachments - 'nr_of_attachments' => 'One attachment|:count attachments', - 'attachments' => 'Attachments', - 'edit_attachment' => 'Edit attachment ":name"', - 'update_attachment' => 'Update attachment', - 'delete_attachment' => 'Delete attachment ":name"', - 'attachment_deleted' => 'Deleted attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', + 'nr_of_attachments' => 'One attachment|:count attachments', + 'attachments' => 'Attachments', + 'edit_attachment' => 'Edit attachment ":name"', + 'update_attachment' => 'Update attachment', + 'delete_attachment' => 'Delete attachment ":name"', + 'attachment_deleted' => 'Deleted attachment ":name"', + 'upload_max_file_size' => 'Maximum file size: :size', // tour: - 'prev' => 'Prev', - 'next' => 'Next', - 'end-tour' => 'End tour', - 'pause' => 'Pause', + 'prev' => 'Prev', + 'next' => 'Next', + 'end-tour' => 'End tour', + 'pause' => 'Pause', // transaction index - 'title_expenses' => 'Expenses', - 'title_withdrawal' => 'Expenses', - 'title_revenue' => 'Revenue / income', - 'title_deposit' => 'Revenue / income', - 'title_transfer' => 'Transferts', - 'title_transfers' => 'Transferts', + 'title_expenses' => 'Expenses', + 'title_withdrawal' => 'Expenses', + 'title_revenue' => 'Revenue / income', + 'title_deposit' => 'Revenue / income', + 'title_transfer' => 'Transferts', + 'title_transfers' => 'Transferts', // csv import: - 'csv_import' => 'Import CSV file', - 'csv' => 'CSV', - 'csv_index_title' => 'Upload and import a CSV file', - 'csv_define_column_roles' => 'Define column roles', - 'csv_map_values' => 'Map found values to existing values', - 'csv_download_config' => 'Download CSV configuration file.', - 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', - 'csv_index_beta_warning' => 'This tool is very much in beta. Please proceed with caution', - 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', - 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', - 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', - 'csv_upload_button' => 'Start importing CSV', - 'csv_column_roles_title' => 'Define column roles', - 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', - 'csv_column_roles_table' => 'Column roles', - 'csv_column' => 'CSV column', - 'csv_column_name' => 'CSV column name', - 'csv_column_example' => 'Column example data', - 'csv_column_role' => 'Column contains?', - 'csv_do_map_value' => 'Map value?', - 'csv_continue' => 'Continue to the next step', - 'csv_go_back' => 'Go back to the previous step', - 'csv_map_title' => 'Map found values to existing values', - 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', - 'csv_field_value' => 'Field value from CSV', - 'csv_field_mapped_to' => 'Must be mapped to...', - 'csv_do_not_map' => 'Do not map this value', - 'csv_download_config_title' => 'Download CSV configuration', - 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', - 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', - 'csv_do_download_config' => 'Download configuration file.', - 'csv_empty_description' => '(empty description)', - 'csv_upload_form' => 'CSV upload form', - 'csv_index_unsupported_warning' => 'The CSV importer is yet incapable of doing the following:', - 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', - 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', - 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', - 'csv_process_title' => 'CSV import finished!', - 'csv_process_text' => 'The CSV importer has finished and has processed :rows rows', - 'csv_row' => 'Row', - 'csv_import_with_errors' => 'There was one error.|There were :errors errors.', - 'csv_error_see_logs' => 'Check the log files to see details.', - 'csv_process_new_entries' => 'Firefly has created :imported new transaction(s).', - 'csv_start_over' => 'Import again', - 'csv_to_index' => 'Back home', - 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', - 'csv_column__ignore' => '(ignore this column)', - 'csv_column_account-iban' => 'Asset account (IBAN)', - 'csv_column_account-id' => 'Asset account ID (matching Firefly)', - 'csv_column_account-name' => 'Asset account (name)', - 'csv_column_amount' => 'Amount', - 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'csv_column_bill-id' => 'Bill ID (matching Firefly)', - 'csv_column_bill-name' => 'Bill name', - 'csv_column_budget-id' => 'Budget ID (matching Firefly)', - 'csv_column_budget-name' => 'Budget name', - 'csv_column_category-id' => 'Category ID (matching Firefly)', - 'csv_column_category-name' => 'Category name', - 'csv_column_currency-code' => 'Currency code (ISO 4217)', - 'csv_column_currency-id' => 'Currency ID (matching Firefly)', - 'csv_column_currency-name' => 'Currency name (matching Firefly)', - 'csv_column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'csv_column_date-rent' => 'Rent calculation date', - 'csv_column_date-transaction' => 'Date', - 'csv_column_description' => 'Description', - 'csv_column_opposing-iban' => 'Opposing account (IBAN)', - 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'csv_column_opposing-name' => 'Opposing account (name)', - 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'csv_column_sepa-db' => 'SEPA Direct Debet', - 'csv_column_tags-comma' => 'Tags (comma separated)', - 'csv_column_tags-space' => 'Tags (space separated)', - 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', - 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', - 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', - 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', + 'csv_import' => 'Import CSV file', + 'csv' => 'CSV', + 'csv_index_title' => 'Upload and import a CSV file', + 'csv_define_column_roles' => 'Define column roles', + 'csv_map_values' => 'Map found values to existing values', + 'csv_download_config' => 'Download CSV configuration file.', + 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', + 'csv_index_beta_warning' => 'This tool is very much in beta. Please proceed with caution', + 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', + 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', + 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', + 'csv_upload_button' => 'Start importing CSV', + 'csv_column_roles_title' => 'Define column roles', + 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', + 'csv_column_roles_table' => 'Column roles', + 'csv_column' => 'CSV column', + 'csv_column_name' => 'CSV column name', + 'csv_column_example' => 'Column example data', + 'csv_column_role' => 'Column contains?', + 'csv_do_map_value' => 'Map value?', + 'csv_continue' => 'Continue to the next step', + 'csv_go_back' => 'Go back to the previous step', + 'csv_map_title' => 'Map found values to existing values', + 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', + 'csv_field_value' => 'Field value from CSV', + 'csv_field_mapped_to' => 'Must be mapped to...', + 'csv_do_not_map' => 'Do not map this value', + 'csv_download_config_title' => 'Download CSV configuration', + 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', + 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', + 'csv_do_download_config' => 'Download configuration file.', + 'csv_empty_description' => '(empty description)', + 'csv_upload_form' => 'CSV upload form', + 'csv_index_unsupported_warning' => 'The CSV importer is yet incapable of doing the following:', + 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', + 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', + 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', + 'csv_process_title' => 'CSV import finished!', + 'csv_process_text' => 'The CSV importer has finished and has processed :rows rows', + 'csv_row' => 'Row', + 'csv_import_with_errors' => 'There was one error.|There were :errors errors.', + 'csv_error_see_logs' => 'Check the log files to see details.', + 'csv_process_new_entries' => 'Firefly has created :imported new transaction(s).', + 'csv_start_over' => 'Import again', + 'csv_to_index' => 'Back home', + 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', + 'csv_column__ignore' => '(ignore this column)', + 'csv_column_account-iban' => 'Asset account (IBAN)', + 'csv_column_account-id' => 'Asset account ID (matching Firefly)', + 'csv_column_account-name' => 'Asset account (name)', + 'csv_column_amount' => 'Amount', + 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'csv_column_bill-id' => 'Bill ID (matching Firefly)', + 'csv_column_bill-name' => 'Bill name', + 'csv_column_budget-id' => 'Budget ID (matching Firefly)', + 'csv_column_budget-name' => 'Budget name', + 'csv_column_category-id' => 'Category ID (matching Firefly)', + 'csv_column_category-name' => 'Category name', + 'csv_column_currency-code' => 'Currency code (ISO 4217)', + 'csv_column_currency-id' => 'Currency ID (matching Firefly)', + 'csv_column_currency-name' => 'Currency name (matching Firefly)', + 'csv_column_currency-symbol' => 'Currency symbol (matching Firefly)', + 'csv_column_date-rent' => 'Rent calculation date', + 'csv_column_date-transaction' => 'Date', + 'csv_column_description' => 'Description', + 'csv_column_opposing-iban' => 'Opposing account (IBAN)', + 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', + 'csv_column_opposing-name' => 'Opposing account (name)', + 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', + 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator', + 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', + 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', + 'csv_column_sepa-db' => 'SEPA Direct Debet', + 'csv_column_tags-comma' => 'Tags (comma separated)', + 'csv_column_tags-space' => 'Tags (space separated)', + 'csv_column_account-number' => 'Asset account (account number)', + 'csv_column_opposing-number' => 'Opposing account (account number)', + 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', + 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', + 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', + 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', // create new stuff: - 'create_new_withdrawal' => 'Creer un nouveau retrait', - 'create_new_deposit' => 'Create new deposit', - 'create_new_transfer' => 'Creer un nouveau transfert', - 'create_new_asset' => 'Create new asset account', - 'create_new_expense' => 'Create new expense account', - 'create_new_revenue' => 'Create new revenue account', - 'create_new_piggy_bank' => 'Create new piggy bank', - 'create_new_bill' => 'Create new bill', + 'create_new_withdrawal' => 'Creer un nouveau retrait', + 'create_new_deposit' => 'Create new deposit', + 'create_new_transfer' => 'Creer un nouveau transfert', + 'create_new_asset' => 'Create new asset account', + 'create_new_expense' => 'Create new expense account', + 'create_new_revenue' => 'Create new revenue account', + 'create_new_piggy_bank' => 'Create new piggy bank', + 'create_new_bill' => 'Create new bill', // currencies: - 'create_currency' => 'Create a new currency', - 'edit_currency' => 'Edit currency ":name"', - 'store_currency' => 'Store new currency', - 'update_currency' => 'Update currency', - 'new_default_currency' => ':name is now the default currency.', - 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', - 'deleted_currency' => 'Currency :name deleted', - 'created_currency' => 'Currency :name created', - 'updated_currency' => 'Currency :name updated', - 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', - 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', - 'make_default_currency' => 'make default', - 'default_currency' => 'default', + 'create_currency' => 'Create a new currency', + 'edit_currency' => 'Edit currency ":name"', + 'store_currency' => 'Store new currency', + 'update_currency' => 'Update currency', + 'new_default_currency' => ':name is now the default currency.', + 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', + 'deleted_currency' => 'Currency :name deleted', + 'created_currency' => 'Currency :name created', + 'updated_currency' => 'Currency :name updated', + 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', + 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', + 'make_default_currency' => 'make default', + 'default_currency' => 'default', // new user: - 'submit' => 'Submit', - 'getting_started' => 'Getting started', - 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', - 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', - 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', + 'submit' => 'Submit', + 'getting_started' => 'Getting started', + 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', + 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', + 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - 'something' => 'Something!', + 'mandatoryFields' => 'Mandatory fields', + 'optionalFields' => 'Optional fields', + 'options' => 'Options', + 'something' => 'Something!', // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => ' Store new budget', - 'availableIn' => 'Available in :date', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', - 'createBudget' => 'New budget', - 'inactiveBudgets' => 'Inactive budgets', - 'without_budget_between' => 'Transactions without a budget between :start and :end', - 'budget_in_month' => ':name in :month', - 'delete_budget' => 'Delete budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', + 'create_new_budget' => 'Create a new budget', + 'store_new_budget' => ' Store new budget', + 'availableIn' => 'Available in :date', + 'transactionsWithoutBudget' => 'Expenses without budget', + 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', + 'createBudget' => 'New budget', + 'inactiveBudgets' => 'Inactive budgets', + 'without_budget_between' => 'Transactions without a budget between :start and :end', + 'budget_in_month' => ':name in :month', + 'delete_budget' => 'Delete budget ":name"', + 'edit_budget' => 'Edit budget ":name"', + 'update_amount' => 'Update amount', + 'update_budget' => 'Update budget', // bills: - 'delete_bill' => 'Delete bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'update_bill' => 'Update bill', - 'store_new_bill' => 'Store new bill', + 'delete_bill' => 'Delete bill ":name"', + 'edit_bill' => 'Edit bill ":name"', + 'update_bill' => 'Update bill', + 'store_new_bill' => 'Store new bill', // accounts: - 'details_for_asset' => 'Details for asset account ":name"', - 'details_for_expense' => 'Details for expense account ":name"', - 'details_for_revenue' => 'Details for revenue account ":name"', - 'details_for_cash' => 'Details for cash account ":name"', - 'store_new_asset_account' => 'Store new asset account', - 'store_new_expense_account' => 'Store new expense account', - 'store_new_revenue_account' => 'Store new revenue account', - 'edit_asset_account' => 'Edit asset account ":name"', - 'edit_expense_account' => 'Edit expense account ":name"', - 'edit_revenue_account' => 'Edit revenue account ":name"', - 'delete_asset_account' => 'Delete asset account ":name"', - 'delete_expense_account' => 'Delete expense account ":name"', - 'delete_revenue_account' => 'Delete revenue account ":name"', - 'asset_deleted' => 'Successfully deleted asset account ":name"', - 'expense_deleted' => 'Successfully deleted expense account ":name"', - 'revenue_deleted' => 'Successfully deleted revenue account ":name"', - 'update_asset_account' => 'Update asset account', - 'update_expense_account' => 'Update expense account', - 'update_revenue_account' => 'Update revenue account', - 'make_new_asset_account' => 'Create a new asset account', - 'make_new_expense_account' => 'Create a new expense account', - 'make_new_revenue_account' => 'Create a new revenue account', - 'asset_accounts' => 'Asset accounts', - 'expense_accounts' => 'Expense accounts', - 'revenue_accounts' => 'Revenue accounts', - 'accountExtraHelp_asset' => '', - 'accountExtraHelp_expense' => '', - 'accountExtraHelp_revenue' => '', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', + 'details_for_asset' => 'Details for asset account ":name"', + 'details_for_expense' => 'Details for expense account ":name"', + 'details_for_revenue' => 'Details for revenue account ":name"', + 'details_for_cash' => 'Details for cash account ":name"', + 'store_new_asset_account' => 'Store new asset account', + 'store_new_expense_account' => 'Store new expense account', + 'store_new_revenue_account' => 'Store new revenue account', + 'edit_asset_account' => 'Edit asset account ":name"', + 'edit_expense_account' => 'Edit expense account ":name"', + 'edit_revenue_account' => 'Edit revenue account ":name"', + 'delete_asset_account' => 'Delete asset account ":name"', + 'delete_expense_account' => 'Delete expense account ":name"', + 'delete_revenue_account' => 'Delete revenue account ":name"', + 'asset_deleted' => 'Successfully deleted asset account ":name"', + 'expense_deleted' => 'Successfully deleted expense account ":name"', + 'revenue_deleted' => 'Successfully deleted revenue account ":name"', + 'update_asset_account' => 'Update asset account', + 'update_expense_account' => 'Update expense account', + 'update_revenue_account' => 'Update revenue account', + 'make_new_asset_account' => 'Create a new asset account', + 'make_new_expense_account' => 'Create a new expense account', + 'make_new_revenue_account' => 'Create a new revenue account', + 'asset_accounts' => 'Asset accounts', + 'expense_accounts' => 'Expense accounts', + 'revenue_accounts' => 'Revenue accounts', + 'accountExtraHelp_asset' => '', + 'accountExtraHelp_expense' => '', + 'accountExtraHelp_revenue' => '', + 'account_type' => 'Account type', + 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Wijzig categorie', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'store_category' => 'Store new category', - 'without_category_between' => 'Without category between :start and :end', + 'new_category' => 'New category', + 'create_new_category' => 'Create a new category', + 'without_category' => 'Without a category', + 'update_category' => 'Wijzig categorie', + 'categories' => 'Categories', + 'edit_category' => 'Edit category ":name"', + 'no_category' => '(no category)', + 'category' => 'Category', + 'delete_category' => 'Delete category ":name"', + 'store_category' => 'Store new category', + 'without_category_between' => 'Without category between :start and :end', // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', + 'update_withdrawal' => 'Update withdrawal', + 'update_deposit' => 'Update deposit', + 'update_transfer' => 'Update transfer', + 'delete_withdrawal' => 'Delete withdrawal ":description"', + 'delete_deposit' => 'Delete deposit ":description"', + 'delete_transfer' => 'Delete transfer ":description"', // new user: - 'welcome' => 'Welcome to Firefly!', - 'createNewAsset' => 'Create a new asset account to get started. ' . - 'This will allow you to create transactions and start your financial management', - 'createNewAssetButton' => 'Create new asset account', + 'welcome' => 'Welcome to Firefly!', + 'createNewAsset' => 'Create a new asset account to get started. ' . + 'This will allow you to create transactions and start your financial management', + 'createNewAssetButton' => 'Create new asset account', // home page: - 'yourAccounts' => 'Your accounts', - 'budgetsAndSpending' => 'Budgets and spending', - 'savings' => 'Savings', - 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', - 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', - 'newWithdrawal' => 'New expense', - 'newDeposit' => 'New deposit', - 'newTransfer' => 'New transfer', - 'moneyIn' => 'Money in', - 'moneyOut' => 'Money out', - 'billsToPay' => 'Bills to pay', - 'billsPaid' => 'Bills paid', - 'viewDetails' => 'View details', - 'divided' => 'divided', - 'toDivide' => 'left to divide', + 'yourAccounts' => 'Your accounts', + 'budgetsAndSpending' => 'Budgets and spending', + 'savings' => 'Savings', + 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', + 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', + 'newWithdrawal' => 'New expense', + 'newDeposit' => 'New deposit', + 'newTransfer' => 'New transfer', + 'moneyIn' => 'Money in', + 'moneyOut' => 'Money out', + 'billsToPay' => 'Bills to pay', + 'billsPaid' => 'Bills paid', + 'viewDetails' => 'View details', + 'divided' => 'divided', + 'toDivide' => 'left to divide', // menu and titles, should be recycled as often as possible: - 'toggleNavigation' => 'Toggle navigation', - 'currency' => 'Currency', - 'preferences' => 'Preferences', - 'logout' => 'Logout', - 'searchPlaceholder' => 'Search...', - 'dashboard' => 'Dashboard', - 'currencies' => 'Currencies', - 'accounts' => 'Accounts', - 'Asset account' => 'Asset account', - 'Default account' => 'Asset account', - 'Expense account' => 'Expense account', - 'Revenue account' => 'Revenue account', - 'Initial balance account' => 'Initial balance account', - 'budgets' => 'Budgets', - 'tags' => 'Tags', - 'reports' => 'Reports', - 'transactions' => 'Transactions', - 'expenses' => 'Expenses', - 'income' => 'Revenue / income', - 'transfers' => 'Transferts', - 'moneyManagement' => 'Money management', - 'piggyBanks' => 'Piggy banks', - 'bills' => 'Bills', - 'createNew' => 'Create new', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'newBalance' => 'New balance', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'weekly' => 'Weekly', - 'monthly' => 'Monthly', - 'quarterly' => 'Quarterly', - 'half-year' => 'Every six months', - 'yearly' => 'Yearly', - 'profile' => 'Profile', + 'toggleNavigation' => 'Toggle navigation', + 'currency' => 'Currency', + 'preferences' => 'Preferences', + 'logout' => 'Logout', + 'searchPlaceholder' => 'Search...', + 'dashboard' => 'Dashboard', + 'currencies' => 'Currencies', + 'accounts' => 'Accounts', + 'Asset account' => 'Asset account', + 'Default account' => 'Asset account', + 'Expense account' => 'Expense account', + 'Revenue account' => 'Revenue account', + 'Initial balance account' => 'Initial balance account', + 'budgets' => 'Budgets', + 'tags' => 'Tags', + 'reports' => 'Reports', + 'transactions' => 'Transactions', + 'expenses' => 'Expenses', + 'income' => 'Revenue / income', + 'transfers' => 'Transferts', + 'moneyManagement' => 'Money management', + 'piggyBanks' => 'Piggy banks', + 'bills' => 'Bills', + 'createNew' => 'Create new', + 'withdrawal' => 'Withdrawal', + 'deposit' => 'Deposit', + 'account' => 'Account', + 'transfer' => 'Transfer', + 'Withdrawal' => 'Withdrawal', + 'Deposit' => 'Deposit', + 'Transfer' => 'Transfer', + 'bill' => 'Bill', + 'yes' => 'Yes', + 'no' => 'No', + 'amount' => 'Amount', + 'newBalance' => 'New balance', + 'overview' => 'Overview', + 'saveOnAccount' => 'Save on account', + 'unknown' => 'Unknown', + 'daily' => 'Daily', + 'weekly' => 'Weekly', + 'monthly' => 'Monthly', + 'quarterly' => 'Quarterly', + 'half-year' => 'Every six months', + 'yearly' => 'Yearly', + 'profile' => 'Profile', // reports: - 'report_default' => 'Default financial report for :start until :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'report_this_month_quick' => 'Current month, all accounts', - 'report_this_year_quick' => 'Current year, all accounts', - 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', - 'report_all_time_quick' => 'All-time, all accounts', - 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', - 'incomeVsExpenses' => 'Income vs. expenses', - 'accountBalances' => 'Account balances', - 'balanceStartOfYear' => 'Balance at start of year', - 'balanceEndOfYear' => 'Balance at end of year', - 'balanceStartOfMonth' => 'Balance at start of month', - 'balanceEndOfMonth' => 'Balance at end of month', - 'balanceStart' => 'Balance at start of period', - 'balanceEnd' => 'Balance at end of period', - 'reportsOwnAccounts' => 'Reports for your own accounts', - 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', - 'splitByAccount' => 'Split by account', - 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', - 'coveredWithTags' => 'Covered with tags', - 'leftUnbalanced' => 'Left unbalanced', - 'expectedBalance' => 'Expected balance', - 'outsideOfBudgets' => 'Outside of budgets', - 'leftInBudget' => 'Left in budget', - 'sumOfSums' => 'Sum of sums', - 'noCategory' => '(no category)', - 'notCharged' => 'Not charged (yet)', - 'inactive' => 'Inactive', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'showTheRest' => 'Show everything', - 'hideTheRest' => 'Show only the top :number', - 'sum_of_year' => 'Sum of year', - 'sum_of_years' => 'Sum of years', - 'average_of_year' => 'Average of year', - 'average_of_years' => 'Average of years', - 'categories_earned_in_year' => 'Categories (by earnings)', - 'categories_spent_in_year' => 'Categories (by spendings)', - 'report_type' => 'Report type', - 'report_type_default' => 'Default financial report', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', + 'report_default' => 'Default financial report for :start until :end', + 'report_audit' => 'Transaction history overview for :start until :end', + 'quick_link_reports' => 'Quick links', + 'quick_link_default_report' => 'Default financial report', + 'report_this_month_quick' => 'Current month, all accounts', + 'report_this_year_quick' => 'Current year, all accounts', + 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', + 'report_all_time_quick' => 'All-time, all accounts', + 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', + 'incomeVsExpenses' => 'Income vs. expenses', + 'accountBalances' => 'Account balances', + 'balanceStartOfYear' => 'Balance at start of year', + 'balanceEndOfYear' => 'Balance at end of year', + 'balanceStartOfMonth' => 'Balance at start of month', + 'balanceEndOfMonth' => 'Balance at end of month', + 'balanceStart' => 'Balance at start of period', + 'balanceEnd' => 'Balance at end of period', + 'reportsOwnAccounts' => 'Reports for your own accounts', + 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', + 'splitByAccount' => 'Split by account', + 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', + 'coveredWithTags' => 'Covered with tags', + 'leftUnbalanced' => 'Left unbalanced', + 'expectedBalance' => 'Expected balance', + 'outsideOfBudgets' => 'Outside of budgets', + 'leftInBudget' => 'Left in budget', + 'sumOfSums' => 'Sum of sums', + 'noCategory' => '(no category)', + 'notCharged' => 'Not charged (yet)', + 'inactive' => 'Inactive', + 'difference' => 'Difference', + 'in' => 'In', + 'out' => 'Out', + 'topX' => 'top :number', + 'showTheRest' => 'Show everything', + 'hideTheRest' => 'Show only the top :number', + 'sum_of_year' => 'Sum of year', + 'sum_of_years' => 'Sum of years', + 'average_of_year' => 'Average of year', + 'average_of_years' => 'Average of years', + 'categories_earned_in_year' => 'Categories (by earnings)', + 'categories_spent_in_year' => 'Categories (by spendings)', + 'report_type' => 'Report type', + 'report_type_default' => 'Default financial report', + 'report_type_audit' => 'Transaction history overview (audit)', + 'report_included_accounts' => 'Included accounts', + 'report_date_range' => 'Date range', + 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', + 'report_preset_ranges' => 'Pre-set ranges', + 'shared' => 'Shared', + 'fiscal_year' => 'Fiscal year', // charts: - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'noBudget' => '(no budget)', - 'maxAmount' => 'Maximum amount', - 'minAmount' => 'Minumum amount', - 'billEntry' => 'Current bill entry', - 'name' => 'Name', - 'date' => 'Date', - 'paid' => 'Paid', - 'unpaid' => 'Unpaid', - 'day' => 'Day', - 'budgeted' => 'Budgeted', - 'period' => 'Period', - 'balance' => 'Balance', - 'summary' => 'Summary', - 'sum' => 'Sum', - 'average' => 'Average', - 'balanceFor' => 'Balance for :name', + 'dayOfMonth' => 'Day of the month', + 'month' => 'Month', + 'budget' => 'Budget', + 'spent' => 'Spent', + 'earned' => 'Earned', + 'overspent' => 'Overspent', + 'left' => 'Left', + 'noBudget' => '(no budget)', + 'maxAmount' => 'Maximum amount', + 'minAmount' => 'Minumum amount', + 'billEntry' => 'Current bill entry', + 'name' => 'Name', + 'date' => 'Date', + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + 'day' => 'Day', + 'budgeted' => 'Budgeted', + 'period' => 'Period', + 'balance' => 'Balance', + 'summary' => 'Summary', + 'sum' => 'Sum', + 'average' => 'Average', + 'balanceFor' => 'Balance for :name', // piggy banks: - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'Create new piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'account_status' => 'Account status', - 'left_for_piggy_banks' => 'Left for piggy banks', - 'sum_of_piggy_banks' => 'Sum of piggy banks', - 'saved_so_far' => 'Saved so far', - 'left_to_save' => 'Left to save', - 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', - 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', - 'add' => 'Add', - 'remove' => 'Remove', - 'max_amount_add' => 'The maximum amount you can add is', - 'max_amount_remove' => 'The maximum amount you can remove is', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'details' => 'Details', - 'events' => 'Events', - 'target_amount' => 'Target amount', - 'start_date' => 'Start date', - 'target_date' => 'Target date', - 'no_target_date' => 'No target date', - 'todo' => 'to do', - 'table' => 'Table', - 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', - 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', - 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', - 'delete_piggy_bank' => 'Delete piggy bank ":name"', + 'piggy_bank' => 'Piggy bank', + 'new_piggy_bank' => 'Create new piggy bank', + 'store_piggy_bank' => 'Store new piggy bank', + 'account_status' => 'Account status', + 'left_for_piggy_banks' => 'Left for piggy banks', + 'sum_of_piggy_banks' => 'Sum of piggy banks', + 'saved_so_far' => 'Saved so far', + 'left_to_save' => 'Left to save', + 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', + 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', + 'add' => 'Add', + 'remove' => 'Remove', + 'max_amount_add' => 'The maximum amount you can add is', + 'max_amount_remove' => 'The maximum amount you can remove is', + 'update_piggy_button' => 'Update piggy bank', + 'update_piggy_title' => 'Update piggy bank ":name"', + 'details' => 'Details', + 'events' => 'Events', + 'target_amount' => 'Target amount', + 'start_date' => 'Start date', + 'target_date' => 'Target date', + 'no_target_date' => 'No target date', + 'todo' => 'to do', + 'table' => 'Table', + 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', + 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', + 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', + 'delete_piggy_bank' => 'Delete piggy bank ":name"', // tags - 'regular_tag' => 'Just a regular tag.', - 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', - 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', - 'delete_tag' => 'Supprimer le tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Editer le tag ":tag"', - 'no_year' => 'No year set', - 'no_month' => 'No month set', - 'tag_title_nothing' => 'Default tags', - 'tag_title_balancingAct' => 'Balancing act tags', - 'tag_title_advancePayment' => 'Advance payment tags', - 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', - 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', - 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', + 'regular_tag' => 'Just a regular tag.', + 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', + 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', + 'delete_tag' => 'Supprimer le tag ":tag"', + 'new_tag' => 'Make new tag', + 'edit_tag' => 'Editer le tag ":tag"', + 'no_year' => 'No year set', + 'no_month' => 'No month set', + 'tag_title_nothing' => 'Default tags', + 'tag_title_balancingAct' => 'Balancing act tags', + 'tag_title_advancePayment' => 'Advance payment tags', + 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', + 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', ]; diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php old mode 100644 new mode 100755 index a572419aec..91c6f5ae34 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -1,4 +1,12 @@ 'Revenue account', 'amount' => 'Amount', 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'category' => 'Category', 'tags' => 'Tags', 'deletePermanently' => 'Delete permanently', @@ -47,6 +58,7 @@ return [ 'symbol' => 'Symbol', 'code' => 'Code', 'iban' => 'IBAN', + 'accountNumber' => 'Account number', 'csv' => 'CSV file', 'has_headers' => 'Headers', 'date_format' => 'Date format', @@ -70,40 +82,44 @@ return [ 'size' => 'Size', 'trigger' => 'Trigger', 'stop_processing' => 'Stop processing', - - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - - - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Supprimer la facture ":name"', - 'delete_budget' => 'Delete budget ":name"', - 'delete_category' => 'Delete category ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'delete_journal' => 'Delete transaction with description ":description"', - 'delete_attachment' => 'Delete attachment ":name"', - 'delete_rule' => 'Delete rule ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - - 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', - 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', - 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', - 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', - 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', - 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', - 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', - 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', - 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', - 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', - 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', - 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_config' => 'Include configuration file', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'csv_comma' => 'A comma (,)', + 'csv_semicolon' => 'A semicolon (;)', + 'csv_tab' => 'A tab (invisible)', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Supprimer la facture ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Delete attachment ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', ]; diff --git a/resources/lang/fr_FR/help.php b/resources/lang/fr_FR/help.php old mode 100644 new mode 100755 index 71f96fa6f2..1f2e858d08 --- a/resources/lang/fr_FR/help.php +++ b/resources/lang/fr_FR/help.php @@ -1,4 +1,12 @@ 'C\'est assez explicite.', 'main-content-end-title' => 'Fin !', 'main-content-end-text' => 'N\'oubliez pas que chaque page a un petit point d\'interrogation en haut à droite. Cliquez dessus pour obtenir de l\'aide concernant la page actuelle.', - - 'index' => 'index', 'home' => 'home', 'accounts-index' => 'accounts.index', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php old mode 100644 new mode 100755 index 9d7fa6e86a..51b0ded72c --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -1,6 +1,11 @@ 'Nom', @@ -19,6 +24,9 @@ return [ 'description' => 'Description', 'amount' => 'Amount', 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'from' => 'From', 'to' => 'To', 'budget' => 'Budget', diff --git a/resources/lang/fr_FR/pagination.php b/resources/lang/fr_FR/pagination.php old mode 100644 new mode 100755 index e16f862ea6..c86fc8b4e9 --- a/resources/lang/fr_FR/pagination.php +++ b/resources/lang/fr_FR/pagination.php @@ -1,19 +1,13 @@ '« Precedent', 'next' => 'Suivant »', - ]; diff --git a/resources/lang/fr_FR/passwords.php b/resources/lang/fr_FR/passwords.php old mode 100644 new mode 100755 index 49f2dfe6b1..8690468e9e --- a/resources/lang/fr_FR/passwords.php +++ b/resources/lang/fr_FR/passwords.php @@ -8,22 +8,10 @@ */ return [ - - /* - |-------------------------------------------------------------------------- - |-------------------------------------------------------------------------- - | - | The following language lines are the default lines which match reasons - | that are given by the password broker for a password update attempt - | has failed, such as for an invalid token or invalid new password. - | - */ - - "password" => "Le mot de passe doit contenir au moins 6 caractères et correspondre à la confirmation.", - "user" => "Aucun utilisateur avec cette addresse email.", - "token" => "Le jeton de réinitialisation de mot de passe est invalide.", - "sent" => "Nous avons envoyé votre lien de réinitialisation de mot de passe!", - "reset" => "Le mot de passe a été réinitialisé!", + 'password' => 'Passwords must be at least six characters and match the confirmation.', + 'user' => 'We can\'t find a user with that e-mail address.', + 'token' => 'This password reset token is invalid.', + 'sent' => 'We have e-mailed your password reset link!', + 'reset' => 'Your password has been reset!', 'blocked' => 'Nice try though.', - ]; diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php old mode 100644 new mode 100755 index 53c07b4811..1a5b75a783 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -1,69 +1,79 @@ 'This value is invalid for the selected trigger.', - 'rule_action_value' => 'This value is invalid for the selected action.', - 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', - 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', - 'file_attached' => 'Succesfully uploaded file ":name".', - 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', - 'file_too_large' => 'File ":name" is too large.', - "accepted" => "Le champ :attribute doit être accepté.", - "active_url" => "Le champ :attribute n'est pas une URL valide.", - "after" => "Le champ :attribute doit être une date postérieure au :date.", - "alpha" => "Le champ :attribute doit seulement contenir des lettres.", - "alpha_dash" => "Le champ :attribute doit seulement contenir des lettres, des chiffres et des tirets.", - "alpha_num" => "Le champ :attribute doit seulement contenir des chiffres et des lettres.", - "array" => "Le champ :attribute doit être un tableau.", - "unique_for_user" => "There already is an entry with this :attribute.", - "before" => "Le champ :attribute doit être une date antérieure au :date.", - 'unique_object_for_user' => 'This name is already in use', - 'unique_account_for_user' => 'This account name is already in use', - "between.numeric" => "La valeur de :attribute doit être comprise entre :min et :max.", - "between.file" => "Le fichier :attribute doit avoir une taille entre :min et :max kilo-octets.", - "between.string" => "Le texte :attribute doit avoir entre :min et :max caractères.", - "between.array" => "Le tableau :attribute doit avoir entre :min et :max éléments.", - "boolean" => "Le champ :attribute doit être vrai ou faux.", - "confirmed" => "Le champ de confirmation :attribute ne correspond pas.", - "date" => "Le champ :attribute n'est pas une date valide.", - "date_format" => "Le champ :attribute ne correspond pas au format :format.", - "different" => "Les champs :attribute et :other doivent être différents.", - "digits" => "Le champ :attribute doit avoir :digits chiffres.", - "digits_between" => "Le champ :attribute doit avoir entre :min et :max chiffres.", - "email" => "Le champ :attribute doit être une adresse email valide.", - "filled" => "Le champ :attribute est obligatoire.", - "exists" => "Le champ :attribute sélectionné est invalide.", - "image" => "Le champ :attribute doit être une image.", - "in" => "Le champ :attribute est invalide.", - "integer" => "Le champ :attribute doit être un entier.", - "ip" => "Le champ :attribute doit être une adresse IP valide.", - 'json' => 'Le champ :attribute doit être un document JSON valide.', - "max.numeric" => "La valeur de :attribute ne peut être supérieure à :max.", - "max.file" => "Le fichier :attribute ne peut être plus gros que :max kilo-octets.", - "max.string" => "Le texte de :attribute ne peut contenir plus de :max caractères.", - "max.array" => "Le tableau :attribute ne peut avoir plus de :max éléments.", - "mimes" => "Le champ :attribute doit être un fichier de type : :values.", - "min.numeric" => "La valeur de :attribute doit être supérieure à :min.", - "min.file" => "Le fichier :attribute doit être plus gros que :min kilo-octets.", - "min.string" => "Le texte :attribute doit contenir au moins :min caractères.", - "min.array" => "Le tableau :attribute doit avoir au moins :min éléments.", - "not_in" => "Le champ :attribute sélectionné n'est pas valide.", - "numeric" => "Le champ :attribute doit contenir un nombre.", - "regex" => "Le format du champ :attribute est invalide.", - "required" => "Le champ :attribute est obligatoire.", - "required_if" => "Le champ :attribute est obligatoire quand la valeur de :other est :value.", - 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', - "required_with" => "Le champ :attribute est obligatoire quand :values est présent.", - "required_with_all" => "Le champ :attribute est obligatoire quand :values est présent.", - "required_without" => "Le champ :attribute est obligatoire quand :values n'est pas présent.", - "required_without_all" => "Le champ :attribute est requis quand aucun de :values n'est présent.", - "same" => "Les champs :attribute et :other doivent être identiques.", - "size.numeric" => "La valeur de :attribute doit être :size.", - "size.file" => "La taille du fichier de :attribute doit être de :size kilo-octets.", - "size.string" => "Le texte de :attribute doit contenir :size caractères.", - "size.array" => "Le tableau :attribute doit contenir :size éléments.", - "unique" => "La valeur du champ :attribute est déjà utilisée.", - 'string' => 'Le champ :attribute doit être une chaîne de caractères.', - "url" => "Le format de l'URL de :attribute n'est pas valide.", - "timezone" => "Le champ :attribute doit être un fuseau horaire valide.", + 'iban' => 'This is not a valid IBAN.', + 'unique_account_number_for_user' => 'It looks like this account number is already in use.', + 'rule_trigger_value' => 'This value is invalid for the selected trigger.', + 'rule_action_value' => 'This value is invalid for the selected action.', + 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', + 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', + 'file_attached' => 'Succesfully uploaded file ":name".', + 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', + 'file_too_large' => 'File ":name" is too large.', + 'accepted' => 'Le champ :attribute doit être accepté.', + 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', + 'after' => 'Le champ :attribute doit être une date postérieure au :date.', + 'alpha' => 'Le champ :attribute doit seulement contenir des lettres.', + 'alpha_dash' => 'Le champ :attribute doit seulement contenir des lettres, des chiffres et des tirets.', + 'alpha_num' => 'Le champ :attribute doit seulement contenir des chiffres et des lettres.', + 'array' => 'Le champ :attribute doit être un tableau.', + 'unique_for_user' => 'There already is an entry with this :attribute.', + 'before' => 'Le champ :attribute doit être une date antérieure au :date.', + 'unique_object_for_user' => 'This name is already in use', + 'unique_account_for_user' => 'This account name is already in use', + 'between.numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', + 'between.file' => 'Le fichier :attribute doit avoir une taille entre :min et :max kilo-octets.', + 'between.string' => 'Le texte :attribute doit avoir entre :min et :max caractères.', + 'between.array' => 'Le tableau :attribute doit avoir entre :min et :max éléments.', + 'boolean' => 'Le champ :attribute doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', + 'date' => 'Le champ :attribute n\'est pas une date valide.', + 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', + 'different' => 'Les champs :attribute et :other doivent être différents.', + 'digits' => 'Le champ :attribute doit avoir :digits chiffres.', + 'digits_between' => 'Le champ :attribute doit avoir entre :min et :max chiffres.', + 'email' => 'Le champ :attribute doit être une adresse email valide.', + 'filled' => 'Le champ :attribute est obligatoire.', + 'exists' => 'Le champ :attribute sélectionné est invalide.', + 'image' => 'Le champ :attribute doit être une image.', + 'in' => 'Le champ :attribute est invalide.', + 'integer' => 'Le champ :attribute doit être un entier.', + 'ip' => 'Le champ :attribute doit être une adresse IP valide.', + 'json' => 'Le champ :attribute doit être un document JSON valide.', + 'max.numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', + 'max.file' => 'Le fichier :attribute ne peut être plus gros que :max kilo-octets.', + 'max.string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', + 'max.array' => 'Le tableau :attribute ne peut avoir plus de :max éléments.', + 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'min.numeric' => 'La valeur de :attribute doit être supérieure à :min.', + 'min.file' => 'Le fichier :attribute doit être plus gros que :min kilo-octets.', + 'min.string' => 'Le texte :attribute doit contenir au moins :min caractères.', + 'min.array' => 'Le tableau :attribute doit avoir au moins :min éléments.', + 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', + 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'regex' => 'Le format du champ :attribute est invalide.', + 'required' => 'Le champ :attribute est obligatoire.', + 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', + 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', + 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', + 'required_with_all' => 'Le champ :attribute est obligatoire quand :values est présent.', + 'required_without' => 'Le champ :attribute est obligatoire quand :values n\'est pas présent.', + 'required_without_all' => 'Le champ :attribute est requis quand aucun de :values n\'est présent.', + 'same' => 'Les champs :attribute et :other doivent être identiques.', + 'size.numeric' => 'La valeur de :attribute doit être :size.', + 'size.file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', + 'size.string' => 'Le texte de :attribute doit contenir :size caractères.', + 'size.array' => 'Le tableau :attribute doit contenir :size éléments.', + 'unique' => 'La valeur du champ :attribute est déjà utilisée.', + 'string' => 'Le champ :attribute doit être une chaîne de caractères.', + 'url' => 'Le format de l\'URL de :attribute n\'est pas valide.', + 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', + '2fa_code' => 'The :attribute field is invalid.', ]; diff --git a/resources/lang/nl_NL/breadcrumbs.php b/resources/lang/nl_NL/breadcrumbs.php old mode 100644 new mode 100755 index 52e1b97a96..8459bf3fc9 --- a/resources/lang/nl_NL/breadcrumbs.php +++ b/resources/lang/nl_NL/breadcrumbs.php @@ -1,60 +1,45 @@ 'Home', - - // accounts 'cash_accounts' => 'Contant geldrekeningen', 'edit_account' => 'Wijzig rekening ":name"', - - // currencies 'edit_currency' => 'Wijzig valuta ":name"', 'delete_currency' => 'Verwijder valuta ":name"', - - // piggy banks 'newPiggyBank' => 'Nieuw spaarpotje', 'edit_piggyBank' => 'Wijzig spaarpotje ":name"', - - // top menu 'preferences' => 'Voorkeuren', 'profile' => 'Profiel', 'changePassword' => 'Verander je wachtwoord', - - // bills 'bills' => 'Contracten', 'newBill' => 'Nieuw contract', 'edit_bill' => 'Wijzig contract ":name"', 'delete_bill' => 'Verwijder contract ":name"', - - // reports 'reports' => 'Overzichten', 'monthly_report' => 'Maandoverzicht :date', 'monthly_report_shared' => 'Maandoverzicht :date (inclusief gedeelde rekeningen)', 'yearly_report' => 'Jaaroverzicht :date', 'yearly_report_shared' => 'Jaaroverzicht :date (inclusief gedeelde rekeningen)', 'budget_report' => 'Budgetoverzicht :date', - - // search 'searchResult' => 'Zoeken naar ":query"', - - // transaction lists. 'withdrawal_list' => 'Uitgaven', 'deposit_list' => 'Inkomsten', 'transfer_list' => 'Overschrijvingen', 'transfers_list' => 'Overschrijvingen', - - // create transactions 'create_withdrawal' => 'Sla nieuwe uitgave op', 'create_deposit' => 'Sla nieuwe inkomsten op', 'create_transfer' => 'Sla nieuwe overschrijving op', - - // edit transactions 'edit_journal' => 'Wijzig transactie ":description"', 'delete_journal' => 'Verwijder transactie ":description"', - - // tags 'tags' => 'Tags', 'createTag' => 'Maak nieuwe tag', 'edit_tag' => 'Wijzig tag ":tag"', 'delete_tag' => 'Verwijder tag ":tag"', - ]; diff --git a/resources/lang/nl_NL/config.php b/resources/lang/nl_NL/config.php old mode 100644 new mode 100755 index 55f590c9a3..518974346d --- a/resources/lang/nl_NL/config.php +++ b/resources/lang/nl_NL/config.php @@ -1,8 +1,20 @@ 'nl, Dutch, nl_NL, nl_NL.utf8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - + 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'week %W, %Y', + 'quarter_of_year' => '%B %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php old mode 100644 new mode 100755 index 5f02d0c10a..d1df014f6b --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -1,111 +1,177 @@ 'Deze taal is nog niet helemaal af', - 'test' => 'Nederlands geselecteerd!', - 'close' => 'Sluiten', - 'pleaseHold' => 'Momentje...', - 'actions' => 'Acties', - 'edit' => 'Wijzig', - 'delete' => 'Verwijder', - 'welcomeBack' => 'Hoe staat het er voor?', - 'everything' => 'Alles', - 'customRange' => 'Zelf bereik kiezen', - 'apply' => 'Go', - 'cancel' => 'Annuleren', - 'from' => 'Van', - 'to' => 'Tot', - 'total_sum' => 'Totale som', - 'period_sum' => 'Som van periode', - 'showEverything' => 'Laat alles zien', - 'never' => 'Nooit', - 'search_results_for' => 'Zoekresultaten voor ":query"', - 'bounced_error' => 'Het emailtje naar :email kwam nooit aan.', - 'deleted_error' => 'Deze gegevens zijn niet correct.', - 'general_blocked_error' => 'Je account is uitgeschakeld, je kan helaas niet inloggen.', - 'removed_amount' => ':amount weggehaald', - 'added_amount' => ':amount toegevoegd', - 'asset_account_role_help' => 'Voorkeuren die voortkomen uit je keuze hier kan je later aangeven.', - 'Opening balance' => 'Startsaldo', - 'create_new_stuff' => 'Nieuw', - 'new_withdrawal' => 'Nieuwe uitgave', - 'new_deposit' => 'Nieuwe inkomsten', - 'new_transfer' => 'Nieuwe overschrijving', - 'new_asset_account' => 'Nieuwe betaalrekening', - 'new_expense_account' => 'Nieuwe crediteur', - 'new_revenue_account' => 'Nieuwe debiteur', - 'new_budget' => 'Nieuw budget', - 'new_bill' => 'Nieuw contract', + 'language_incomplete' => 'Deze taal is nog niet helemaal af', + 'test' => 'Nederlands geselecteerd!', + 'close' => 'Sluiten', + 'pleaseHold' => 'Momentje...', + 'actions' => 'Acties', + 'edit' => 'Wijzig', + 'delete' => 'Verwijder', + 'welcomeBack' => 'Hoe staat het er voor?', + 'everything' => 'Alles', + 'customRange' => 'Zelf bereik kiezen', + 'apply' => 'Go', + 'cancel' => 'Annuleren', + 'from' => 'Van', + 'to' => 'Tot', + 'total_sum' => 'Totale som', + 'period_sum' => 'Som van periode', + 'showEverything' => 'Laat alles zien', + 'never' => 'Nooit', + 'search_results_for' => 'Zoekresultaten voor ":query"', + 'bounced_error' => 'Het emailtje naar :email kwam nooit aan.', + 'deleted_error' => 'Deze gegevens zijn niet correct.', + 'general_blocked_error' => 'Je account is uitgeschakeld, je kan helaas niet inloggen.', + 'removed_amount' => ':amount weggehaald', + 'added_amount' => ':amount toegevoegd', + 'asset_account_role_help' => 'Voorkeuren die voortkomen uit je keuze hier kan je later aangeven.', + 'Opening balance' => 'Startsaldo', + 'create_new_stuff' => 'Nieuw', + 'new_withdrawal' => 'Nieuwe uitgave', + 'new_deposit' => 'Nieuwe inkomsten', + 'new_transfer' => 'Nieuwe overschrijving', + 'new_asset_account' => 'Nieuwe betaalrekening', + 'new_expense_account' => 'Nieuwe crediteur', + 'new_revenue_account' => 'Nieuwe debiteur', + 'new_budget' => 'Nieuw budget', + 'new_bill' => 'Nieuw contract', + 'block_account_logout' => 'Je bent helaas uitgelogd. Geblokkeerde accounts kunnen deze site niet gebruiken. Heb je een geldig e-mailadres gebruikt toen je je registreerde?', + 'flash_success' => 'Gelukt!', + 'flash_info' => 'Melding', + 'flash_warning' => 'Waarschuwing!', + 'flash_error' => 'Fout!', + 'flash_info_multiple' => 'Er is één melding|Er zijn :count meldingen', + 'flash_error_multiple' => 'Er is één fout|Er zijn :count fouten', + 'net_worth' => 'Kapitaal', + 'route_has_no_help' => 'Er is geen helptekst voor deze pagina, of er is geen helptekst in het Nederlands.', + 'two_factor_welcome' => 'Hoi :user!', + 'two_factor_enter_code' => 'Vul je authenticatiecode in. Je authenticatieapplicatie kan deze voor je genereren.', + 'two_factor_code_here' => 'Code', + 'authenticate' => 'Inloggen', + 'two_factor_forgot' => 'Ik kan geen codes meer genereren.', + 'two_factor_lost_header' => 'Kan je geen codes meer genereren?', + 'two_factor_lost_intro' => 'Dit is helaas niet iets dat je kan resetten vanaf de site. Je hebt twee keuzes.', + 'two_factor_lost_fix_self' => 'Als dit jouw installatie van Firefly III is, vind je in de logboeken (storage/logs) instructies.', + 'two_factor_lost_fix_owner' => 'Zo niet, stuur dan een e-mail naar :site_owner en vraag of ze je authenticatie in twee stappen willen resetten.', + + // export data: + 'import_and_export' => 'Import en export', + 'export_data' => 'Exporteren', + 'export_data_intro' => 'Om te backuppen, of wanneer je naar een ander systeem verhuist.', + 'export_format' => 'Exporteerformaat', + 'export_format_csv' => 'Komma-gescheiden bestand (CSV)', + 'export_format_mt940' => 'MT940 bestand', + 'export_included_accounts' => 'Exporteer transacties van deze rekeningen', + 'include_config_help' => 'Voor het makkelijk opnieuw importeren in Firefly III', + 'include_old_uploads_help' => 'Firefly III gooit je oude geïmporteerde CSV bestanden niet weg. Je kan ze meenemen in je exportbestand.', + 'do_export' => 'Exporteren', + 'export_status_never_started' => 'Het exporteren is nog niet begonnen', + 'export_status_make_exporter' => 'Exporteerding maken...', + 'export_status_collecting_journals' => 'Overboekingen verzamelen...', + 'export_status_collected_journals' => 'Overboekingen verzameld!', + 'export_status_converting_to_export_format' => 'Overboekingen overzetten...', + 'export_status_converted_to_export_format' => 'Overboekingen overgezet!', + 'export_status_creating_journal_file' => 'Exportbestand maken...', + 'export_status_created_journal_file' => 'Exportbestand gemaakt!', + 'export_status_collecting_attachments' => 'Bijlagen verzamelen...', + 'export_status_collected_attachments' => 'Bijlagen verzameld!', + 'export_status_collecting_old_uploads' => 'Oude uploads verzamelen...', + 'export_status_collected_old_uploads' => 'Oude uploads verzameld!', + 'export_status_creating_config_file' => 'Configuratiebestand maken...', + 'export_status_created_config_file' => 'Configuratiebestand gemaakt!', + 'export_status_creating_zip_file' => 'Zipbestand maken...', + 'export_status_created_zip_file' => 'Zipbestand gemaakt!', + 'export_status_finished' => 'Klaar met exportbestand! Hoera!', + 'export_data_please_wait' => 'Een ogenblik geduld...', + 'attachment_explanation' => 'Het bestand \':attachment_name\' (#:attachment_id) werd oorspronkelijk geüpload naar (Engels) :type \':description\' (#:journal_id), met datum :date en bedrag :amount.', // rules - 'rules' => 'Regels', - 'rules_explanation' => 'Hier kan je regels instellen. Regels worden in werking gesteld als je een bij-, afschrijving of overboeking maakt (of verandert). Als die transactie bepaalde eigenschappen heeft (zogenaamde "triggers") zal Firefly de bijbehorende "acties" uitvoeren. Gecombineerd kan je Firefly op een bepaalde manier laten reageren op nieuwe transacties.', - 'rule_name' => 'Regelnaam', - 'rule_triggers' => 'Regel reageert op', - 'rule_actions' => 'Regel zal dan', - 'new_rule' => 'Nieuwe regel', - 'new_rule_group' => 'Nieuwe regelgroep', - 'rule_priority_up' => 'Geef regel meer prioriteit', - 'rule_priority_down' => 'Geef regel minder prioriteit', - 'make_new_rule_group' => 'Maak nieuwe regelgroep', - 'store_new_rule_group' => 'Sla nieuwe regelgroep op', - 'created_new_rule_group' => 'Nieuwe regelgroep ":title" opgeslagen!', - 'updated_rule_group' => 'Regelgroep ":title" geüpdatet.', - 'edit_rule_group' => 'Wijzig regelgroep ":title"', - 'delete_rule_group' => 'Verwijder regelgroep ":title"', - 'deleted_rule_group' => 'Regelgroep ":title" verwijderd', - 'update_rule_group' => 'Wijzig regelgroep', - 'no_rules_in_group' => 'Er zijn geen regels in deze groep', - 'move_rule_group_up' => 'Verplaats regelgroep omhoog', - 'move_rule_group_down' => 'Verplaats regelgroep omlaag', - 'save_rules_by_moving' => 'Red deze regel(s) van de ondergang door ze te verplaatsen naar een andere regelgroep:', - 'make_new_rule' => 'Maak een nieuwe regel in regelgroep ":title"', - 'rule_help_stop_processing' => 'Zet hier een vinkje om latere regels in deze groep te negeren.', - 'rule_help_active' => 'Niet actieve regels zullen nooit worden gecontroleerd.', - 'stored_new_rule' => 'Nieuwe regel ":title" opgeslagen', - 'deleted_rule' => 'Regel ":title" verwijderd', - 'store_new_rule' => 'Sla nieuwe regel op', - 'updated_rule' => 'Regel ":title" geüpdatet', - 'default_rule_group_name' => 'Standaard regels', - 'default_rule_group_description' => 'Al je regels die niet in een bepaalde groep zitten.', - 'default_rule_name' => 'Je eerste standaardregel', - 'default_rule_description' => 'Deze regel is een voorbeeld. Je kan hem rustig verwijderen.', - 'default_rule_trigger_description' => 'The Man Who Sold the World', - 'default_rule_trigger_from_account' => 'David Bowie', - 'default_rule_action_prepend' => 'Kocht de wereld van ', - 'default_rule_action_set_category' => 'Grote uitgaven', - - 'trigger' => 'Trigger', - 'trigger_value' => 'Trigger bij waarde', - 'stop_processing_other_triggers' => 'Reageer niet meer op andere triggers', - 'add_rule_trigger' => 'Nieuwe trigger toevoegen', - 'action' => 'Actie', - 'action_value' => 'Actie-waarde', - 'stop_executing_other_actions' => 'Voer verdere acties niet uit', - 'add_rule_action' => 'Nieuwe actie toevoegen', - 'edit_rule' => 'Wijzig regel ":title"', - 'update_rule' => 'Werk regel bij', + 'rules' => 'Regels', + 'rules_explanation' => 'Hier kan je regels instellen. Regels worden in werking gesteld als je een bij-, afschrijving of overboeking maakt (of verandert). Als die transactie bepaalde eigenschappen heeft (zogenaamde "triggers") zal Firefly de bijbehorende "acties" uitvoeren. Gecombineerd kan je Firefly op een bepaalde manier laten reageren op nieuwe transacties.', + 'rule_name' => 'Regelnaam', + 'rule_triggers' => 'Regel reageert op', + 'rule_actions' => 'Regel zal dan', + 'new_rule' => 'Nieuwe regel', + 'new_rule_group' => 'Nieuwe regelgroep', + 'rule_priority_up' => 'Geef regel meer prioriteit', + 'rule_priority_down' => 'Geef regel minder prioriteit', + 'make_new_rule_group' => 'Maak nieuwe regelgroep', + 'store_new_rule_group' => 'Sla nieuwe regelgroep op', + 'created_new_rule_group' => 'Nieuwe regelgroep ":title" opgeslagen!', + 'updated_rule_group' => 'Regelgroep ":title" geüpdatet.', + 'edit_rule_group' => 'Wijzig regelgroep ":title"', + 'delete_rule_group' => 'Verwijder regelgroep ":title"', + 'deleted_rule_group' => 'Regelgroep ":title" verwijderd', + 'update_rule_group' => 'Wijzig regelgroep', + 'no_rules_in_group' => 'Er zijn geen regels in deze groep', + 'move_rule_group_up' => 'Verplaats regelgroep omhoog', + 'move_rule_group_down' => 'Verplaats regelgroep omlaag', + 'save_rules_by_moving' => 'Red deze regel(s) van de ondergang door ze te verplaatsen naar een andere regelgroep:', + 'make_new_rule' => 'Maak een nieuwe regel in regelgroep ":title"', + 'rule_help_stop_processing' => 'Zet hier een vinkje om latere regels in deze groep te negeren.', + 'rule_help_active' => 'Niet actieve regels zullen nooit worden gecontroleerd.', + 'stored_new_rule' => 'Nieuwe regel ":title" opgeslagen', + 'deleted_rule' => 'Regel ":title" verwijderd', + 'store_new_rule' => 'Sla nieuwe regel op', + 'updated_rule' => 'Regel ":title" geüpdatet', + 'default_rule_group_name' => 'Standaard regels', + 'default_rule_group_description' => 'Al je regels die niet in een bepaalde groep zitten.', + 'default_rule_name' => 'Je eerste standaardregel', + 'default_rule_description' => 'Deze regel is een voorbeeld. Je kan hem rustig verwijderen.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Kocht de wereld van ', + 'default_rule_action_set_category' => 'Grote uitgaven', + 'trigger' => 'Trigger', + 'trigger_value' => 'Trigger bij waarde', + 'stop_processing_other_triggers' => 'Reageer niet meer op andere triggers', + 'add_rule_trigger' => 'Nieuwe trigger toevoegen', + 'action' => 'Actie', + 'action_value' => 'Actie-waarde', + 'stop_executing_other_actions' => 'Voer verdere acties niet uit', + 'add_rule_action' => 'Nieuwe actie toevoegen', + 'edit_rule' => 'Wijzig regel ":title"', + 'delete_rule' => 'Verwijder regel ":title"', + 'update_rule' => 'Werk regel bij', + 'test_rule_triggers' => 'Bekijk welke transacties hieraan voldoen', + 'warning_transaction_subset' => 'Je ziet hier maximaal :max_num_transactions transacties omdat je anders veel te lang moet wachten', + 'warning_no_matching_transactions' => 'Niks gevonden in je laatste :num_transactions transacties.', + 'warning_no_valid_triggers' => 'Geen geldige triggers gevonden.', + 'execute_on_existing_transactions' => 'Toepassen op bestaande transacties', + 'execute_on_existing_transactions_intro' => 'Wanneer een regel of groep is veranderd of toegevoegd, kun je hem hier uitvoeren voor bestaande transacties', + 'execute_on_existing_transactions_short' => 'Bestaande transacties', + 'executed_group_on_existing_transactions' => 'Regelgroep ":title" is uitgevoerd op bestaande transacties', + 'execute_group_on_existing_transactions' => 'Regelgroep uitvoeren op bestaande transacties', + 'include_transactions_from_accounts' => 'Gebruik transacties van deze rekeningen', + 'execute' => 'Uitvoeren', // actions and triggers - 'rule_trigger_user_action' => 'Gebruikersactie is ":trigger_value"', - 'rule_trigger_from_account_starts' => 'Bronrekeningnaam begint met ":trigger_value"', - 'rule_trigger_from_account_ends' => 'Bronrekeningnaam eindigt op ":trigger_value"', - 'rule_trigger_from_account_is' => 'Bronrekeningnaam is ":trigger_value"', - 'rule_trigger_from_account_contains' => 'Bronrekeningnaam bevat ":trigger_value"', - 'rule_trigger_to_account_starts' => 'Doelrekeningnaam begint met ":trigger_value"', - 'rule_trigger_to_account_ends' => 'Doelrekeningnaam eindigt op ":trigger_value"', - 'rule_trigger_to_account_is' => 'Doelrekeningnaam is ":trigger_value"', - 'rule_trigger_to_account_contains' => 'Doelrekeningnaam bevat ":trigger_value"', - 'rule_trigger_transaction_type' => 'Transactiesoort is ":trigger_value" (Engels)', - 'rule_trigger_amount_less' => 'Bedrag is minder dan :trigger_value', - 'rule_trigger_amount_exactly' => 'Bedrag is :trigger_value', - 'rule_trigger_amount_more' => 'Bedrag is meer dan :trigger_value', - 'rule_trigger_description_starts' => 'Omschrijving begint met ":trigger_value"', - 'rule_trigger_description_ends' => 'Omschrijving eindigt op ":trigger_value"', - 'rule_trigger_description_contains' => 'Omschrijving bevat ":trigger_value"', - 'rule_trigger_description_is' => 'Omschrijving is ":trigger_value"', - + 'rule_trigger_user_action' => 'Gebruikersactie is ":trigger_value"', + 'rule_trigger_from_account_starts' => 'Bronrekeningnaam begint met ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Bronrekeningnaam eindigt op ":trigger_value"', + 'rule_trigger_from_account_is' => 'Bronrekeningnaam is ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Bronrekeningnaam bevat ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Doelrekeningnaam begint met ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Doelrekeningnaam eindigt op ":trigger_value"', + 'rule_trigger_to_account_is' => 'Doelrekeningnaam is ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Doelrekeningnaam bevat ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transactiesoort is ":trigger_value" (Engels)', + 'rule_trigger_amount_less' => 'Bedrag is minder dan :trigger_value', + 'rule_trigger_amount_exactly' => 'Bedrag is :trigger_value', + 'rule_trigger_amount_more' => 'Bedrag is meer dan :trigger_value', + 'rule_trigger_description_starts' => 'Omschrijving begint met ":trigger_value"', + 'rule_trigger_description_ends' => 'Omschrijving eindigt op ":trigger_value"', + 'rule_trigger_description_contains' => 'Omschrijving bevat ":trigger_value"', + 'rule_trigger_description_is' => 'Omschrijving is ":trigger_value"', 'rule_trigger_from_account_starts_choice' => 'Bronrekening naam begint met..', 'rule_trigger_from_account_ends_choice' => 'Bronrekening eindigt op..', 'rule_trigger_from_account_is_choice' => 'Bronrekening is..', @@ -122,486 +188,498 @@ return [ 'rule_trigger_description_ends_choice' => 'Omschrijving eindigt op..', 'rule_trigger_description_contains_choice' => 'Omschrijving bevat..', 'rule_trigger_description_is_choice' => 'Omschrijving is..', - - 'rule_trigger_store_journal' => 'Als een transactie wordt gemaakt', - 'rule_trigger_update_journal' => 'Als een transactie wordt bewerkt', - - 'rule_action_set_category' => 'Verander categorie naar ":action_value"', - 'rule_action_clear_category' => 'Maak categorie-veld leeg', - 'rule_action_set_budget' => 'Sla op onder budget ":action_value"', - 'rule_action_clear_budget' => 'Sla op zonder budget', - 'rule_action_add_tag' => 'Voeg tag ":action_value" toe', - 'rule_action_remove_tag' => 'Haal tag ":action_value" weg', - 'rule_action_remove_all_tags' => 'Haal alle tags weg', - 'rule_action_set_description' => 'Geef omschrijving ":action_value"', - 'rule_action_append_description' => 'Zet ":action_value" voor de omschrijving', - 'rule_action_prepend_description' => 'Zet ":action_value" voor de omschrijving', - - 'rule_action_set_category_choice' => 'Geef categorie..', - 'rule_action_clear_category_choice' => 'Geef geen categorie', - 'rule_action_set_budget_choice' => 'Sla op onder budget..', - 'rule_action_clear_budget_choice' => 'Maak budget-veld leeg', - 'rule_action_add_tag_choice' => 'Voeg tag toe..', - 'rule_action_remove_tag_choice' => 'Haal tag weg..', - 'rule_action_remove_all_tags_choice' => 'Haal alle tags weg', - 'rule_action_set_description_choice' => 'Geef omschrijving..', - 'rule_action_append_description_choice' => 'Zet .. achter de omschrijving', - 'rule_action_prepend_description_choice' => 'Zet .. voor de omschrijving', + 'rule_trigger_store_journal' => 'Als een transactie wordt gemaakt', + 'rule_trigger_update_journal' => 'Als een transactie wordt bewerkt', + 'rule_action_set_category' => 'Verander categorie naar ":action_value"', + 'rule_action_clear_category' => 'Maak categorie-veld leeg', + 'rule_action_set_budget' => 'Sla op onder budget ":action_value"', + 'rule_action_clear_budget' => 'Sla op zonder budget', + 'rule_action_add_tag' => 'Voeg tag ":action_value" toe', + 'rule_action_remove_tag' => 'Haal tag ":action_value" weg', + 'rule_action_remove_all_tags' => 'Haal alle tags weg', + 'rule_action_set_description' => 'Geef omschrijving ":action_value"', + 'rule_action_append_description' => 'Zet ":action_value" voor de omschrijving', + 'rule_action_prepend_description' => 'Zet ":action_value" voor de omschrijving', + 'rule_action_set_category_choice' => 'Geef categorie..', + 'rule_action_clear_category_choice' => 'Geef geen categorie', + 'rule_action_set_budget_choice' => 'Sla op onder budget..', + 'rule_action_clear_budget_choice' => 'Maak budget-veld leeg', + 'rule_action_add_tag_choice' => 'Voeg tag toe..', + 'rule_action_remove_tag_choice' => 'Haal tag weg..', + 'rule_action_remove_all_tags_choice' => 'Haal alle tags weg', + 'rule_action_set_description_choice' => 'Geef omschrijving..', + 'rule_action_append_description_choice' => 'Zet .. achter de omschrijving', + 'rule_action_prepend_description_choice' => 'Zet .. voor de omschrijving', // tags - 'store_new_tag' => 'Sla tag op', - 'update_tag' => 'Sla wijzigingen op', - 'no_location_set' => 'Zonder plaats', - 'meta_data' => 'Metagegevens', - 'location' => 'Plaats', + 'store_new_tag' => 'Sla tag op', + 'update_tag' => 'Sla wijzigingen op', + 'no_location_set' => 'Zonder plaats', + 'meta_data' => 'Metagegevens', + 'location' => 'Plaats', // preferences - 'pref_home_screen_accounts' => 'Voorpaginarekeningen', - 'pref_home_screen_accounts_help' => 'Welke betaalrekeningen wil je op de voorpagina zien?', - 'pref_budget_settings' => 'Budgetinstellingen', - 'pref_budget_settings_help' => 'Wat is het maximale bedrag dat je voor een budget kan instellen?', - 'pref_view_range' => 'Bereik', - 'pref_view_range_help' => 'Sommige pagina\'s springen naar een standaard bereik. Welk bereik heeft jouw voorkeur?', - 'pref_1D' => 'Eén dag', - 'pref_1W' => 'Eén week', - 'pref_1M' => 'Eén maand', - 'pref_3M' => 'Drie maanden (kwartaal)', - 'pref_6M' => 'Zes maanden', - 'pref_languages' => 'Talen', - 'pref_languages_help' => 'Firefly III ondersteunt meerdere talen. Welke heeft jouw voorkeur?', - 'pref_custom_fiscal_year' => 'Instellingen voor boekjaar', - 'pref_custom_fiscal_year_label' => 'Ingeschakeld', - 'pref_custom_fiscal_year_help' => 'Voor in landen die een boekjaar gebruiken anders dan 1 januari tot 31 december', - 'pref_fiscal_year_start_label' => 'Start van boekjaar', - 'pref_save_settings' => 'Instellingen opslaan', + 'pref_home_screen_accounts' => 'Voorpaginarekeningen', + 'pref_home_screen_accounts_help' => 'Welke betaalrekeningen wil je op de voorpagina zien?', + 'pref_budget_settings' => 'Budgetinstellingen', + 'pref_budget_settings_help' => 'Wat is het maximale bedrag dat je voor een budget kan instellen?', + 'pref_view_range' => 'Bereik', + 'pref_view_range_help' => 'Sommige pagina\'s springen naar een standaard bereik. Welk bereik heeft jouw voorkeur?', + 'pref_1D' => 'Eén dag', + 'pref_1W' => 'Eén week', + 'pref_1M' => 'Eén maand', + 'pref_3M' => 'Drie maanden (kwartaal)', + 'pref_6M' => 'Zes maanden', + 'pref_languages' => 'Talen', + 'pref_languages_help' => 'Firefly III ondersteunt meerdere talen. Welke heeft jouw voorkeur?', + 'pref_custom_fiscal_year' => 'Instellingen voor boekjaar', + 'pref_custom_fiscal_year_label' => 'Ingeschakeld', + 'pref_custom_fiscal_year_help' => 'Voor in landen die een boekjaar gebruiken anders dan 1 januari tot 31 december', + 'pref_fiscal_year_start_label' => 'Start van boekjaar', + 'pref_two_factor_auth' => 'Authenticatie in twee stappen', + 'pref_two_factor_auth_help' => 'Als je authenticatie in twee stappen (ook wel twee-factor authenticatie genoemd) inschakelt voeg je een extra beveiligingslaag toe aan je account. Je logt in met iets dat je weet (je wachtwoord) en iets dat je hebt (een verificatiecode). Verificatiecodes worden gegeneerd door apps op je telefoon, zoals Authy en Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Authenticatie in twee stappen inschakelen', + 'pref_two_factor_auth_disabled' => 'Je verificatiecode voor authenticatie in twee stappen is verwijderd, en uitgeschakeld', + 'pref_two_factor_auth_remove_it' => 'Vergeet niet om je Firefly account uit je authenticatie appje te verwijderen!', + 'pref_two_factor_auth_code' => 'Bevestig de code', + 'pref_two_factor_auth_code_help' => 'Scan deze QR code met een app op je telefoon (zoals Authy of Google Authenticator). Vul de code die je terug krijgt hier in.', + 'pref_two_factor_auth_reset_code' => 'Reset de verificatiecode', + 'pref_two_factor_auth_remove_code' => 'Verwijder de verificatiecode', + 'pref_two_factor_auth_remove_will_disable' => '(hiermee zet je authenticatie in twee stappen ook uit)', + 'pref_save_settings' => 'Instellingen opslaan', // profile: - 'change_your_password' => 'Verander je wachtwoord', - 'delete_account' => 'Verwijder je account', - 'current_password' => 'Huidige wachtwoord', - 'new_password' => 'Nieuw wachtwoord', - 'new_password_again' => 'Nieuw wachtwoord (bevestiging)', - 'delete_your_account' => 'Verwijder je account', - 'delete_your_account_help' => 'Als je je account verwijdert worden ook al je rekeningen, transacties en alle andere zaken verwijderd. Alles is dan WEG.', - 'delete_your_account_password' => 'Voer je wachtwoord in om door te gaan.', - 'password' => 'Wachtwoord', - 'are_you_sure' => 'Zeker weten? Je kan niet meer terug!', - 'delete_account_button' => 'VERWIJDER je account', - 'invalid_current_password' => 'Huidige wachtwoord is niet geldig!', - 'password_changed' => 'Je wachtwoord is veranderd!', - 'should_change' => 'Vul ook echt een ander wachtwoord in.', - 'invalid_password' => 'Ongeldig wachtwoord!', + 'change_your_password' => 'Verander je wachtwoord', + 'delete_account' => 'Verwijder je account', + 'current_password' => 'Huidige wachtwoord', + 'new_password' => 'Nieuw wachtwoord', + 'new_password_again' => 'Nieuw wachtwoord (bevestiging)', + 'delete_your_account' => 'Verwijder je account', + 'delete_your_account_help' => 'Als je je account verwijdert worden ook al je rekeningen, transacties en alle andere zaken verwijderd. Alles is dan WEG.', + 'delete_your_account_password' => 'Voer je wachtwoord in om door te gaan.', + 'password' => 'Wachtwoord', + 'are_you_sure' => 'Zeker weten? Je kan niet meer terug!', + 'delete_account_button' => 'VERWIJDER je account', + 'invalid_current_password' => 'Huidige wachtwoord is niet geldig!', + 'password_changed' => 'Je wachtwoord is veranderd!', + 'should_change' => 'Vul ook echt een ander wachtwoord in.', + 'invalid_password' => 'Ongeldig wachtwoord!', // attachments - 'nr_of_attachments' => 'Eén bijlage|:count bijlagen', - 'attachments' => 'Bijlagen', - 'edit_attachment' => 'Wijzig bijlage ":name"', - 'update_attachment' => 'Update bijlage', - 'delete_attachment' => 'Verwijder bijlage ":name"', - 'attachment_deleted' => 'Bijlage ":name" verwijderd', - 'upload_max_file_size' => 'Maximale grootte: :size', + 'nr_of_attachments' => 'Eén bijlage|:count bijlagen', + 'attachments' => 'Bijlagen', + 'edit_attachment' => 'Wijzig bijlage ":name"', + 'update_attachment' => 'Update bijlage', + 'delete_attachment' => 'Verwijder bijlage ":name"', + 'attachment_deleted' => 'Bijlage ":name" verwijderd', + 'upload_max_file_size' => 'Maximale grootte: :size', // tour: - 'prev' => 'Vorige', - 'next' => 'Volgende', - 'end-tour' => 'Einde', - 'pause' => 'Pauze', + 'prev' => 'Vorige', + 'next' => 'Volgende', + 'end-tour' => 'Einde', + 'pause' => 'Pauze', // transaction index - 'title_expenses' => 'Uitgaven', - 'title_withdrawal' => 'Uitgaven', - 'title_revenue' => 'Inkomsten', - 'title_deposit' => 'Inkomsten', - 'title_transfer' => 'Overboekingen', - 'title_transfers' => 'Overboekingen', + 'title_expenses' => 'Uitgaven', + 'title_withdrawal' => 'Uitgaven', + 'title_revenue' => 'Inkomsten', + 'title_deposit' => 'Inkomsten', + 'title_transfer' => 'Overboekingen', + 'title_transfers' => 'Overboekingen', // csv import: - 'csv_import' => 'Importeer CSV-bestand', - 'csv' => 'CSV', - 'csv_index_title' => 'Upload en importeer een kommagescheiden tekstbestand', - 'csv_define_column_roles' => 'Bepaal kolominhoud', - 'csv_map_values' => 'Leg relaties met kolomwaardes', - 'csv_download_config' => 'Download CSV configuratiebestand.', - 'csv_index_text' => 'Met deze (en de komende) pagina\'s kan je kommagescheiden tekstbestanden importeren. Deze tool is gebaseerd op de prachtige tool van Atlassian. Om te beginnen selecteer je jouw tekstbestand bij "CSV-bestand". Als je hulp nodig hebt, klik dan op het -icoontje rechtsboven.', - 'csv_index_beta_warning' => 'Deze tool is nog erg experimenteel. Wees dus voorzichtig.', - 'csv_header_help' => 'Zet hier een vinkje als de eerste rij van het CSV-bestand de namen van de kolommen bevat', - 'csv_date_help' => 'Het gebruikte datumformaat in jouw bestand. Gebruik het formaat zoals deze pagina het uitlegt (Engels). Het standaardformaat kan omgaan met data zoals deze: :dateExample.', - 'csv_csv_file_help' => 'Voer hier je kommagescheiden tekstbestand in. Je kan er maar één tegelijkertijd invoeren.', - 'csv_csv_config_file_help' => 'Voer hier je configuratiebestand in. Als je deze niet hebt, geen zorgen. Latere stappen leggen dit uit.', - 'csv_upload_button' => 'Begin de import', - 'csv_column_roles_title' => 'Bepaal de inhoud van elke kolom', - 'csv_column_roles_text' => 'Firefly kan niet automatisch ontdekken wat elke kolom betekent. Je moet het zelf aangeven. Gebruik de voorbeeldgegevens als je het ook niet zeker weet. Klik op het vraagteken-icoontje (rechtsboven) om te ontdekken wat elke kolomsoort precies is. Als de kolominhoud een directe relatie heeft met gegevens die al in Firefly staan, gebruik dan het vinkje. Tijdens de volgende stap komt Firefly hier dan op terug.', - 'csv_column_roles_table' => 'Kolominhoud', - 'csv_column' => 'CSV-kolom', - 'csv_column_name' => 'CSV-kolomnaam', - 'csv_column_example' => 'Voorbeeldgegevens', - 'csv_column_role' => 'Kolom bevat?', - 'csv_do_map_value' => 'Directe relatie?', - 'csv_continue' => 'Naar de volgende stap', - 'csv_go_back' => 'Terug naar de vorige stap', - 'csv_map_title' => 'Leg relaties met kolomwaardes', - 'csv_map_text' => 'Sommige kolommen bevatten waardes die misschien al in Firefly bestaan. Selecteer hier de juiste combinaties zodat het importeren netjes aansluit bij je huidige gegevens.', - 'csv_field_value' => 'Veldwaarde', - 'csv_field_mapped_to' => 'Is gelijk aan', - 'csv_do_not_map' => 'Geen relatie', - 'csv_download_config_title' => 'Download importconfiguratie', - 'csv_download_config_text' => 'Alles wat je nu hebt zitten instellen kan je downloaden als configuratiebestand voor de volgende keer. Klik op de knop om dit te doen.', - 'csv_more_information_text' => 'Ook als het importeren fout gaat is dit bestand handig. Na het importeren krijg je nogmaals de gelegenheid dit bestand te downloaden.', - 'csv_do_download_config' => 'Download het configuratiebestand', - 'csv_empty_description' => '(geen omschrijving)', - 'csv_upload_form' => 'CSV upload formulier', - 'csv_index_unsupported_warning' => 'Het volgende wordt nog niet ondersteund:', - 'csv_unsupported_map' => 'De tool kan de kolom ":columnRole" niet koppelen aan bestaande gegevens in de database.', - 'csv_unsupported_value' => 'Firefly kan niet omgaan met kolommen gemarkeerd als ":columnRole".', - 'csv_cannot_store_value' => 'Firefly heeft geen ruimte gereserveerd voor kolommen gemarkeert als ":columnRole" en kan ze helaas niet verwerken.', - 'csv_process_title' => 'Het importeren is klaar', - 'csv_process_text' => ':rows rijen zijn verwerkt.', - 'csv_row' => 'Rij', - 'csv_import_with_errors' => 'Er ging één ding fout.|Er gingen :errors dingen fout.', - 'csv_error_see_logs' => 'De logboeken bevatten mogelijk meer details.', - 'csv_process_new_entries' => 'Firefly heeft :imported nieuwe transactie(s) gemaakt.', - 'csv_start_over' => 'Begin opnieuw', - 'csv_to_index' => 'Naar de index', - 'csv_upload_not_writeable' => 'Kan niet naar onderstaand pad schrijven. Kan dus niet uploaden.', - 'csv_column__ignore' => '(negeer deze kolom)', - 'csv_column_account-iban' => 'Betaalrekening (IBAN)', - 'csv_column_account-id' => 'Betaalrekening (ID gelijk aan Firefly)', - 'csv_column_account-name' => 'Betaalrekeningnaam', - 'csv_column_amount' => 'Bedrag', - 'csv_column_amount-comma-separated' => 'Bedrag (komma as decimaalscheidingsteken)', - 'csv_column_bill-id' => 'Contract (ID gelijk aan Firefly)', - 'csv_column_bill-name' => 'Contractnaam', - 'csv_column_budget-id' => 'Budget (ID gelijk aan Firefly)', - 'csv_column_budget-name' => 'Budgetnaam', - 'csv_column_category-id' => 'Categorie (ID gelijk aan Firefly)', - 'csv_column_category-name' => 'Categorienaam', - 'csv_column_currency-code' => 'Valutacode (ISO 4217)', - 'csv_column_currency-id' => 'Valuta (ID gelijk aan Firefly)', - 'csv_column_currency-name' => 'Valutanaam', - 'csv_column_currency-symbol' => 'Valuta', - 'csv_column_date-rent' => 'Datum (renteberekening)', - 'csv_column_date-transaction' => 'Datum (transactie)', - 'csv_column_description' => 'Omschrijving', - 'csv_column_opposing-iban' => 'Tegenrekening (IBAN)', - 'csv_column_opposing-id' => 'Tegenrekening (ID gelijk aan Firefly)', - 'csv_column_opposing-name' => 'Tegenrekeningnaam', - 'csv_column_rabo-debet-credit' => 'Rabobankspecifiek bij/af indicator', - 'csv_column_sepa-ct-id' => 'SEPA transactienummer', - 'csv_column_sepa-ct-op' => 'SEPA tegenrekeningnummer', - 'csv_column_sepa-db' => 'SEPA "direct debet"-nummer', - 'csv_column_tags-comma' => 'Tags (kommagescheiden)', - 'csv_column_tags-space' => 'Tags (spatiegescheiden)', - 'csv_specifix_RabobankDescription' => 'Vink dit aan als je Rabobank CSV-bestanden importeert.', - 'csv_specifix_AbnAmroDescription' => 'Vink dit aan als je ABN AMRO CSV-bestanden importeert.', - 'csv_specifix_Dummy' => 'Dit vinkje doet niks (dummy).', - 'csv_import_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw rekening(en), geef dan hier aan om welke rekening het gaat.', - 'csv_delimiter_help' => 'Kies het veldscheidingsteken dat in het invoerbestand is gebruikt. Bij twijfel is de komma de veiligste optie.', - 'csv_date_parse_error' => 'Firefly kan van ":value" geen datum maken, gegeven het formaat ":format". Weet je zeker dat je CSV goed is?', + 'csv_import' => 'Importeer CSV-bestand', + 'csv' => 'CSV', + 'csv_index_title' => 'Upload en importeer een kommagescheiden tekstbestand', + 'csv_define_column_roles' => 'Bepaal kolominhoud', + 'csv_map_values' => 'Leg relaties met kolomwaardes', + 'csv_download_config' => 'Download CSV configuratiebestand.', + 'csv_index_text' => 'Met deze (en de komende) pagina\'s kan je kommagescheiden tekstbestanden importeren. Deze tool is gebaseerd op de prachtige tool van Atlassian. Om te beginnen selecteer je jouw tekstbestand bij "CSV-bestand". Als je hulp nodig hebt, klik dan op het -icoontje rechtsboven.', + 'csv_index_beta_warning' => 'Deze tool is nog erg experimenteel. Wees dus voorzichtig.', + 'csv_header_help' => 'Zet hier een vinkje als de eerste rij van het CSV-bestand de namen van de kolommen bevat', + 'csv_date_help' => 'Het gebruikte datumformaat in jouw bestand. Gebruik het formaat zoals deze pagina het uitlegt (Engels). Het standaardformaat kan omgaan met data zoals deze: :dateExample.', + 'csv_csv_file_help' => 'Voer hier je kommagescheiden tekstbestand in. Je kan er maar één tegelijkertijd invoeren.', + 'csv_csv_config_file_help' => 'Voer hier je configuratiebestand in. Als je deze niet hebt, geen zorgen. Latere stappen leggen dit uit.', + 'csv_upload_button' => 'Begin de import', + 'csv_column_roles_title' => 'Bepaal de inhoud van elke kolom', + 'csv_column_roles_text' => 'Firefly kan niet automatisch ontdekken wat elke kolom betekent. Je moet het zelf aangeven. Gebruik de voorbeeldgegevens als je het ook niet zeker weet. Klik op het vraagteken-icoontje (rechtsboven) om te ontdekken wat elke kolomsoort precies is. Als de kolominhoud een directe relatie heeft met gegevens die al in Firefly staan, gebruik dan het vinkje. Tijdens de volgende stap komt Firefly hier dan op terug.', + 'csv_column_roles_table' => 'Kolominhoud', + 'csv_column' => 'CSV-kolom', + 'csv_column_name' => 'CSV-kolomnaam', + 'csv_column_example' => 'Voorbeeldgegevens', + 'csv_column_role' => 'Kolom bevat?', + 'csv_do_map_value' => 'Directe relatie?', + 'csv_continue' => 'Naar de volgende stap', + 'csv_go_back' => 'Terug naar de vorige stap', + 'csv_map_title' => 'Leg relaties met kolomwaardes', + 'csv_map_text' => 'Sommige kolommen bevatten waardes die misschien al in Firefly bestaan. Selecteer hier de juiste combinaties zodat het importeren netjes aansluit bij je huidige gegevens.', + 'csv_field_value' => 'Veldwaarde', + 'csv_field_mapped_to' => 'Is gelijk aan', + 'csv_do_not_map' => 'Geen relatie', + 'csv_download_config_title' => 'Download importconfiguratie', + 'csv_download_config_text' => 'Alles wat je nu hebt zitten instellen kan je downloaden als configuratiebestand voor de volgende keer. Klik op de knop om dit te doen.', + 'csv_more_information_text' => 'Ook als het importeren fout gaat is dit bestand handig. Na het importeren krijg je nogmaals de gelegenheid dit bestand te downloaden.', + 'csv_do_download_config' => 'Download het configuratiebestand', + 'csv_empty_description' => '(geen omschrijving)', + 'csv_upload_form' => 'CSV upload formulier', + 'csv_index_unsupported_warning' => 'Het volgende wordt nog niet ondersteund:', + 'csv_unsupported_map' => 'De tool kan de kolom ":columnRole" niet koppelen aan bestaande gegevens in de database.', + 'csv_unsupported_value' => 'Firefly kan niet omgaan met kolommen gemarkeerd als ":columnRole".', + 'csv_cannot_store_value' => 'Firefly heeft geen ruimte gereserveerd voor kolommen gemarkeert als ":columnRole" en kan ze helaas niet verwerken.', + 'csv_process_title' => 'Het importeren is klaar', + 'csv_process_text' => ':rows rijen zijn verwerkt.', + 'csv_row' => 'Rij', + 'csv_import_with_errors' => 'Er ging één ding fout.|Er gingen :errors dingen fout.', + 'csv_error_see_logs' => 'De logboeken bevatten mogelijk meer details.', + 'csv_process_new_entries' => 'Firefly heeft :imported nieuwe transactie(s) gemaakt.', + 'csv_start_over' => 'Begin opnieuw', + 'csv_to_index' => 'Naar de index', + 'csv_upload_not_writeable' => 'Kan niet naar onderstaand pad schrijven. Kan dus niet uploaden.', + 'csv_column__ignore' => '(negeer deze kolom)', + 'csv_column_account-iban' => 'Betaalrekening (IBAN)', + 'csv_column_account-id' => 'Betaalrekening (ID gelijk aan Firefly)', + 'csv_column_account-name' => 'Betaalrekeningnaam', + 'csv_column_amount' => 'Bedrag', + 'csv_column_amount-comma-separated' => 'Bedrag (komma as decimaalscheidingsteken)', + 'csv_column_bill-id' => 'Contract (ID gelijk aan Firefly)', + 'csv_column_bill-name' => 'Contractnaam', + 'csv_column_budget-id' => 'Budget (ID gelijk aan Firefly)', + 'csv_column_budget-name' => 'Budgetnaam', + 'csv_column_category-id' => 'Categorie (ID gelijk aan Firefly)', + 'csv_column_category-name' => 'Categorienaam', + 'csv_column_currency-code' => 'Valutacode (ISO 4217)', + 'csv_column_currency-id' => 'Valuta (ID gelijk aan Firefly)', + 'csv_column_currency-name' => 'Valutanaam', + 'csv_column_currency-symbol' => 'Valuta', + 'csv_column_date-rent' => 'Datum (renteberekening)', + 'csv_column_date-transaction' => 'Datum (transactie)', + 'csv_column_description' => 'Omschrijving', + 'csv_column_opposing-iban' => 'Tegenrekening (IBAN)', + 'csv_column_opposing-id' => 'Tegenrekening (ID gelijk aan Firefly)', + 'csv_column_opposing-name' => 'Tegenrekeningnaam', + 'csv_column_rabo-debet-credit' => 'Rabobankspecifiek bij/af indicator', + 'csv_column_ing-debet-credit' => 'ING-specifieke bij/af indicator', + 'csv_column_sepa-ct-id' => 'SEPA transactienummer', + 'csv_column_sepa-ct-op' => 'SEPA tegenrekeningnummer', + 'csv_column_sepa-db' => 'SEPA "direct debet"-nummer', + 'csv_column_tags-comma' => 'Tags (kommagescheiden)', + 'csv_column_tags-space' => 'Tags (spatiegescheiden)', + 'csv_column_account-number' => 'Betaalrekening (rekeningnummer)', + 'csv_column_opposing-number' => 'Tegenrekening (rekeningnummer)', + 'csv_specifix_RabobankDescription' => 'Vink dit aan als je Rabobank CSV-bestanden importeert.', + 'csv_specifix_AbnAmroDescription' => 'Vink dit aan als je ABN AMRO CSV-bestanden importeert.', + 'csv_specifix_Dummy' => 'Dit vinkje doet niks (dummy).', + 'csv_import_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw rekening(en), geef dan hier aan om welke rekening het gaat.', + 'csv_delimiter_help' => 'Kies het veldscheidingsteken dat in het invoerbestand is gebruikt. Bij twijfel is de komma de veiligste optie.', + 'csv_date_parse_error' => 'Firefly kan van ":value" geen datum maken, gegeven het formaat ":format". Weet je zeker dat je CSV goed is?', // create new stuff: - 'create_new_withdrawal' => 'Nieuwe uitgave', - 'create_new_deposit' => 'Nieuwe inkomsten', - 'create_new_transfer' => 'Nieuwe overschrijving', - 'create_new_asset' => 'Nieuwe betaalrekening', - 'create_new_expense' => 'Nieuwe crediteur', - 'create_new_revenue' => 'Nieuwe debiteur', - 'create_new_piggy_bank' => 'Nieuw spaarpotje', - 'create_new_bill' => 'Nieuw contract', + 'create_new_withdrawal' => 'Nieuwe uitgave', + 'create_new_deposit' => 'Nieuwe inkomsten', + 'create_new_transfer' => 'Nieuwe overschrijving', + 'create_new_asset' => 'Nieuwe betaalrekening', + 'create_new_expense' => 'Nieuwe crediteur', + 'create_new_revenue' => 'Nieuwe debiteur', + 'create_new_piggy_bank' => 'Nieuw spaarpotje', + 'create_new_bill' => 'Nieuw contract', // currencies: - 'create_currency' => 'Voeg nieuwe valuta toe', - 'edit_currency' => 'Wijzig valuta ":name"', - 'store_currency' => 'Sla nieuwe valuta op', - 'update_currency' => 'Wijzig valuta', - 'new_default_currency' => ':name is nu de standaard valuta.', - 'cannot_delete_currency' => 'Kan :name niet verwijderen omdat er nog transacties van zijn!', - 'deleted_currency' => 'Valuta :name verwijderd', - 'created_currency' => 'Nieuwe valuta :name opgeslagen', - 'updated_currency' => 'Valuta :name bijgewerkt', - 'ask_site_owner' => 'Vraag :owner of deze valuta wilt toevoegen, verwijderen of wijzigen.', - 'currencies_intro' => 'Firefly III ondersteunt diverse valuta die je hier kan instellen en bewerken.', - 'make_default_currency' => 'maak standaard', - 'default_currency' => 'standaard', + 'create_currency' => 'Voeg nieuwe valuta toe', + 'edit_currency' => 'Wijzig valuta ":name"', + 'store_currency' => 'Sla nieuwe valuta op', + 'update_currency' => 'Wijzig valuta', + 'new_default_currency' => ':name is nu de standaard valuta.', + 'cannot_delete_currency' => 'Kan :name niet verwijderen omdat er nog transacties van zijn!', + 'deleted_currency' => 'Valuta :name verwijderd', + 'created_currency' => 'Nieuwe valuta :name opgeslagen', + 'updated_currency' => 'Valuta :name bijgewerkt', + 'ask_site_owner' => 'Vraag :owner of deze valuta wilt toevoegen, verwijderen of wijzigen.', + 'currencies_intro' => 'Firefly III ondersteunt diverse valuta die je hier kan instellen en bewerken.', + 'make_default_currency' => 'maak standaard', + 'default_currency' => 'standaard', // new user: - 'submit' => 'Invoeren', - 'getting_started' => 'Aan de start!', - 'to_get_started' => 'Begin met de naam van de bank waar je je betaalrekening hebt, en het saldo van die rekening.', - 'savings_balance_text' => 'Voer ook het saldo van je spaarrekening in, als je die hebt.', - 'cc_balance_text' => 'Als je een credit card hebt, vul dan hier je credit cardlimiet in.', + 'submit' => 'Invoeren', + 'getting_started' => 'Aan de start!', + 'to_get_started' => 'Begin met de naam van de bank waar je je betaalrekening hebt, en het saldo van die rekening.', + 'savings_balance_text' => 'Voer ook het saldo van je spaarrekening in, als je die hebt.', + 'cc_balance_text' => 'Als je een credit card hebt, vul dan hier je credit cardlimiet in.', // forms: - 'mandatoryFields' => 'Verplichte velden', - 'optionalFields' => 'Optionele velden', - 'options' => 'Opties', - 'something' => 'Iets!', + 'mandatoryFields' => 'Verplichte velden', + 'optionalFields' => 'Optionele velden', + 'options' => 'Opties', + 'something' => 'Iets!', // budgets: - 'create_new_budget' => 'Maak een nieuw budget', - 'store_new_budget' => 'Sla nieuw budget op', - 'availableIn' => 'Beschikbaar in :date', - 'transactionsWithoutBudget' => 'Uitgaven zonder budget', - 'transactionsWithoutBudgetDate' => 'Uitgaven zonder budget in :date', - 'createBudget' => 'Maak nieuw budget', - 'inactiveBudgets' => 'Inactieve budgetten', - 'without_budget_between' => 'Transacties zonder budget tussen :start en :end', - 'budget_in_month' => ':name in :month', - 'delete_budget' => 'Verwijder budget ":name"', - 'edit_budget' => 'Wijzig budget ":name"', - 'update_amount' => 'Bedrag bijwerken', - 'update_budget' => 'Budget bijwerken', + 'create_new_budget' => 'Maak een nieuw budget', + 'store_new_budget' => 'Sla nieuw budget op', + 'availableIn' => 'Beschikbaar in :date', + 'transactionsWithoutBudget' => 'Uitgaven zonder budget', + 'transactionsWithoutBudgetDate' => 'Uitgaven zonder budget in :date', + 'createBudget' => 'Maak nieuw budget', + 'inactiveBudgets' => 'Inactieve budgetten', + 'without_budget_between' => 'Transacties zonder budget tussen :start en :end', + 'budget_in_month' => ':name in :month', + 'delete_budget' => 'Verwijder budget ":name"', + 'edit_budget' => 'Wijzig budget ":name"', + 'update_amount' => 'Bedrag bijwerken', + 'update_budget' => 'Budget bijwerken', // bills: - 'delete_bill' => 'Verwijder contract ":name"', - 'edit_bill' => 'Wijzig contract ":name"', - 'update_bill' => 'Wijzig contract', - 'store_new_bill' => 'Sla nieuw contract op', + 'delete_bill' => 'Verwijder contract ":name"', + 'edit_bill' => 'Wijzig contract ":name"', + 'update_bill' => 'Wijzig contract', + 'store_new_bill' => 'Sla nieuw contract op', // accounts: - 'details_for_asset' => 'Overzicht voor betaalrekening ":name"', - 'details_for_expense' => 'Overzicht voor crediteur ":name"', - 'details_for_revenue' => 'Overzicht voor debiteur ":name"', - 'details_for_cash' => 'Overzicht voor contant geldrekening ":name"', - 'store_new_asset_account' => 'Sla nieuwe betaalrekening op', - 'store_new_expense_account' => 'Sla nieuwe crediteur op', - 'store_new_revenue_account' => 'Sla nieuwe debiteur op', - 'edit_asset_account' => 'Wijzig betaalrekening ":name"', - 'edit_expense_account' => 'Wijzig crediteur ":name"', - 'edit_revenue_account' => 'Wijzig debiteur ":name"', - 'delete_asset_account' => 'Verwijder betaalrekening ":name"', - 'delete_expense_account' => 'Verwijder crediteur ":name"', - 'delete_revenue_account' => 'Verwijder debiteur ":name"', - 'asset_deleted' => 'Betaalrekening ":name" is verwijderd.', - 'expense_deleted' => 'Crediteur ":name" is verwijderd.', - 'revenue_deleted' => 'Debiteur ":name" is verwijderd.', - 'update_asset_account' => 'Wijzig betaalrekening', - 'update_expense_account' => 'Wijzig crediteur', - 'update_revenue_account' => 'Wijzig debiteur', - 'make_new_asset_account' => 'Nieuwe betaalrekening', - 'make_new_expense_account' => 'Nieuwe crediteur', - 'make_new_revenue_account' => 'Nieuwe debiteur', - 'asset_accounts' => 'Betaalrekeningen', - 'expense_accounts' => 'Crediteuren', - 'revenue_accounts' => 'Debiteuren', - 'accountExtraHelp_asset' => '', - 'accountExtraHelp_expense' => '', - 'accountExtraHelp_revenue' => '', - 'account_type' => 'Rekeningtype', - 'save_transactions_by_moving' => 'Bewaar deze transacties door ze aan een andere rekening te koppelen:', + 'details_for_asset' => 'Overzicht voor betaalrekening ":name"', + 'details_for_expense' => 'Overzicht voor crediteur ":name"', + 'details_for_revenue' => 'Overzicht voor debiteur ":name"', + 'details_for_cash' => 'Overzicht voor contant geldrekening ":name"', + 'store_new_asset_account' => 'Sla nieuwe betaalrekening op', + 'store_new_expense_account' => 'Sla nieuwe crediteur op', + 'store_new_revenue_account' => 'Sla nieuwe debiteur op', + 'edit_asset_account' => 'Wijzig betaalrekening ":name"', + 'edit_expense_account' => 'Wijzig crediteur ":name"', + 'edit_revenue_account' => 'Wijzig debiteur ":name"', + 'delete_asset_account' => 'Verwijder betaalrekening ":name"', + 'delete_expense_account' => 'Verwijder crediteur ":name"', + 'delete_revenue_account' => 'Verwijder debiteur ":name"', + 'asset_deleted' => 'Betaalrekening ":name" is verwijderd.', + 'expense_deleted' => 'Crediteur ":name" is verwijderd.', + 'revenue_deleted' => 'Debiteur ":name" is verwijderd.', + 'update_asset_account' => 'Wijzig betaalrekening', + 'update_expense_account' => 'Wijzig crediteur', + 'update_revenue_account' => 'Wijzig debiteur', + 'make_new_asset_account' => 'Nieuwe betaalrekening', + 'make_new_expense_account' => 'Nieuwe crediteur', + 'make_new_revenue_account' => 'Nieuwe debiteur', + 'asset_accounts' => 'Betaalrekeningen', + 'expense_accounts' => 'Crediteuren', + 'revenue_accounts' => 'Debiteuren', + 'accountExtraHelp_asset' => '', + 'accountExtraHelp_expense' => '', + 'accountExtraHelp_revenue' => '', + 'account_type' => 'Rekeningtype', + 'save_transactions_by_moving' => 'Bewaar deze transacties door ze aan een andere rekening te koppelen:', // categories: - 'new_category' => 'Nieuwe categorie', - 'create_new_category' => 'Nieuwe categorie', - 'without_category' => 'Zonder categorie', - 'update_category' => 'Wijzig categorie', - 'categories' => 'Categorieën', - 'edit_category' => 'Wijzig categorie ":name"', - 'no_category' => '(geen categorie)', - 'category' => 'Categorie', - 'delete_category' => 'Verwijder categorie ":name"', - 'store_category' => 'Sla nieuwe categorie op', - 'without_category_between' => 'Zonder categorie tussen :start en :end', + 'new_category' => 'Nieuwe categorie', + 'create_new_category' => 'Nieuwe categorie', + 'without_category' => 'Zonder categorie', + 'update_category' => 'Wijzig categorie', + 'categories' => 'Categorieën', + 'edit_category' => 'Wijzig categorie ":name"', + 'no_category' => '(geen categorie)', + 'category' => 'Categorie', + 'delete_category' => 'Verwijder categorie ":name"', + 'store_category' => 'Sla nieuwe categorie op', + 'without_category_between' => 'Zonder categorie tussen :start en :end', // transactions: - 'update_withdrawal' => 'Wijzig uitgave', - 'update_deposit' => 'Wijzig inkomsten', - 'update_transfer' => 'Wijzig overschrijving', - 'delete_withdrawal' => 'Verwijder uitgave ":description"', - 'delete_deposit' => 'Verwijder inkomsten ":description"', - 'delete_transfer' => 'Verwijder overschrijving ":description"', + 'update_withdrawal' => 'Wijzig uitgave', + 'update_deposit' => 'Wijzig inkomsten', + 'update_transfer' => 'Wijzig overschrijving', + 'delete_withdrawal' => 'Verwijder uitgave ":description"', + 'delete_deposit' => 'Verwijder inkomsten ":description"', + 'delete_transfer' => 'Verwijder overschrijving ":description"', // new user: - 'welcome' => 'Welkom bij Firefly!', - 'createNewAsset' => 'Maak om te beginnen een nieuwe betaalrekening .' . - 'Hiermee kan je nieuwe transacties opslaan en beginnen met het beheren van je geld', - 'createNewAssetButton' => 'Maak een nieuwe betaalrekening', + 'welcome' => 'Welkom bij Firefly!', + 'createNewAsset' => 'Maak om te beginnen een nieuwe betaalrekening .' . + 'Hiermee kan je nieuwe transacties opslaan en beginnen met het beheren van je geld', + 'createNewAssetButton' => 'Maak een nieuwe betaalrekening', // home page: - 'yourAccounts' => 'Je betaalrekeningen', - 'budgetsAndSpending' => 'Budgetten en uitgaven', - 'savings' => 'Sparen', - 'markAsSavingsToContinue' => 'Om hier wat te zien stel je je betaalrekeningen in als "spaarrekening".', - 'createPiggyToContinue' => 'Maak spaarpotjes om hier iets te zien.', - 'newWithdrawal' => 'Nieuwe uitgave', - 'newDeposit' => 'Nieuwe inkomsten', - 'newTransfer' => 'Nieuwe overschrijving', - 'moneyIn' => 'Inkomsten', - 'moneyOut' => 'Uitgaven', - 'billsToPay' => 'Openstaande contracten', - 'billsPaid' => 'Betaalde contracten', - 'viewDetails' => 'Meer info', - 'divided' => 'verdeeld', - 'toDivide' => 'te verdelen', + 'yourAccounts' => 'Je betaalrekeningen', + 'budgetsAndSpending' => 'Budgetten en uitgaven', + 'savings' => 'Sparen', + 'markAsSavingsToContinue' => 'Om hier wat te zien stel je je betaalrekeningen in als "spaarrekening".', + 'createPiggyToContinue' => 'Maak spaarpotjes om hier iets te zien.', + 'newWithdrawal' => 'Nieuwe uitgave', + 'newDeposit' => 'Nieuwe inkomsten', + 'newTransfer' => 'Nieuwe overschrijving', + 'moneyIn' => 'Inkomsten', + 'moneyOut' => 'Uitgaven', + 'billsToPay' => 'Openstaande contracten', + 'billsPaid' => 'Betaalde contracten', + 'viewDetails' => 'Meer info', + 'divided' => 'verdeeld', + 'toDivide' => 'te verdelen', // menu and titles, should be recycled as often as possible: - 'toggleNavigation' => 'Navigatie aan of uit', - 'currency' => 'Valuta', - 'preferences' => 'Voorkeuren', - 'logout' => 'Uitloggen', - 'searchPlaceholder' => 'Zoeken...', - 'dashboard' => 'Dashboard', - 'currencies' => 'Valuta', - 'accounts' => 'Rekeningen', - 'Asset account' => 'Betaalrekening', - 'Default account' => 'Betaalrekening', - 'Expense account' => 'Crediteur', - 'Revenue account' => 'Debiteur', - 'Initial balance account' => 'Startbalansrekening', - 'budgets' => 'Budgetten', - 'tags' => 'Tags', - 'reports' => 'Overzichten', - 'transactions' => 'Transacties', - 'expenses' => 'Uitgaven', - 'income' => 'Inkomsten', - 'transfers' => 'Overschrijvingen', - 'moneyManagement' => 'Geldbeheer', - 'piggyBanks' => 'Spaarpotjes', - 'bills' => 'Contracten', - 'createNew' => 'Nieuw', - 'withdrawal' => 'Uitgave', - 'deposit' => 'Inkomsten', - 'account' => 'Rekening', - 'transfer' => 'Overschrijving', - 'Withdrawal' => 'Uitgave', - 'Deposit' => 'Inkomsten', - 'Transfer' => 'Overschrijving', - 'bill' => 'Contract', - 'yes' => 'Ja', - 'no' => 'Nee', - 'amount' => 'Bedrag', - 'newBalance' => 'Nieuw saldo', - 'overview' => 'Overzicht', - 'saveOnAccount' => 'Sparen op rekening', - 'unknown' => 'Onbekend', - 'daily' => 'Dagelijks', - 'weekly' => 'Wekelijks', - 'monthly' => 'Maandelijks', - 'quarterly' => 'Elk kwartaal', - 'half-year' => 'Elk half jaar', - 'yearly' => 'Jaarlijks', - 'profile' => 'Profiel', + 'toggleNavigation' => 'Navigatie aan of uit', + 'currency' => 'Valuta', + 'preferences' => 'Voorkeuren', + 'logout' => 'Uitloggen', + 'searchPlaceholder' => 'Zoeken...', + 'dashboard' => 'Dashboard', + 'currencies' => 'Valuta', + 'accounts' => 'Rekeningen', + 'Asset account' => 'Betaalrekening', + 'Default account' => 'Betaalrekening', + 'Expense account' => 'Crediteur', + 'Revenue account' => 'Debiteur', + 'Initial balance account' => 'Startbalansrekening', + 'budgets' => 'Budgetten', + 'tags' => 'Tags', + 'reports' => 'Overzichten', + 'transactions' => 'Transacties', + 'expenses' => 'Uitgaven', + 'income' => 'Inkomsten', + 'transfers' => 'Overschrijvingen', + 'moneyManagement' => 'Geldbeheer', + 'piggyBanks' => 'Spaarpotjes', + 'bills' => 'Contracten', + 'createNew' => 'Nieuw', + 'withdrawal' => 'Uitgave', + 'deposit' => 'Inkomsten', + 'account' => 'Rekening', + 'transfer' => 'Overschrijving', + 'Withdrawal' => 'Uitgave', + 'Deposit' => 'Inkomsten', + 'Transfer' => 'Overschrijving', + 'bill' => 'Contract', + 'yes' => 'Ja', + 'no' => 'Nee', + 'amount' => 'Bedrag', + 'newBalance' => 'Nieuw saldo', + 'overview' => 'Overzicht', + 'saveOnAccount' => 'Sparen op rekening', + 'unknown' => 'Onbekend', + 'daily' => 'Dagelijks', + 'weekly' => 'Wekelijks', + 'monthly' => 'Maandelijks', + 'quarterly' => 'Elk kwartaal', + 'half-year' => 'Elk half jaar', + 'yearly' => 'Jaarlijks', + 'profile' => 'Profiel', // reports: - 'report_default' => 'Standaard financieel rapport (:start tot :end)', - 'quick_link_reports' => 'Snelle links', - 'quick_link_default_report' => 'Standaard financieel rapport', - 'report_this_month_quick' => 'Deze maand, alle rekeningen', - 'report_this_year_quick' => 'Dit jaar, alle rekeningen', - 'report_this_fiscal_year_quick' => 'Huidig boekjaar, alle rekeningen', - 'report_all_time_quick' => 'Gehele periode, alle rekeningen', - 'reports_can_bookmark' => 'Je kan rapporten aan je favorieten toevoegen.', - 'incomeVsExpenses' => 'Inkomsten tegenover uitgaven', - 'accountBalances' => 'Rekeningsaldi', - 'balanceStartOfYear' => 'Saldo aan het begin van het jaar', - 'balanceEndOfYear' => 'Saldo aan het einde van het jaar', - 'balanceStartOfMonth' => 'Saldo aan het begin van de maand', - 'balanceEndOfMonth' => 'Saldo aan het einde van de maand', - 'balanceStart' => 'Saldo aan het begin van de periode', - 'balanceEnd' => 'Saldo aan het einde van de periode', - 'reportsOwnAccounts' => 'Overzichten voor je eigen betaalrekeningen', - 'reportsOwnAccountsAndShared' => 'Overzichten voor je eigen betaalrekeningen en gedeelde rekeningen', - 'splitByAccount' => 'Per betaalrekening', - 'balancedByTransfersAndTags' => 'Gecorrigeerd met overschrijvingen en tags', - 'coveredWithTags' => 'Gecorrigeerd met tags', - 'leftUnbalanced' => 'Ongecorrigeerd', - 'expectedBalance' => 'Verwacht saldo', - 'outsideOfBudgets' => 'Buiten budgetten', - 'leftInBudget' => 'Over van budget', - 'sumOfSums' => 'Alles bij elkaar', - 'noCategory' => '(zonder categorie)', - 'notCharged' => '(Nog) niet betaald', - 'inactive' => 'Niet actief', - 'difference' => 'Verschil', - 'in' => 'In', - 'out' => 'Uit', - 'topX' => 'top :number', - 'showTheRest' => 'Laat alles zien', - 'hideTheRest' => 'Laat alleen de top :number zien', - 'sum_of_year' => 'Som van jaar', - 'sum_of_years' => 'Som van jaren', - 'average_of_year' => 'Gemiddelde in jaar', - 'average_of_years' => 'Gemiddelde in jaren', - 'categories_earned_in_year' => 'Categorieën (inkomsten)', - 'categories_spent_in_year' => 'Categorieën (uitgaven)', - 'report_type' => 'Rapporttype', - 'report_type_default' => 'Standard financieel rapport', - 'report_included_accounts' => 'Accounts in rapport', - 'report_date_range' => 'Datumbereik', - 'report_include_help' => 'Overboekingen naar gedeelde rekeningen tellen als uitgave. Overboekingen van gedeelde rekeningen tellen als inkomsten.', - 'report_preset_ranges' => 'Standaardbereik', - 'shared' => 'Gedeeld', - 'fiscal_year' => 'Boekjaar', + 'report_default' => 'Standaard financieel rapport (:start tot :end)', + 'report_audit' => 'Transactiehistorie-overzicht van :start tot :end', + 'quick_link_reports' => 'Snelle links', + 'quick_link_default_report' => 'Standaard financieel rapport', + 'report_this_month_quick' => 'Deze maand, alle rekeningen', + 'report_this_year_quick' => 'Dit jaar, alle rekeningen', + 'report_this_fiscal_year_quick' => 'Huidig boekjaar, alle rekeningen', + 'report_all_time_quick' => 'Gehele periode, alle rekeningen', + 'reports_can_bookmark' => 'Je kan rapporten aan je favorieten toevoegen.', + 'incomeVsExpenses' => 'Inkomsten tegenover uitgaven', + 'accountBalances' => 'Rekeningsaldi', + 'balanceStartOfYear' => 'Saldo aan het begin van het jaar', + 'balanceEndOfYear' => 'Saldo aan het einde van het jaar', + 'balanceStartOfMonth' => 'Saldo aan het begin van de maand', + 'balanceEndOfMonth' => 'Saldo aan het einde van de maand', + 'balanceStart' => 'Saldo aan het begin van de periode', + 'balanceEnd' => 'Saldo aan het einde van de periode', + 'reportsOwnAccounts' => 'Overzichten voor je eigen betaalrekeningen', + 'reportsOwnAccountsAndShared' => 'Overzichten voor je eigen betaalrekeningen en gedeelde rekeningen', + 'splitByAccount' => 'Per betaalrekening', + 'balancedByTransfersAndTags' => 'Gecorrigeerd met overschrijvingen en tags', + 'coveredWithTags' => 'Gecorrigeerd met tags', + 'leftUnbalanced' => 'Ongecorrigeerd', + 'expectedBalance' => 'Verwacht saldo', + 'outsideOfBudgets' => 'Buiten budgetten', + 'leftInBudget' => 'Over van budget', + 'sumOfSums' => 'Alles bij elkaar', + 'noCategory' => '(zonder categorie)', + 'notCharged' => '(Nog) niet betaald', + 'inactive' => 'Niet actief', + 'difference' => 'Verschil', + 'in' => 'In', + 'out' => 'Uit', + 'topX' => 'top :number', + 'showTheRest' => 'Laat alles zien', + 'hideTheRest' => 'Laat alleen de top :number zien', + 'sum_of_year' => 'Som van jaar', + 'sum_of_years' => 'Som van jaren', + 'average_of_year' => 'Gemiddelde in jaar', + 'average_of_years' => 'Gemiddelde in jaren', + 'categories_earned_in_year' => 'Categorieën (inkomsten)', + 'categories_spent_in_year' => 'Categorieën (uitgaven)', + 'report_type' => 'Rapporttype', + 'report_type_default' => 'Standard financieel rapport', + 'report_type_audit' => 'Transactiehistorie-overzicht (audit)', + 'report_included_accounts' => 'Accounts in rapport', + 'report_date_range' => 'Datumbereik', + 'report_include_help' => 'Overboekingen naar gedeelde rekeningen tellen als uitgave. Overboekingen van gedeelde rekeningen tellen als inkomsten.', + 'report_preset_ranges' => 'Standaardbereik', + 'shared' => 'Gedeeld', + 'fiscal_year' => 'Boekjaar', // charts: - 'dayOfMonth' => 'Dag vd maand', - 'month' => 'Maand', - 'budget' => 'Budget', - 'spent' => 'Uitgegeven', - 'earned' => 'Verdiend', - 'overspent' => 'Teveel uitgegeven', - 'left' => 'Over', - 'noBudget' => '(geen budget)', - 'maxAmount' => 'Maximaal bedrag', - 'minAmount' => 'Minimaal bedrag', - 'billEntry' => 'Bedrag voor dit contract', - 'name' => 'Naam', - 'date' => 'Datum', - 'paid' => 'Betaald', - 'unpaid' => 'Niet betaald', - 'day' => 'Dag', - 'budgeted' => 'Gebudgetteerd', - 'period' => 'Periode', - 'balance' => 'Saldo', - 'summary' => 'Samenvatting', - 'sum' => 'Som', - 'average' => 'Gemiddeld', - 'balanceFor' => 'Saldo op :name', + 'dayOfMonth' => 'Dag vd maand', + 'month' => 'Maand', + 'budget' => 'Budget', + 'spent' => 'Uitgegeven', + 'earned' => 'Verdiend', + 'overspent' => 'Teveel uitgegeven', + 'left' => 'Over', + 'noBudget' => '(geen budget)', + 'maxAmount' => 'Maximaal bedrag', + 'minAmount' => 'Minimaal bedrag', + 'billEntry' => 'Bedrag voor dit contract', + 'name' => 'Naam', + 'date' => 'Datum', + 'paid' => 'Betaald', + 'unpaid' => 'Niet betaald', + 'day' => 'Dag', + 'budgeted' => 'Gebudgetteerd', + 'period' => 'Periode', + 'balance' => 'Saldo', + 'summary' => 'Samenvatting', + 'sum' => 'Som', + 'average' => 'Gemiddeld', + 'balanceFor' => 'Saldo op :name', // piggy banks: - 'piggy_bank' => 'Spaarpotje', - 'new_piggy_bank' => 'Nieuw spaarpotje', - 'store_piggy_bank' => 'Sla spaarpotje op', - 'account_status' => 'Rekeningoverzicht', - 'left_for_piggy_banks' => 'Over voor spaarpotjes', - 'sum_of_piggy_banks' => 'Som van spaarpotjes', - 'saved_so_far' => 'Gespaard', - 'left_to_save' => 'Te sparen', - 'add_money_to_piggy_title' => 'Stop geld in spaarpotje ":name"', - 'remove_money_from_piggy_title' => 'Haal geld uit spaarpotje ":name"', - 'add' => 'Toevoegen', - 'remove' => 'Verwijderen', - 'max_amount_add' => 'Hooguit toe te voegen', - 'max_amount_remove' => 'Hooguit te verwijderen', - 'update_piggy_button' => 'Wijzig spaarpotje', - 'update_piggy_title' => 'Wijzig spaarpotje ":name"', - 'details' => 'Details', - 'events' => 'Gebeurtenissen', - 'target_amount' => 'Doelbedrag', - 'start_date' => 'Startdatum', - 'target_date' => 'Doeldatum', - 'no_target_date' => 'Geen doeldatum', - 'todo' => 'te doen', - 'table' => 'Tabel', - 'piggy_bank_not_exists' => 'Dit spaarpotje bestaat niet meer.', - 'add_any_amount_to_piggy' => 'Stop geld in dit spaarpotje om het doel van :amount te halen.', - 'add_set_amount_to_piggy' => 'Stop voor :date :amount in dit spaarpotje om hem op tijd te vullen.', - 'delete_piggy_bank' => 'Verwijder spaarpotje ":name"', + 'piggy_bank' => 'Spaarpotje', + 'new_piggy_bank' => 'Nieuw spaarpotje', + 'store_piggy_bank' => 'Sla spaarpotje op', + 'account_status' => 'Rekeningoverzicht', + 'left_for_piggy_banks' => 'Over voor spaarpotjes', + 'sum_of_piggy_banks' => 'Som van spaarpotjes', + 'saved_so_far' => 'Gespaard', + 'left_to_save' => 'Te sparen', + 'add_money_to_piggy_title' => 'Stop geld in spaarpotje ":name"', + 'remove_money_from_piggy_title' => 'Haal geld uit spaarpotje ":name"', + 'add' => 'Toevoegen', + 'remove' => 'Verwijderen', + 'max_amount_add' => 'Hooguit toe te voegen', + 'max_amount_remove' => 'Hooguit te verwijderen', + 'update_piggy_button' => 'Wijzig spaarpotje', + 'update_piggy_title' => 'Wijzig spaarpotje ":name"', + 'details' => 'Details', + 'events' => 'Gebeurtenissen', + 'target_amount' => 'Doelbedrag', + 'start_date' => 'Startdatum', + 'target_date' => 'Doeldatum', + 'no_target_date' => 'Geen doeldatum', + 'todo' => 'te doen', + 'table' => 'Tabel', + 'piggy_bank_not_exists' => 'Dit spaarpotje bestaat niet meer.', + 'add_any_amount_to_piggy' => 'Stop geld in dit spaarpotje om het doel van :amount te halen.', + 'add_set_amount_to_piggy' => 'Stop voor :date :amount in dit spaarpotje om hem op tijd te vullen.', + 'delete_piggy_bank' => 'Verwijder spaarpotje ":name"', // tags - 'regular_tag' => 'Een gewone tag.', - 'balancing_act' => 'Er kunnen maar twee transacties worden getagged; een uitgaven en inkomsten. Ze balanceren elkaar.', - 'advance_payment' => 'Je kan een uitgave taggen en zoveel inkomsten om de uitgave (helemaal) te compenseren.', - 'delete_tag' => 'Verwijder tag ":tag"', - 'new_tag' => 'Maak nieuwe tag', - 'edit_tag' => 'Wijzig tag ":tag"', - 'no_year' => 'Zonder jaar', - 'no_month' => 'Zonder maand', - 'tag_title_nothing' => 'Standaard tags', - 'tag_title_balancingAct' => 'Balancerende tags', - 'tag_title_advancePayment' => 'Vooruitbetaalde tags', - 'tags_introduction' => 'Normaal gesproken zijn tags enkele woorden, gebruikt om gerelateerde zaken snel aan elkaar te plakken. dure-aanschaf, rekening, feestje. In Firefly III hebben tags meer betekenis en kan je er een datum, omschrijving en locatie aan geven. Daarmee kan je je transacties op een wat zinvollere manier aan elkaar koppelen. Je kan bijvoorbeeld een tag Kerstdiner maken en informatie over het restaurant meenemen. Zulke tags zijn enkelvoudig; je gebruikt ze maar bij één gelegenheid.', - 'tags_group' => 'Omdat tags transacties groeperen kan je er teruggaves, vergoedingen en andere geldzaken mee aanduiden, zolang de transacties elkaar "opheffen". Hoe je dit aanpakt is aan jou. De gewone manier kan natuurlijk ook.', - 'tags_start' => 'Maak hieronder een tag, of voer nieuwe tags in als je nieuwe transacties maakt.', + 'regular_tag' => 'Een gewone tag.', + 'balancing_act' => 'Er kunnen maar twee transacties worden getagged; een uitgaven en inkomsten. Ze balanceren elkaar.', + 'advance_payment' => 'Je kan een uitgave taggen en zoveel inkomsten om de uitgave (helemaal) te compenseren.', + 'delete_tag' => 'Verwijder tag ":tag"', + 'new_tag' => 'Maak nieuwe tag', + 'edit_tag' => 'Wijzig tag ":tag"', + 'no_year' => 'Zonder jaar', + 'no_month' => 'Zonder maand', + 'tag_title_nothing' => 'Standaard tags', + 'tag_title_balancingAct' => 'Balancerende tags', + 'tag_title_advancePayment' => 'Vooruitbetaalde tags', + 'tags_introduction' => 'Normaal gesproken zijn tags enkele woorden, gebruikt om gerelateerde zaken snel aan elkaar te plakken. dure-aanschaf, rekening, feestje. In Firefly III hebben tags meer betekenis en kan je er een datum, omschrijving en locatie aan geven. Daarmee kan je je transacties op een wat zinvollere manier aan elkaar koppelen. Je kan bijvoorbeeld een tag Kerstdiner maken en informatie over het restaurant meenemen. Zulke tags zijn enkelvoudig; je gebruikt ze maar bij één gelegenheid.', + 'tags_group' => 'Omdat tags transacties groeperen kan je er teruggaves, vergoedingen en andere geldzaken mee aanduiden, zolang de transacties elkaar "opheffen". Hoe je dit aanpakt is aan jou. De gewone manier kan natuurlijk ook.', + 'tags_start' => 'Maak hieronder een tag, of voer nieuwe tags in als je nieuwe transacties maakt.', ]; diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php old mode 100644 new mode 100755 index cce6489790..2bb5146290 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -1,7 +1,13 @@ 'Banknaam', 'bank_balance' => 'Saldo', 'savings_balance' => 'Saldo van spaarrekening', @@ -37,6 +43,9 @@ return [ 'revenue_account' => 'Debiteur', 'amount' => 'Bedrag', 'date' => 'Datum', + 'interest_date' => 'Rentedatum', + 'book_date' => 'Boekdatum', + 'process_date' => 'Verwerkingsdatum', 'category' => 'Categorie', 'tags' => 'Tags', 'deletePermanently' => 'Verwijderen', @@ -47,6 +56,7 @@ return [ 'symbol' => 'Symbool', 'code' => 'Code', 'iban' => 'IBAN', + 'accountNumber' => 'Rekeningnummer', 'csv' => 'CSV-bestand', 'has_headers' => 'Kolomnamen op de eerste rij?', 'date_format' => 'Datumformaat', @@ -70,40 +80,44 @@ return [ 'size' => 'Grootte', 'trigger' => 'Trigger', 'stop_processing' => 'Stop met verwerken', - - 'csv_comma' => 'Een komma (,)', - 'csv_semicolon' => 'Een puntkomma (;)', - 'csv_tab' => 'Een tab (onzichtbaar)', - - - 'delete_account' => 'Verwijder rekening ":name"', - 'delete_bill' => 'Verwijder contract ":name"', - 'delete_budget' => 'Verwijder budget ":name"', - 'delete_category' => 'Verwijder categorie ":name"', - 'delete_currency' => 'Verwijder valuta ":name"', - 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', - 'delete_attachment' => 'Verwijder bijlage ":name"', - 'delete_rule' => 'Verwijder regel ":title"', - 'delete_rule_group' => 'Verwijder regelgroep ":title"', - - 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', - 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', - 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', - 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', - 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', - 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', - 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', - 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', - 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', - 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', - 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', - - 'permDeleteWarning' => 'Dingen verwijderen uit Firefly is permanent en kan niet ongedaan gemaakt worden.', - 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', - 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', - 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', - 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', - 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', - 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', - 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', + 'start_date' => 'Start van bereik', + 'end_date' => 'Einde van bereik', + 'export_start_range' => 'Start van exportbereik', + 'export_end_range' => 'Einde van exportbereik', + 'export_format' => 'Bestandsformaat', + 'include_attachments' => 'Sla ook geüploade bijlagen op', + 'include_config' => 'Sla ook een configuratiebestand ook', + 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', + 'accounts' => 'Exporteer boekingen van deze rekeningen', + 'csv_comma' => 'Een komma (,)', + 'csv_semicolon' => 'Een puntkomma (;)', + 'csv_tab' => 'Een tab (onzichtbaar)', + 'delete_account' => 'Verwijder rekening ":name"', + 'delete_bill' => 'Verwijder contract ":name"', + 'delete_budget' => 'Verwijder budget ":name"', + 'delete_category' => 'Verwijder categorie ":name"', + 'delete_currency' => 'Verwijder valuta ":name"', + 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', + 'delete_attachment' => 'Verwijder bijlage ":name"', + 'delete_rule' => 'Verwijder regel ":title"', + 'delete_rule_group' => 'Verwijder regelgroep ":title"', + 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', + 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', + 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', + 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', + 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', + 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', + 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', + 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', + 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', + 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', + 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', + 'permDeleteWarning' => 'Dingen verwijderen uit Firefly is permanent en kan niet ongedaan gemaakt worden.', + 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', + 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', + 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', + 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', + 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', + 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', + 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', ]; diff --git a/resources/lang/nl_NL/help.php b/resources/lang/nl_NL/help.php old mode 100644 new mode 100755 index 815b05bfc9..3cf854e3e6 --- a/resources/lang/nl_NL/help.php +++ b/resources/lang/nl_NL/help.php @@ -1,25 +1,29 @@ 'Welkom bij Firefly III', - 'main-content-text' => 'Doe jezelf een lol en volg deze korte tour. Je weet dan precies hoe alles werkt.', - 'sidebar-toggle-title' => 'Sidebar om nieuwe dingen te maken', - 'sidebar-toggle-text' => 'Verstopt onder het plusje vind je de knoppen die je nodig hebt om nieuwe dingen te maken.', - 'account-menu-title' => 'Alle rekeningen', - 'account-menu-text' => 'Hier vind je al je rekeningen.', - 'budget-menu-title' => 'Budgetten', - 'budget-menu-text' => 'Gebruik deze pagina voor budgetten.', - 'report-menu-title' => 'Overzichten', - 'report-menu-text' => 'Hier vind je allerlei financiele rapportages.', - 'transaction-menu-title' => 'Transacties', - 'transaction-menu-text' => 'Hier vind je al je bijschrijvingen, afschrijvingen en overboekingen.', - 'option-menu-title' => 'Opties', - 'option-menu-text' => 'Hier vind je alle opties.', - 'main-content-end-title' => 'Einde!', - 'main-content-end-text' => 'Elke pagina heeft een vraagtekentje rechtsboven. Gebruik deze voor meer hulp. Veel plezier!', - - + 'main-content-title' => 'Welkom bij Firefly III', + 'main-content-text' => 'Doe jezelf een lol en volg deze korte tour. Je weet dan precies hoe alles werkt.', + 'sidebar-toggle-title' => 'Sidebar om nieuwe dingen te maken', + 'sidebar-toggle-text' => 'Verstopt onder het plusje vind je de knoppen die je nodig hebt om nieuwe dingen te maken.', + 'account-menu-title' => 'Alle rekeningen', + 'account-menu-text' => 'Hier vind je al je rekeningen.', + 'budget-menu-title' => 'Budgetten', + 'budget-menu-text' => 'Gebruik deze pagina voor budgetten.', + 'report-menu-title' => 'Overzichten', + 'report-menu-text' => 'Hier vind je allerlei financiele rapportages.', + 'transaction-menu-title' => 'Transacties', + 'transaction-menu-text' => 'Hier vind je al je bijschrijvingen, afschrijvingen en overboekingen.', + 'option-menu-title' => 'Opties', + 'option-menu-text' => 'Hier vind je alle opties.', + 'main-content-end-title' => 'Einde!', + 'main-content-end-text' => 'Elke pagina heeft een vraagtekentje rechtsboven. Gebruik deze voor meer hulp. Veel plezier!', 'index' => 'index', 'home' => 'home', 'accounts-index' => 'rekeningen', diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php old mode 100644 new mode 100755 index 8eb2ac36e7..4d91559b82 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -1,6 +1,11 @@ 'Naam', @@ -19,6 +24,9 @@ return [ 'description' => 'Omschrijving', 'amount' => 'Bedrag', 'date' => 'Datum', + 'interest_date' => 'Rentedatum', + 'book_date' => 'Boekdatum', + 'process_date' => 'Verwerkingsdatum', 'from' => 'Van', 'to' => 'Naar', 'budget' => 'Budget', diff --git a/resources/lang/nl_NL/pagination.php b/resources/lang/nl_NL/pagination.php old mode 100644 new mode 100755 index 9a2a9677a4..f6f0715d30 --- a/resources/lang/nl_NL/pagination.php +++ b/resources/lang/nl_NL/pagination.php @@ -1,19 +1,13 @@ '« Vorige', 'next' => 'Volgende »', - ]; diff --git a/resources/lang/nl_NL/passwords.php b/resources/lang/nl_NL/passwords.php old mode 100644 new mode 100755 index c8451e8ac0..13b2a25332 --- a/resources/lang/nl_NL/passwords.php +++ b/resources/lang/nl_NL/passwords.php @@ -8,22 +8,10 @@ */ return [ - - /* - |-------------------------------------------------------------------------- - |-------------------------------------------------------------------------- - | - | The following language lines are the default lines which match reasons - | that are given by the password broker for a password update attempt - | has failed, such as for an invalid token or invalid new password. - | - */ - - "password" => "Wachtwoorden moeten zes karakters lang zijn, en natuurlijk 2x hetzelfde invoeren.", - "user" => "Geen gebruiker met dat e-mailadres.", - "token" => "Ongeldig token! Sorry", - "sent" => "Je krijgt een mailtje met een linkje om je wachtwoord te herstellen!", - "reset" => "Je wachtwoord is hersteld!", + 'password' => 'Wachtwoorden moeten zes karakters lang zijn, en natuurlijk 2x hetzelfde invoeren.', + 'user' => 'Geen gebruiker met dat e-mailadres.', + 'token' => 'Ongeldig token! Sorry.', + 'sent' => 'Je krijgt een mailtje met een linkje om je wachtwoord te herstellen!', + 'reset' => 'Je wachtwoord is hersteld!', 'blocked' => 'Leuk geprobeerd wel.', - ]; diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php old mode 100644 new mode 100755 index b3da11cde3..37c25ed3d5 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -1,69 +1,72 @@ 'Deze waarde is niet geldig voor de geselecteerde trigger.', - 'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.', - 'invalid_domain' => 'Kan niet registereren vanaf dit domein.', - 'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.', - 'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.', - 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', - 'file_too_large' => 'Bestand ":name" is te groot.', - "accepted" => ":attribute moet geaccepteerd zijn.", - "active_url" => ":attribute is geen geldige URL.", - "after" => ":attribute moet een datum na :date zijn.", - "alpha" => ":attribute mag alleen letters bevatten.", - "alpha_dash" => ":attribute mag alleen letters, nummers, onderstreep(_) en strepen(-) bevatten.", - "alpha_num" => ":attribute mag alleen letters en nummers bevatten.", - "array" => ":attribute moet geselecteerde elementen bevatten.", - "unique_for_user" => "Er is al een entry met deze :attribute.", - "before" => ":attribute moet een datum voor :date zijn.", - 'unique_object_for_user' => 'Deze naam is al in gebruik', - 'unique_account_for_user' => 'This rekeningnaam is already in use', - "between.numeric" => ":attribute moet tussen :min en :max zijn.", - "between.file" => ":attribute moet tussen :min en :max kilobytes zijn.", - "between.string" => ":attribute moet tussen :min en :max karakters zijn.", - "between.array" => ":attribute moet tussen :min en :max items bevatten.", - "boolean" => ":attribute moet true of false zijn.", - "confirmed" => ":attribute bevestiging komt niet overeen.", - "date" => ":attribute moet een datum bevatten.", - "date_format" => ":attribute moet een geldig datum formaat bevatten.", - "different" => ":attribute en :other moeten verschillend zijn.", - "digits" => ":attribute moet bestaan uit :digits cijfers.", - "digits_between" => ":attribute moet bestaan uit minimaal :min en maximaal :max cijfers.", - "email" => ":attribute is geen geldig e-mailadres.", - "filled" => ":attribute is verplicht.", - "exists" => ":attribute bestaat niet.", - "image" => ":attribute moet een afbeelding zijn.", - "in" => ":attribute is ongeldig.", - "integer" => ":attribute moet een getal zijn.", - "ip" => ":attribute moet een geldig IP-adres zijn.", - 'json' => 'De :attribute moet een JSON tekst zijn.', - "max.numeric" => ":attribute mag niet hoger dan :max zijn.", - "max.file" => ":attribute mag niet meer dan :max kilobytes zijn.", - "max.string" => ":attribute mag niet uit meer dan :max karakters bestaan.", - "max.array" => ":attribute mag niet meer dan :max items bevatten.", - "mimes" => ":attribute moet een bestand zijn van het bestandstype :values.", - "min.numeric" => ":attribute moet minimaal :min zijn.", - "min.file" => ":attribute moet minimaal :min kilobytes zijn.", - "min.string" => ":attribute moet minimaal :min karakters zijn.", - "min.array" => ":attribute moet minimaal :min items bevatten.", - "not_in" => "Het formaat van :attribute is ongeldig.", - "numeric" => ":attribute moet een nummer zijn.", - "regex" => ":attribute formaat is ongeldig.", - "required" => ":attribute is verplicht.", - "required_if" => ":attribute is verplicht indien :other gelijk is aan :value.", - 'required_unless' => ':attribute is verplicht tenzij :other gelijk is aan :values.', - "required_with" => ":attribute is verplicht i.c.m. :values", - "required_with_all" => ":attribute is verplicht i.c.m. :values", - "required_without" => ":attribute is verplicht als :values niet ingevuld is.", - "required_without_all" => ":attribute is verplicht als :values niet ingevuld zijn.", - "same" => ":attribute en :other moeten overeenkomen.", - "size.numeric" => ":attribute moet :size zijn.", - "size.file" => ":attribute moet :size kilobyte zijn.", - "size.string" => ":attribute moet :size karakters zijn.", - "size.array" => ":attribute moet :size items bevatten.", - "unique" => ":attribute is al in gebruik.", - 'string' => 'Het :attribute moet een tekenreeks zijn.', - "url" => ":attribute is geen geldige URL.", - "timezone" => "Het :attribute moet een geldige zone zijn.", + 'iban' => 'Dit is niet een geldige IBAN.', + 'unique_account_number_for_user' => 'Het lijkt erop dat dit rekeningnummer al in gebruik is.', + 'rule_trigger_value' => 'Deze waarde is niet geldig voor de geselecteerde trigger.', + 'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.', + 'invalid_domain' => 'Kan niet registereren vanaf dit domein.', + 'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.', + 'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.', + 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', + 'file_too_large' => 'Bestand ":name" is te groot.', + 'accepted' => ':attribute moet geaccepteerd zijn.', + 'active_url' => ':attribute is geen geldige URL.', + 'after' => ':attribute moet een datum na :date zijn.', + 'alpha' => ':attribute mag alleen letters bevatten.', + 'alpha_dash' => ':attribute mag alleen letters, nummers, onderstreep(_) en strepen(-) bevatten.', + 'alpha_num' => ':attribute mag alleen letters en nummers bevatten.', + 'array' => ':attribute moet geselecteerde elementen bevatten.', + 'unique_for_user' => 'Er is al een entry met deze :attribute.', + 'before' => ':attribute moet een datum voor :date zijn.', + 'unique_object_for_user' => 'Deze naam is al in gebruik', + 'unique_account_for_user' => 'This rekeningnaam is already in use', + 'between.numeric' => ':attribute moet tussen :min en :max zijn.', + 'between.file' => ':attribute moet tussen :min en :max kilobytes zijn.', + 'between.string' => ':attribute moet tussen :min en :max karakters zijn.', + 'between.array' => ':attribute moet tussen :min en :max items bevatten.', + 'boolean' => ':attribute moet true of false zijn.', + 'confirmed' => ':attribute bevestiging komt niet overeen.', + 'date' => ':attribute moet een datum bevatten.', + 'date_format' => ':attribute moet een geldig datum formaat bevatten.', + 'different' => ':attribute en :other moeten verschillend zijn.', + 'digits' => ':attribute moet bestaan uit :digits cijfers.', + 'digits_between' => ':attribute moet bestaan uit minimaal :min en maximaal :max cijfers.', + 'email' => ':attribute is geen geldig e-mailadres.', + 'filled' => ':attribute is verplicht.', + 'exists' => ':attribute bestaat niet.', + 'image' => ':attribute moet een afbeelding zijn.', + 'in' => ':attribute is ongeldig.', + 'integer' => ':attribute moet een getal zijn.', + 'ip' => ':attribute moet een geldig IP-adres zijn.', + 'json' => 'De :attribute moet een JSON tekst zijn.', + 'max.numeric' => ':attribute mag niet hoger dan :max zijn.', + 'max.file' => ':attribute mag niet meer dan :max kilobytes zijn.', + 'max.string' => ':attribute mag niet uit meer dan :max karakters bestaan.', + 'max.array' => ':attribute mag niet meer dan :max items bevatten.', + 'mimes' => ':attribute moet een bestand zijn van het bestandstype :values.', + 'min.numeric' => ':attribute moet minimaal :min zijn.', + 'min.file' => ':attribute moet minimaal :min kilobytes zijn.', + 'min.string' => ':attribute moet minimaal :min karakters zijn.', + 'min.array' => ':attribute moet minimaal :min items bevatten.', + 'not_in' => 'Het formaat van :attribute is ongeldig.', + 'numeric' => ':attribute moet een nummer zijn.', + 'regex' => ':attribute formaat is ongeldig.', + 'required' => ':attribute is verplicht.', + 'required_if' => ':attribute is verplicht indien :other gelijk is aan :value.', + 'required_unless' => ':attribute is verplicht tenzij :other gelijk is aan :values.', + 'required_with' => ':attribute is verplicht i.c.m. :values', + 'required_with_all' => ':attribute is verplicht i.c.m. :values', + 'required_without' => ':attribute is verplicht als :values niet ingevuld is.', + 'required_without_all' => ':attribute is verplicht als :values niet ingevuld zijn.', + 'same' => ':attribute en :other moeten overeenkomen.', + 'size.numeric' => ':attribute moet :size zijn.', + 'size.file' => ':attribute moet :size kilobyte zijn.', + 'size.string' => ':attribute moet :size karakters zijn.', + 'size.array' => ':attribute moet :size items bevatten.', + 'unique' => ':attribute is al in gebruik.', + 'string' => 'Het :attribute moet een tekenreeks zijn.', + 'url' => ':attribute is geen geldige URL.', + 'timezone' => 'Het :attribute moet een geldige zone zijn.', + '2fa_code' => 'De waarde in het :attribute-veld is niet geldig.', ]; diff --git a/resources/lang/pt_BR/breadcrumbs.php b/resources/lang/pt_BR/breadcrumbs.php old mode 100644 new mode 100755 index 3693e4935d..87445014c1 --- a/resources/lang/pt_BR/breadcrumbs.php +++ b/resources/lang/pt_BR/breadcrumbs.php @@ -1,60 +1,45 @@ 'Início', - - // accounts 'cash_accounts' => 'Contas Correntes', 'edit_account' => 'Editar conta ":name"', - - // currencies 'edit_currency' => 'Editar moedas ":name"', 'delete_currency' => 'Apagar moedas ":name"', - - // piggy banks 'newPiggyBank' => 'Criar um novo cofrinho', 'edit_piggyBank' => 'Editar cofrinho ":name"', - - // top menu 'preferences' => 'Preferências', 'profile' => 'Perfil', 'changePassword' => 'Alterar sua senha', - - // bills 'bills' => 'Faturas', 'newBill' => 'Nova fatura', 'edit_bill' => 'Editar fatura ":name"', 'delete_bill' => 'Apagar fatura ":name"', - - // reports 'reports' => 'Relatórios', 'monthly_report' => 'Relatório Mensal para :date', 'monthly_report_shared' => 'Relatório mensal para :date (incluindo contas compartilhadas)', 'yearly_report' => 'Relatório Anual para :date', 'yearly_report_shared' => 'Relatório anual para :date (incluindo contas compartilhadas)', 'budget_report' => 'Relatório Orçamentário para :date', - - // search 'searchResult' => 'Pesquisa por ":query"', - - // transaction lists. 'withdrawal_list' => 'Despesas', 'deposit_list' => 'Receitas, renda e depósitos', 'transfer_list' => 'Transferências', 'transfers_list' => 'Transferências', - - // create transactions 'create_withdrawal' => 'Criar uma nova retirada', 'create_deposit' => 'Criar um novo depósito', 'create_transfer' => 'Criar nova transferência', - - // edit transactions 'edit_journal' => 'Editar transação ":description"', 'delete_journal' => 'Apagar transação ":description"', - - // tags 'tags' => 'Etiquetas', 'createTag' => 'Criar nova etiqueta', 'edit_tag' => 'Editar etiqueta ":tag"', 'delete_tag' => 'Apagar etiqueta ":tag"', - ]; diff --git a/resources/lang/pt_BR/config.php b/resources/lang/pt_BR/config.php old mode 100644 new mode 100755 index 342e06a566..51fb2620dd --- a/resources/lang/pt_BR/config.php +++ b/resources/lang/pt_BR/config.php @@ -1,8 +1,20 @@ 'pt_BR, pt_BR.utf8', - 'month' => '%B %Y', - 'month_and_day' => '%e de %B de %Y', - + 'locale' => 'pt_BR, pt_BR.utf8', + 'month' => '%B %Y', + 'month_and_day' => '%e de %B de %Y', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Week %W, %Y', + 'quarter_of_year' => '%B %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', ]; diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php old mode 100644 new mode 100755 index 22805ae830..3cf3bc65ab --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -1,111 +1,167 @@ 'This language is not yet fully translated', - 'test' => 'Você selecionou Inglês', - 'close' => 'Fechar', - 'pleaseHold' => 'Por favor espere...', - 'actions' => 'Ações', - 'edit' => 'Editar', - 'delete' => 'Apagar', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Tudo', - 'customRange' => 'Intervalo Personalizado', - 'apply' => 'Aplicar', - 'cancel' => 'Cancelar', - 'from' => 'De', - 'to' => 'Até', - 'total_sum' => 'Soma Total', - 'period_sum' => 'Soma por período', - 'showEverything' => 'Mostrar tudo', - 'never' => 'Never', - 'search_results_for' => 'Pesquisar resultados por ":query"', - 'bounced_error' => 'A mensagem enviado para :email ressaltado, não tem acesso para você.', - 'deleted_error' => 'Estas credenciais não correspondem aos nossos registros.', - 'general_blocked_error' => 'Sua conta foi desativada, você não pode entrar.', - 'removed_amount' => ':amount removido', - 'added_amount' => ':amount adicionada', - 'asset_account_role_help' => 'Quaisquer opções extras resultantes da sua escolha pode ser definido mais tarde.', - 'Opening balance' => 'Saldo inicial', - 'create_new_stuff' => 'Criar novas coisas', - 'new_withdrawal' => 'Nova retirada', - 'new_deposit' => 'Novo depósito', - 'new_transfer' => 'Nova transferência', - 'new_asset_account' => 'Nova conta de ativo', - 'new_expense_account' => 'Nova conta de despesa', - 'new_revenue_account' => 'Nova conta de receita', - 'new_budget' => 'Novo orçamento', - 'new_bill' => 'Nova fatura', + 'language_incomplete' => 'This language is not yet fully translated', + 'test' => 'Você selecionou Inglês', + 'close' => 'Fechar', + 'pleaseHold' => 'Por favor espere...', + 'actions' => 'Ações', + 'edit' => 'Editar', + 'delete' => 'Apagar', + 'welcomeBack' => 'What\'s playing?', + 'everything' => 'Tudo', + 'customRange' => 'Intervalo Personalizado', + 'apply' => 'Aplicar', + 'cancel' => 'Cancelar', + 'from' => 'De', + 'to' => 'Até', + 'total_sum' => 'Soma Total', + 'period_sum' => 'Soma por período', + 'showEverything' => 'Mostrar tudo', + 'never' => 'Never', + 'search_results_for' => 'Pesquisar resultados por ":query"', + 'bounced_error' => 'A mensagem enviado para :email ressaltado, não tem acesso para você.', + 'deleted_error' => 'Estas credenciais não correspondem aos nossos registros.', + 'general_blocked_error' => 'Sua conta foi desativada, você não pode entrar.', + 'removed_amount' => ':amount removido', + 'added_amount' => ':amount adicionada', + 'asset_account_role_help' => 'Quaisquer opções extras resultantes da sua escolha pode ser definido mais tarde.', + 'Opening balance' => 'Saldo inicial', + 'create_new_stuff' => 'Criar novas coisas', + 'new_withdrawal' => 'Nova retirada', + 'new_deposit' => 'Novo depósito', + 'new_transfer' => 'Nova transferência', + 'new_asset_account' => 'Nova conta de ativo', + 'new_expense_account' => 'Nova conta de despesa', + 'new_revenue_account' => 'Nova conta de receita', + 'new_budget' => 'Novo orçamento', + 'new_bill' => 'Nova fatura', + 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', + 'flash_success' => 'Success!', + 'flash_info' => 'Message', + 'flash_warning' => 'Warning!', + 'flash_error' => 'Error!', + 'flash_info_multiple' => 'There is one message|There are :count messages', + 'flash_error_multiple' => 'There is one error|There are :count errors', + 'net_worth' => 'Net worth', + + // export data: + 'import_and_export' => 'Import and export', + 'export_data' => 'Export data', + 'export_data_intro' => 'For backup purposes, when migrating to another system or when migrating to another Firefly III installation.', + 'export_format' => 'Export format', + 'export_format_csv' => 'Comma separated values (CSV file)', + 'export_format_mt940' => 'MT940 compatible format', + 'export_included_accounts' => 'Export transactions from these accounts', + 'include_config_help' => 'For easy re-import into Firefly III', + 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', + 'do_export' => 'Export', + 'export_status_never_started' => 'The export has not started yet', + 'export_status_make_exporter' => 'Creating exporter thing...', + 'export_status_collecting_journals' => 'Collecting your transactions...', + 'export_status_collected_journals' => 'Collected your transactions!', + 'export_status_converting_to_export_format' => 'Converting your transactions...', + 'export_status_converted_to_export_format' => 'Converted your transactions!', + 'export_status_creating_journal_file' => 'Creating the export file...', + 'export_status_created_journal_file' => 'Created the export file!', + 'export_status_collecting_attachments' => 'Collecting all your attachments...', + 'export_status_collected_attachments' => 'Collected all your attachments!', + 'export_status_collecting_old_uploads' => 'Collecting all your previous uploads...', + 'export_status_collected_old_uploads' => 'Collected all your previous uploads!', + 'export_status_creating_config_file' => 'Creating a configuration file...', + 'export_status_created_config_file' => 'Created a configuration file!', + 'export_status_creating_zip_file' => 'Creating a zip file...', + 'export_status_created_zip_file' => 'Created a zip file!', + 'export_status_finished' => 'Export has succesfully finished! Yay!', + 'export_data_please_wait' => 'Please wait...', + 'attachment_explanation' => 'The file called \':attachment_name\' (#:attachment_id) was originally uploaded to :type \':description\' (#:journal_id) dated :date for the amount of :amount.', // rules - 'rules' => 'Rules', - 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', - 'rule_name' => 'Name of rule', - 'rule_triggers' => 'Rule triggers when', - 'rule_actions' => 'Rule will', - 'new_rule' => 'New rule', - 'new_rule_group' => 'New rule group', - 'rule_priority_up' => 'Give rule more priority', - 'rule_priority_down' => 'Give rule less priority', - 'make_new_rule_group' => 'Make new rule group', - 'store_new_rule_group' => 'Store new rule group', - 'created_new_rule_group' => 'New rule group ":title" stored!', - 'updated_rule_group' => 'Successfully updated rule group ":title".', - 'edit_rule_group' => 'Edit rule group ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - 'deleted_rule_group' => 'Deleted rule group ":title"', - 'update_rule_group' => 'Update rule group', - 'no_rules_in_group' => 'There are no rules in this group', - 'move_rule_group_up' => 'Move rule group up', - 'move_rule_group_down' => 'Move rule group down', - 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', - 'make_new_rule' => 'Make new rule in rule group ":title"', - 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', - 'rule_help_active' => 'Inactive rules will never fire.', - 'stored_new_rule' => 'Stored new rule with title ":title"', - 'deleted_rule' => 'Deleted rule with title ":title"', - 'store_new_rule' => 'Store new rule', - 'updated_rule' => 'Updated rule with title ":title"', - 'default_rule_group_name' => 'Default rules', - 'default_rule_group_description' => 'All your rules not in a particular group.', - 'default_rule_name' => 'Your first default rule', - 'default_rule_description' => 'This rule is an example. You can safely delete it.', - 'default_rule_trigger_description' => 'The Man Who Sold the World', - 'default_rule_trigger_from_account' => 'David Bowie', - 'default_rule_action_prepend' => 'Bought the world from ', - 'default_rule_action_set_category' => 'Large expenses', - - 'trigger' => 'Trigger', - 'trigger_value' => 'Trigger on value', - 'stop_processing_other_triggers' => 'Stop processing other triggers', - 'add_rule_trigger' => 'Add new trigger', - 'action' => 'Action', - 'action_value' => 'Action value', - 'stop_executing_other_actions' => 'Stop executing other actions', - 'add_rule_action' => 'Add new action', - 'edit_rule' => 'Edit rule ":title"', - 'update_rule' => 'Update rule', + 'rules' => 'Rules', + 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', + 'rule_name' => 'Name of rule', + 'rule_triggers' => 'Rule triggers when', + 'rule_actions' => 'Rule will', + 'new_rule' => 'New rule', + 'new_rule_group' => 'New rule group', + 'rule_priority_up' => 'Give rule more priority', + 'rule_priority_down' => 'Give rule less priority', + 'make_new_rule_group' => 'Make new rule group', + 'store_new_rule_group' => 'Store new rule group', + 'created_new_rule_group' => 'New rule group ":title" stored!', + 'updated_rule_group' => 'Successfully updated rule group ":title".', + 'edit_rule_group' => 'Edit rule group ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'deleted_rule_group' => 'Deleted rule group ":title"', + 'update_rule_group' => 'Update rule group', + 'no_rules_in_group' => 'There are no rules in this group', + 'move_rule_group_up' => 'Move rule group up', + 'move_rule_group_down' => 'Move rule group down', + 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', + 'make_new_rule' => 'Make new rule in rule group ":title"', + 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', + 'rule_help_active' => 'Inactive rules will never fire.', + 'stored_new_rule' => 'Stored new rule with title ":title"', + 'deleted_rule' => 'Deleted rule with title ":title"', + 'store_new_rule' => 'Store new rule', + 'updated_rule' => 'Updated rule with title ":title"', + 'default_rule_group_name' => 'Default rules', + 'default_rule_group_description' => 'All your rules not in a particular group.', + 'default_rule_name' => 'Your first default rule', + 'default_rule_description' => 'This rule is an example. You can safely delete it.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Large expenses', + 'trigger' => 'Trigger', + 'trigger_value' => 'Trigger on value', + 'stop_processing_other_triggers' => 'Stop processing other triggers', + 'add_rule_trigger' => 'Add new trigger', + 'action' => 'Action', + 'action_value' => 'Action value', + 'stop_executing_other_actions' => 'Stop executing other actions', + 'add_rule_action' => 'Add new action', + 'edit_rule' => 'Edit rule ":title"', + 'delete_rule' => 'Delete rule ":title"', + 'update_rule' => 'Update rule', + 'test_rule_triggers' => 'See matching transactions', + 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions', + 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', + 'warning_no_valid_triggers' => 'No valid triggers provided.', + 'execute_on_existing_transactions' => 'Execute for existing transactions', + 'execute_on_existing_transactions_intro' => 'When a rule or group has been changed or added, you can execute it for existing transactions', + 'execute_on_existing_transactions_short' => 'Existing transactions', + 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', + 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions', + 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'execute' => 'Execute', // actions and triggers - 'rule_trigger_user_action' => 'User action is ":trigger_value"', - 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', - 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', - 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', - 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', - 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', - 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', - 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', - 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', - 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', - 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', - 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', - 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', - 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', - 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', - 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', - 'rule_trigger_description_is' => 'Description is ":trigger_value"', - + 'rule_trigger_user_action' => 'User action is ":trigger_value"', + 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', + 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', + 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', + 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', + 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', + 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', + 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', + 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', + 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', + 'rule_trigger_description_is' => 'Description is ":trigger_value"', 'rule_trigger_from_account_starts_choice' => 'Source account starts with..', 'rule_trigger_from_account_ends_choice' => 'Source account ends with..', 'rule_trigger_from_account_is_choice' => 'Source account is..', @@ -122,486 +178,497 @@ return [ 'rule_trigger_description_ends_choice' => 'Description ends with..', 'rule_trigger_description_contains_choice' => 'Description contains..', 'rule_trigger_description_is_choice' => 'Description is..', - - 'rule_trigger_store_journal' => 'When a journal is created', - 'rule_trigger_update_journal' => 'When a journal is updated', - - 'rule_action_set_category' => 'Set category to ":action_value"', - 'rule_action_clear_category' => 'Clear category', - 'rule_action_set_budget' => 'Set budget to ":action_value"', - 'rule_action_clear_budget' => 'Clear budget', - 'rule_action_add_tag' => 'Add tag ":action_value"', - 'rule_action_remove_tag' => 'Remove tag ":action_value"', - 'rule_action_remove_all_tags' => 'Remove all tags', - 'rule_action_set_description' => 'Set description to ":action_value"', - 'rule_action_append_description' => 'Append description with ":action_value"', - 'rule_action_prepend_description' => 'Prepend description with ":action_value"', - - 'rule_action_set_category_choice' => 'Set category to..', - 'rule_action_clear_category_choice' => 'Clear any category', - 'rule_action_set_budget_choice' => 'Set budget to..', - 'rule_action_clear_budget_choice' => 'Clear any budget', - 'rule_action_add_tag_choice' => 'Add tag..', - 'rule_action_remove_tag_choice' => 'Remove tag..', - 'rule_action_remove_all_tags_choice' => 'Remove all tags', - 'rule_action_set_description_choice' => 'Set description to..', - 'rule_action_append_description_choice' => 'Append description with..', - 'rule_action_prepend_description_choice' => 'Prepend description with..', + 'rule_trigger_store_journal' => 'When a journal is created', + 'rule_trigger_update_journal' => 'When a journal is updated', + 'rule_action_set_category' => 'Set category to ":action_value"', + 'rule_action_clear_category' => 'Clear category', + 'rule_action_set_budget' => 'Set budget to ":action_value"', + 'rule_action_clear_budget' => 'Clear budget', + 'rule_action_add_tag' => 'Add tag ":action_value"', + 'rule_action_remove_tag' => 'Remove tag ":action_value"', + 'rule_action_remove_all_tags' => 'Remove all tags', + 'rule_action_set_description' => 'Set description to ":action_value"', + 'rule_action_append_description' => 'Append description with ":action_value"', + 'rule_action_prepend_description' => 'Prepend description with ":action_value"', + 'rule_action_set_category_choice' => 'Set category to..', + 'rule_action_clear_category_choice' => 'Clear any category', + 'rule_action_set_budget_choice' => 'Set budget to..', + 'rule_action_clear_budget_choice' => 'Clear any budget', + 'rule_action_add_tag_choice' => 'Add tag..', + 'rule_action_remove_tag_choice' => 'Remove tag..', + 'rule_action_remove_all_tags_choice' => 'Remove all tags', + 'rule_action_set_description_choice' => 'Set description to..', + 'rule_action_append_description_choice' => 'Append description with..', + 'rule_action_prepend_description_choice' => 'Prepend description with..', // tags - 'store_new_tag' => 'Armazenar nova tag', - 'update_tag' => 'Atualizar tag', - 'no_location_set' => 'Nenhuma localização.', - 'meta_data' => 'Meta dados', - 'location' => 'Localização', + 'store_new_tag' => 'Armazenar nova tag', + 'update_tag' => 'Atualizar tag', + 'no_location_set' => 'Nenhuma localização.', + 'meta_data' => 'Meta dados', + 'location' => 'Localização', // preferences - 'pref_home_screen_accounts' => 'Conta da tela inicial', - 'pref_home_screen_accounts_help' => 'Que conta deve ser exibida na tela inicial?', - 'pref_budget_settings' => 'Definições de Orçamento', - 'pref_budget_settings_help' => 'Qual a quantidade máxima de dinheiro um envelope orçamental pode conter?', - 'pref_view_range' => 'Ver intervalo', - 'pref_view_range_help' => 'Alguns gráficos são agrupados automaticamente em períodos. Qual período você prefere?', - 'pref_1D' => 'Um dia', - 'pref_1W' => 'Uma semana', - 'pref_1M' => 'Um mês', - 'pref_3M' => 'Trimestral', - 'pref_6M' => 'Semestral', - 'pref_languages' => 'Idiomas', - 'pref_languages_help' => 'Firefly III suporta muitos idiomas. Qual você prefere?', - 'pref_custom_fiscal_year' => 'Fiscal year settings', - 'pref_custom_fiscal_year_label' => 'Enabled', - 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', - 'pref_fiscal_year_start_label' => 'Fiscal year start date', - 'pref_save_settings' => 'Salvar definições', + 'pref_home_screen_accounts' => 'Conta da tela inicial', + 'pref_home_screen_accounts_help' => 'Que conta deve ser exibida na tela inicial?', + 'pref_budget_settings' => 'Definições de Orçamento', + 'pref_budget_settings_help' => 'Qual a quantidade máxima de dinheiro um envelope orçamental pode conter?', + 'pref_view_range' => 'Ver intervalo', + 'pref_view_range_help' => 'Alguns gráficos são agrupados automaticamente em períodos. Qual período você prefere?', + 'pref_1D' => 'Um dia', + 'pref_1W' => 'Uma semana', + 'pref_1M' => 'Um mês', + 'pref_3M' => 'Trimestral', + 'pref_6M' => 'Semestral', + 'pref_languages' => 'Idiomas', + 'pref_languages_help' => 'Firefly III suporta muitos idiomas. Qual você prefere?', + 'pref_custom_fiscal_year' => 'Fiscal year settings', + 'pref_custom_fiscal_year_label' => 'Enabled', + 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', + 'pref_fiscal_year_start_label' => 'Fiscal year start date', + 'pref_two_factor_auth' => '2-step verification', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', + 'pref_two_factor_auth_code' => 'Verify code', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Reset verification code', + 'pref_two_factor_auth_remove_code' => 'Remove verification code', + 'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)', + 'pref_save_settings' => 'Salvar definições', // profile: - 'change_your_password' => 'Alterar sua senha', - 'delete_account' => 'Apagar conta', - 'current_password' => 'Senha atual', - 'new_password' => 'Nova senha', - 'new_password_again' => 'Nova senha (novamente)', - 'delete_your_account' => 'Apagar sua conta', - 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', - 'delete_your_account_password' => 'Coloque sua senha para continuar.', - 'password' => 'Senha', - 'are_you_sure' => 'Você tem certeza? Você não poderá desfazer isso.', - 'delete_account_button' => 'Apagar sua conta', - 'invalid_current_password' => 'Senha atual inválida!', - 'password_changed' => 'Senha alterada!', - 'should_change' => 'A idéia é alterar sua senha.', - 'invalid_password' => 'Senha inválida!', - + 'change_your_password' => 'Alterar sua senha', + 'delete_account' => 'Apagar conta', + 'current_password' => 'Senha atual', + 'new_password' => 'Nova senha', + 'new_password_again' => 'Nova senha (novamente)', + 'delete_your_account' => 'Apagar sua conta', + 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', + 'delete_your_account_password' => 'Coloque sua senha para continuar.', + 'password' => 'Senha', + 'are_you_sure' => 'Você tem certeza? Você não poderá desfazer isso.', + 'delete_account_button' => 'Apagar sua conta', + 'invalid_current_password' => 'Senha atual inválida!', + 'password_changed' => 'Senha alterada!', + 'should_change' => 'A idéia é alterar sua senha.', + 'invalid_password' => 'Senha inválida!', // attachments - 'nr_of_attachments' => 'Um anexo|:count anexos', - 'attachments' => 'Anexos', - 'edit_attachment' => 'Editar anexo ":name"', - 'update_attachment' => 'Atualizar anexo', - 'delete_attachment' => 'Apagar anexo ":name"', - 'attachment_deleted' => 'Anexo apagado ":name"', - 'upload_max_file_size' => 'Tamanho máximo do arquivo: :size', + 'nr_of_attachments' => 'Um anexo|:count anexos', + 'attachments' => 'Anexos', + 'edit_attachment' => 'Editar anexo ":name"', + 'update_attachment' => 'Atualizar anexo', + 'delete_attachment' => 'Apagar anexo ":name"', + 'attachment_deleted' => 'Anexo apagado ":name"', + 'upload_max_file_size' => 'Tamanho máximo do arquivo: :size', // tour: - 'prev' => 'Anterior', - 'next' => 'Próximo', - 'end-tour' => 'Fim do Tour', - 'pause' => 'Parar', + 'prev' => 'Anterior', + 'next' => 'Próximo', + 'end-tour' => 'Fim do Tour', + 'pause' => 'Parar', // transaction index - 'title_expenses' => 'Despesas', - 'title_withdrawal' => 'Despesas', - 'title_revenue' => 'Receitas / Renda', - 'title_deposit' => 'Receita / Renda', - 'title_transfer' => 'Transferências', - 'title_transfers' => 'Transferências', + 'title_expenses' => 'Despesas', + 'title_withdrawal' => 'Despesas', + 'title_revenue' => 'Receitas / Renda', + 'title_deposit' => 'Receita / Renda', + 'title_transfer' => 'Transferências', + 'title_transfers' => 'Transferências', // csv import: - 'csv_import' => 'Importar arquivo CSV', - 'csv' => 'CSV', - 'csv_index_title' => 'Carregar e importar um arquivo CSV', - 'csv_define_column_roles' => 'Definir papeis da coluna', - 'csv_map_values' => 'Valores mapeados encontrados para valores existentes', - 'csv_download_config' => 'Download do arquivo CSV de configuração.', - 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', - 'csv_index_beta_warning' => 'Esta ferramenta está em beta. Por favor proceder com cautela', - 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', - 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', - 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', - 'csv_upload_button' => 'Iniciando importação do CSV', - 'csv_column_roles_title' => 'Definir papeis da coluna', - 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', - 'csv_column_roles_table' => 'Papéis da Coluna', - 'csv_column' => 'Coluna CSV', - 'csv_column_name' => 'Nome da coluna do CSV', - 'csv_column_example' => 'Exemplo de dados da coluna', - 'csv_column_role' => 'Coluna contém?', - 'csv_do_map_value' => 'Valor mapeado?', - 'csv_continue' => 'Continuar para o próximo passo', - 'csv_go_back' => 'Voltar para o passo anterior', - 'csv_map_title' => 'Valores mapeados encontrados para valores existentes', - 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', - 'csv_field_value' => 'Valor do campo do CSV', - 'csv_field_mapped_to' => 'Deve ser mapeado para...', - 'csv_do_not_map' => 'Não mapear este valor', - 'csv_download_config_title' => 'Download do CSV de configuração ', - 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', - 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', - 'csv_do_download_config' => 'Download do arquivo de configuração.', - 'csv_empty_description' => '(descrição vazia)', - 'csv_upload_form' => 'Formulário de Upload do CSV', - 'csv_index_unsupported_warning' => 'O importador de CSV está incapaz de fazer o seguinte:', - 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', - 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', - 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', - 'csv_process_title' => 'Importação do CSV terminou!', - 'csv_process_text' => 'O importador do CSV terminou e processou :rows linhas', - 'csv_row' => 'Linha', - 'csv_import_with_errors' => 'Houve um erro.|Houve :errors erros.', - 'csv_error_see_logs' => 'Verifique o arquivo de log para ver detalhes.', - 'csv_process_new_entries' => 'Firefly criou :imported nova(s) transação(ões)', - 'csv_start_over' => 'Importar novamente', - 'csv_to_index' => 'Voltar para tela inicial', - 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', - 'csv_column__ignore' => '(ignorar esta coluna)', - 'csv_column_account-iban' => 'Conta de Ativo (IBAN)', - 'csv_column_account-id' => 'ID da Conta de Ativo (correspondente Firefly)', - 'csv_column_account-name' => 'Conta de Ativo (nome)', - 'csv_column_amount' => 'Valor', - 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'csv_column_bill-id' => 'ID Fatura (correspondente Firefly)', - 'csv_column_bill-name' => 'Nom da Fatura', - 'csv_column_budget-id' => 'ID do Orçamento (correspondente Firefly)', - 'csv_column_budget-name' => 'Nome do Orçamento', - 'csv_column_category-id' => 'ID da Categoria (correspondente Firefly)', - 'csv_column_category-name' => 'Nome da Categoria', - 'csv_column_currency-code' => 'Código da Moeda (ISO 4217)', - 'csv_column_currency-id' => 'ID da Moeda (correspondente Firefly)', - 'csv_column_currency-name' => 'Nome da Moeda (correspondente Firefly)', - 'csv_column_currency-symbol' => 'Símbolo da Moeda (correspondente Firefly)', - 'csv_column_date-rent' => 'Rent calculation date', - 'csv_column_date-transaction' => 'Data', - 'csv_column_description' => 'Descrição', - 'csv_column_opposing-iban' => 'Opposing account (IBAN)', - 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'csv_column_opposing-name' => 'Opposing account (name)', - 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'csv_column_sepa-db' => 'SEPA Direct Debet', - 'csv_column_tags-comma' => 'Tags (separadas por vírgula)', - 'csv_column_tags-space' => 'Tags (separadas por espaço)', - 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', - 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', - 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', - 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', + 'csv_import' => 'Importar arquivo CSV', + 'csv' => 'CSV', + 'csv_index_title' => 'Carregar e importar um arquivo CSV', + 'csv_define_column_roles' => 'Definir papeis da coluna', + 'csv_map_values' => 'Valores mapeados encontrados para valores existentes', + 'csv_download_config' => 'Download do arquivo CSV de configuração.', + 'csv_index_text' => 'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by the folks at Atlassian. Simply upload your CSV file and follow the instructions. If you would like to learn more, please click on the button at the top of this page.', + 'csv_index_beta_warning' => 'Esta ferramenta está em beta. Por favor proceder com cautela', + 'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data', + 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time', + 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', + 'csv_upload_button' => 'Iniciando importação do CSV', + 'csv_column_roles_title' => 'Definir papeis da coluna', + 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', + 'csv_column_roles_table' => 'Papéis da Coluna', + 'csv_column' => 'Coluna CSV', + 'csv_column_name' => 'Nome da coluna do CSV', + 'csv_column_example' => 'Exemplo de dados da coluna', + 'csv_column_role' => 'Coluna contém?', + 'csv_do_map_value' => 'Valor mapeado?', + 'csv_continue' => 'Continuar para o próximo passo', + 'csv_go_back' => 'Voltar para o passo anterior', + 'csv_map_title' => 'Valores mapeados encontrados para valores existentes', + 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', + 'csv_field_value' => 'Valor do campo do CSV', + 'csv_field_mapped_to' => 'Deve ser mapeado para...', + 'csv_do_not_map' => 'Não mapear este valor', + 'csv_download_config_title' => 'Download do CSV de configuração ', + 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', + 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', + 'csv_do_download_config' => 'Download do arquivo de configuração.', + 'csv_empty_description' => '(descrição vazia)', + 'csv_upload_form' => 'Formulário de Upload do CSV', + 'csv_index_unsupported_warning' => 'O importador de CSV está incapaz de fazer o seguinte:', + 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', + 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', + 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', + 'csv_process_title' => 'Importação do CSV terminou!', + 'csv_process_text' => 'O importador do CSV terminou e processou :rows linhas', + 'csv_row' => 'Linha', + 'csv_import_with_errors' => 'Houve um erro.|Houve :errors erros.', + 'csv_error_see_logs' => 'Verifique o arquivo de log para ver detalhes.', + 'csv_process_new_entries' => 'Firefly criou :imported nova(s) transação(ões)', + 'csv_start_over' => 'Importar novamente', + 'csv_to_index' => 'Voltar para tela inicial', + 'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload', + 'csv_column__ignore' => '(ignorar esta coluna)', + 'csv_column_account-iban' => 'Conta de Ativo (IBAN)', + 'csv_column_account-id' => 'ID da Conta de Ativo (correspondente Firefly)', + 'csv_column_account-name' => 'Conta de Ativo (nome)', + 'csv_column_amount' => 'Valor', + 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'csv_column_bill-id' => 'ID Fatura (correspondente Firefly)', + 'csv_column_bill-name' => 'Nom da Fatura', + 'csv_column_budget-id' => 'ID do Orçamento (correspondente Firefly)', + 'csv_column_budget-name' => 'Nome do Orçamento', + 'csv_column_category-id' => 'ID da Categoria (correspondente Firefly)', + 'csv_column_category-name' => 'Nome da Categoria', + 'csv_column_currency-code' => 'Código da Moeda (ISO 4217)', + 'csv_column_currency-id' => 'ID da Moeda (correspondente Firefly)', + 'csv_column_currency-name' => 'Nome da Moeda (correspondente Firefly)', + 'csv_column_currency-symbol' => 'Símbolo da Moeda (correspondente Firefly)', + 'csv_column_date-rent' => 'Rent calculation date', + 'csv_column_date-transaction' => 'Data', + 'csv_column_description' => 'Descrição', + 'csv_column_opposing-iban' => 'Opposing account (IBAN)', + 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', + 'csv_column_opposing-name' => 'Opposing account (name)', + 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', + 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator', + 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', + 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', + 'csv_column_sepa-db' => 'SEPA Direct Debet', + 'csv_column_tags-comma' => 'Tags (separadas por vírgula)', + 'csv_column_tags-space' => 'Tags (separadas por espaço)', + 'csv_column_account-number' => 'Asset account (account number)', + 'csv_column_opposing-number' => 'Opposing account (account number)', + 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', + 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', + 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', + 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', // create new stuff: - 'create_new_withdrawal' => 'Criar nova retirada', - 'create_new_deposit' => 'Criar um novo depósito', - 'create_new_transfer' => 'Criar nova transferência', - 'create_new_asset' => 'Criar nova conta de ativo', - 'create_new_expense' => 'Criar nova conta de despesa', - 'create_new_revenue' => 'Criar nova conta de receita', - 'create_new_piggy_bank' => 'Criar novo cofrinho', - 'create_new_bill' => 'Criar nova fatura', + 'create_new_withdrawal' => 'Criar nova retirada', + 'create_new_deposit' => 'Criar um novo depósito', + 'create_new_transfer' => 'Criar nova transferência', + 'create_new_asset' => 'Criar nova conta de ativo', + 'create_new_expense' => 'Criar nova conta de despesa', + 'create_new_revenue' => 'Criar nova conta de receita', + 'create_new_piggy_bank' => 'Criar novo cofrinho', + 'create_new_bill' => 'Criar nova fatura', // currencies: - 'create_currency' => 'Criar uma nova moeda', - 'edit_currency' => 'Editar moeda ":name"', - 'store_currency' => 'Armazenar nova moeda', - 'update_currency' => 'Atualizar moeda', - 'new_default_currency' => ':name is now the default currency.', - 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', - 'deleted_currency' => 'Currency :name deleted', - 'created_currency' => 'Currency :name created', - 'updated_currency' => 'Currency :name updated', - 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', - 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', - 'make_default_currency' => 'make default', - 'default_currency' => 'default', + 'create_currency' => 'Criar uma nova moeda', + 'edit_currency' => 'Editar moeda ":name"', + 'store_currency' => 'Armazenar nova moeda', + 'update_currency' => 'Atualizar moeda', + 'new_default_currency' => ':name is now the default currency.', + 'cannot_delete_currency' => 'Cannot delete :name because there are still transactions attached to it!', + 'deleted_currency' => 'Currency :name deleted', + 'created_currency' => 'Currency :name created', + 'updated_currency' => 'Currency :name updated', + 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', + 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', + 'make_default_currency' => 'make default', + 'default_currency' => 'default', // new user: - 'submit' => 'Enviar', - 'getting_started' => 'Iniciar', - 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', - 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', - 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', + 'submit' => 'Enviar', + 'getting_started' => 'Iniciar', + 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', + 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', + 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', // forms: - 'mandatoryFields' => 'Campos obrigatórios', - 'optionalFields' => 'Campos opcionais', - 'options' => 'Opções', - 'something' => 'Qualquer coisa!', + 'mandatoryFields' => 'Campos obrigatórios', + 'optionalFields' => 'Campos opcionais', + 'options' => 'Opções', + 'something' => 'Qualquer coisa!', // budgets: - 'create_new_budget' => 'Criar um novo orçamento', - 'store_new_budget' => 'Armazenar novo orçamento', - 'availableIn' => 'Disponível em :date', - 'transactionsWithoutBudget' => 'Despesas sem orçamentos', - 'transactionsWithoutBudgetDate' => 'Despesas sem orçamentos em :date', - 'createBudget' => 'Novo orçamento', - 'inactiveBudgets' => 'Orçamentos inativos', - 'without_budget_between' => 'Transactions without a budget between :start and :end', - 'budget_in_month' => ':name no :month', - 'delete_budget' => 'Delete budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', + 'create_new_budget' => 'Criar um novo orçamento', + 'store_new_budget' => 'Armazenar novo orçamento', + 'availableIn' => 'Disponível em :date', + 'transactionsWithoutBudget' => 'Despesas sem orçamentos', + 'transactionsWithoutBudgetDate' => 'Despesas sem orçamentos em :date', + 'createBudget' => 'Novo orçamento', + 'inactiveBudgets' => 'Orçamentos inativos', + 'without_budget_between' => 'Transactions without a budget between :start and :end', + 'budget_in_month' => ':name no :month', + 'delete_budget' => 'Delete budget ":name"', + 'edit_budget' => 'Edit budget ":name"', + 'update_amount' => 'Update amount', + 'update_budget' => 'Update budget', // bills: - 'delete_bill' => 'Delete bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'update_bill' => 'Update bill', - 'store_new_bill' => 'Store new bill', + 'delete_bill' => 'Delete bill ":name"', + 'edit_bill' => 'Edit bill ":name"', + 'update_bill' => 'Update bill', + 'store_new_bill' => 'Store new bill', // accounts: - 'details_for_asset' => 'Details for asset account ":name"', - 'details_for_expense' => 'Details for expense account ":name"', - 'details_for_revenue' => 'Details for revenue account ":name"', - 'details_for_cash' => 'Details for cash account ":name"', - 'store_new_asset_account' => 'Store new asset account', - 'store_new_expense_account' => 'Store new expense account', - 'store_new_revenue_account' => 'Store new revenue account', - 'edit_asset_account' => 'Edit asset account ":name"', - 'edit_expense_account' => 'Edit expense account ":name"', - 'edit_revenue_account' => 'Edit revenue account ":name"', - 'delete_asset_account' => 'Delete asset account ":name"', - 'delete_expense_account' => 'Delete expense account ":name"', - 'delete_revenue_account' => 'Delete revenue account ":name"', - 'asset_deleted' => 'Successfully deleted asset account ":name"', - 'expense_deleted' => 'Successfully deleted expense account ":name"', - 'revenue_deleted' => 'Successfully deleted revenue account ":name"', - 'update_asset_account' => 'Update asset account', - 'update_expense_account' => 'Update expense account', - 'update_revenue_account' => 'Update revenue account', - 'make_new_asset_account' => 'Create a new asset account', - 'make_new_expense_account' => 'Create a new expense account', - 'make_new_revenue_account' => 'Create a new revenue account', - 'asset_accounts' => 'Asset accounts', - 'expense_accounts' => 'Expense accounts', - 'revenue_accounts' => 'Revenue accounts', - 'accountExtraHelp_asset' => '', - 'accountExtraHelp_expense' => '', - 'accountExtraHelp_revenue' => '', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', + 'details_for_asset' => 'Details for asset account ":name"', + 'details_for_expense' => 'Details for expense account ":name"', + 'details_for_revenue' => 'Details for revenue account ":name"', + 'details_for_cash' => 'Details for cash account ":name"', + 'store_new_asset_account' => 'Store new asset account', + 'store_new_expense_account' => 'Store new expense account', + 'store_new_revenue_account' => 'Store new revenue account', + 'edit_asset_account' => 'Edit asset account ":name"', + 'edit_expense_account' => 'Edit expense account ":name"', + 'edit_revenue_account' => 'Edit revenue account ":name"', + 'delete_asset_account' => 'Delete asset account ":name"', + 'delete_expense_account' => 'Delete expense account ":name"', + 'delete_revenue_account' => 'Delete revenue account ":name"', + 'asset_deleted' => 'Successfully deleted asset account ":name"', + 'expense_deleted' => 'Successfully deleted expense account ":name"', + 'revenue_deleted' => 'Successfully deleted revenue account ":name"', + 'update_asset_account' => 'Update asset account', + 'update_expense_account' => 'Update expense account', + 'update_revenue_account' => 'Update revenue account', + 'make_new_asset_account' => 'Create a new asset account', + 'make_new_expense_account' => 'Create a new expense account', + 'make_new_revenue_account' => 'Create a new revenue account', + 'asset_accounts' => 'Asset accounts', + 'expense_accounts' => 'Expense accounts', + 'revenue_accounts' => 'Revenue accounts', + 'accountExtraHelp_asset' => '', + 'accountExtraHelp_expense' => '', + 'accountExtraHelp_revenue' => '', + 'account_type' => 'Account type', + 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Wijzig categorie', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'store_category' => 'Store new category', - 'without_category_between' => 'Without category between :start and :end', + 'new_category' => 'New category', + 'create_new_category' => 'Create a new category', + 'without_category' => 'Without a category', + 'update_category' => 'Wijzig categorie', + 'categories' => 'Categories', + 'edit_category' => 'Edit category ":name"', + 'no_category' => '(no category)', + 'category' => 'Category', + 'delete_category' => 'Delete category ":name"', + 'store_category' => 'Store new category', + 'without_category_between' => 'Without category between :start and :end', // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', + 'update_withdrawal' => 'Update withdrawal', + 'update_deposit' => 'Update deposit', + 'update_transfer' => 'Update transfer', + 'delete_withdrawal' => 'Delete withdrawal ":description"', + 'delete_deposit' => 'Delete deposit ":description"', + 'delete_transfer' => 'Delete transfer ":description"', // new user: - 'welcome' => 'Welcome to Firefly!', - 'createNewAsset' => 'Create a new asset account to get started. ' . - 'This will allow you to create transactions and start your financial management', - 'createNewAssetButton' => 'Criar nova conta de ativo', + 'welcome' => 'Welcome to Firefly!', + 'createNewAsset' => 'Create a new asset account to get started. ' . + 'This will allow you to create transactions and start your financial management', + 'createNewAssetButton' => 'Criar nova conta de ativo', // home page: - 'yourAccounts' => 'Your accounts', - 'budgetsAndSpending' => 'Budgets and spending', - 'savings' => 'Savings', - 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', - 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', - 'newWithdrawal' => 'New expense', - 'newDeposit' => 'Novo depósito', - 'newTransfer' => 'Nova transferência', - 'moneyIn' => 'Money in', - 'moneyOut' => 'Money out', - 'billsToPay' => 'Bills to pay', - 'billsPaid' => 'Bills paid', - 'viewDetails' => 'View details', - 'divided' => 'divided', - 'toDivide' => 'left to divide', + 'yourAccounts' => 'Your accounts', + 'budgetsAndSpending' => 'Budgets and spending', + 'savings' => 'Savings', + 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', + 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', + 'newWithdrawal' => 'New expense', + 'newDeposit' => 'Novo depósito', + 'newTransfer' => 'Nova transferência', + 'moneyIn' => 'Money in', + 'moneyOut' => 'Money out', + 'billsToPay' => 'Bills to pay', + 'billsPaid' => 'Bills paid', + 'viewDetails' => 'View details', + 'divided' => 'divided', + 'toDivide' => 'left to divide', // menu and titles, should be recycled as often as possible: - 'toggleNavigation' => 'Toggle navigation', - 'currency' => 'Currency', - 'preferences' => 'Preferences', - 'logout' => 'Logout', - 'searchPlaceholder' => 'Search...', - 'dashboard' => 'Dashboard', - 'currencies' => 'Currencies', - 'accounts' => 'Accounts', - 'Asset account' => 'Asset account', - 'Default account' => 'Asset account', - 'Expense account' => 'Conta de Despesa', - 'Revenue account' => 'Conta de Receita', - 'Initial balance account' => 'Initial balance account', - 'budgets' => 'Budgets', - 'tags' => 'Tags', - 'reports' => 'Relatórios', - 'transactions' => 'Transações', - 'expenses' => 'Despesas', - 'income' => 'Receita / Renda', - 'transfers' => 'Transferências', - 'moneyManagement' => 'Gerenciamento de Dinheiro', - 'piggyBanks' => 'Cofrinhos', - 'bills' => 'Faturas', - 'createNew' => 'Criar nova(o)', - 'withdrawal' => 'Retirada', - 'deposit' => 'Depósito', - 'account' => 'Conta', - 'transfer' => 'Transferência', - 'Withdrawal' => 'Retirada', - 'Deposit' => 'Depósito', - 'Transfer' => 'Transferência', - 'bill' => 'Fatura', - 'yes' => 'Sim', - 'no' => 'Não', - 'amount' => 'Valor', - 'newBalance' => 'Novo saldo', - 'overview' => 'Visão Geral', - 'saveOnAccount' => 'Salvar na conta', - 'unknown' => 'Desconhecido', - 'daily' => 'Diário', - 'weekly' => 'Semanal', - 'monthly' => 'Mensal', - 'quarterly' => 'Trimestral', - 'half-year' => 'Semestral', - 'yearly' => 'Anual', - 'profile' => 'Perfil', + 'toggleNavigation' => 'Toggle navigation', + 'currency' => 'Currency', + 'preferences' => 'Preferences', + 'logout' => 'Logout', + 'searchPlaceholder' => 'Search...', + 'dashboard' => 'Dashboard', + 'currencies' => 'Currencies', + 'accounts' => 'Accounts', + 'Asset account' => 'Asset account', + 'Default account' => 'Asset account', + 'Expense account' => 'Conta de Despesa', + 'Revenue account' => 'Conta de Receita', + 'Initial balance account' => 'Initial balance account', + 'budgets' => 'Budgets', + 'tags' => 'Tags', + 'reports' => 'Relatórios', + 'transactions' => 'Transações', + 'expenses' => 'Despesas', + 'income' => 'Receita / Renda', + 'transfers' => 'Transferências', + 'moneyManagement' => 'Gerenciamento de Dinheiro', + 'piggyBanks' => 'Cofrinhos', + 'bills' => 'Faturas', + 'createNew' => 'Criar nova(o)', + 'withdrawal' => 'Retirada', + 'deposit' => 'Depósito', + 'account' => 'Conta', + 'transfer' => 'Transferência', + 'Withdrawal' => 'Retirada', + 'Deposit' => 'Depósito', + 'Transfer' => 'Transferência', + 'bill' => 'Fatura', + 'yes' => 'Sim', + 'no' => 'Não', + 'amount' => 'Valor', + 'newBalance' => 'Novo saldo', + 'overview' => 'Visão Geral', + 'saveOnAccount' => 'Salvar na conta', + 'unknown' => 'Desconhecido', + 'daily' => 'Diário', + 'weekly' => 'Semanal', + 'monthly' => 'Mensal', + 'quarterly' => 'Trimestral', + 'half-year' => 'Semestral', + 'yearly' => 'Anual', + 'profile' => 'Perfil', // reports: - 'report_default' => 'Default financial report for :start until :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'report_this_month_quick' => 'Current month, all accounts', - 'report_this_year_quick' => 'Current year, all accounts', - 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', - 'report_all_time_quick' => 'All-time, all accounts', - 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', - 'incomeVsExpenses' => 'Renda vs. Despesas', - 'accountBalances' => 'Saldos de Contas', - 'balanceStartOfYear' => 'Balance at start of year', - 'balanceEndOfYear' => 'Balance at end of year', - 'balanceStartOfMonth' => 'Balance at start of month', - 'balanceEndOfMonth' => 'Balance at end of month', - 'balanceStart' => 'Balance at start of period', - 'balanceEnd' => 'Balance at end of period', - 'reportsOwnAccounts' => 'Reports for your own accounts', - 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', - 'splitByAccount' => 'Split by account', - 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', - 'coveredWithTags' => 'Covered with tags', - 'leftUnbalanced' => 'Left unbalanced', - 'expectedBalance' => 'Saldo Experado', - 'outsideOfBudgets' => 'Fora do orçamento', - 'leftInBudget' => 'Deixou no orçamento', - 'sumOfSums' => 'Soma dos montantes', - 'noCategory' => '(no category)', - 'notCharged' => 'Não cobrado (ainda)', - 'inactive' => 'Inativo', - 'difference' => 'Diferente', - 'in' => 'Entrada', - 'out' => 'Saída', - 'topX' => 'top :number', - 'showTheRest' => 'Mostrar tudo', - 'hideTheRest' => 'Mostrar apenas os top :number', - 'sum_of_year' => 'Soma do ano', - 'sum_of_years' => 'Sum of years', - 'average_of_year' => 'Média do ano', - 'average_of_years' => 'Average of years', - 'categories_earned_in_year' => 'Categories (by earnings)', - 'categories_spent_in_year' => 'Categories (by spendings)', - 'report_type' => 'Report type', - 'report_type_default' => 'Default financial report', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', + 'report_default' => 'Default financial report for :start until :end', + 'report_audit' => 'Transaction history overview for :start until :end', + 'quick_link_reports' => 'Quick links', + 'quick_link_default_report' => 'Default financial report', + 'report_this_month_quick' => 'Current month, all accounts', + 'report_this_year_quick' => 'Current year, all accounts', + 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', + 'report_all_time_quick' => 'All-time, all accounts', + 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', + 'incomeVsExpenses' => 'Renda vs. Despesas', + 'accountBalances' => 'Saldos de Contas', + 'balanceStartOfYear' => 'Balance at start of year', + 'balanceEndOfYear' => 'Balance at end of year', + 'balanceStartOfMonth' => 'Balance at start of month', + 'balanceEndOfMonth' => 'Balance at end of month', + 'balanceStart' => 'Balance at start of period', + 'balanceEnd' => 'Balance at end of period', + 'reportsOwnAccounts' => 'Reports for your own accounts', + 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', + 'splitByAccount' => 'Split by account', + 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', + 'coveredWithTags' => 'Covered with tags', + 'leftUnbalanced' => 'Left unbalanced', + 'expectedBalance' => 'Saldo Experado', + 'outsideOfBudgets' => 'Fora do orçamento', + 'leftInBudget' => 'Deixou no orçamento', + 'sumOfSums' => 'Soma dos montantes', + 'noCategory' => '(no category)', + 'notCharged' => 'Não cobrado (ainda)', + 'inactive' => 'Inativo', + 'difference' => 'Diferente', + 'in' => 'Entrada', + 'out' => 'Saída', + 'topX' => 'top :number', + 'showTheRest' => 'Mostrar tudo', + 'hideTheRest' => 'Mostrar apenas os top :number', + 'sum_of_year' => 'Soma do ano', + 'sum_of_years' => 'Sum of years', + 'average_of_year' => 'Média do ano', + 'average_of_years' => 'Average of years', + 'categories_earned_in_year' => 'Categories (by earnings)', + 'categories_spent_in_year' => 'Categories (by spendings)', + 'report_type' => 'Report type', + 'report_type_default' => 'Default financial report', + 'report_type_audit' => 'Transaction history overview (audit)', + 'report_included_accounts' => 'Included accounts', + 'report_date_range' => 'Date range', + 'report_include_help' => 'In all cases, transfers to shared accounts count as expenses, and transfers from shared accounts count as income.', + 'report_preset_ranges' => 'Pre-set ranges', + 'shared' => 'Shared', + 'fiscal_year' => 'Fiscal year', // charts: - 'dayOfMonth' => 'Dia do mês', - 'month' => 'Mês', - 'budget' => 'Orçamento', - 'spent' => 'Gasto', - 'earned' => 'Ganho', - 'overspent' => 'Gasto excedido', - 'left' => 'Left', - 'noBudget' => '(sem orçamento)', - 'maxAmount' => 'Valor Máximo', - 'minAmount' => 'Valor Mínimo', - 'billEntry' => 'Current bill entry', - 'name' => 'Nome', - 'date' => 'Data', - 'paid' => 'Pago', - 'unpaid' => 'Não pago', - 'day' => 'Dia', - 'budgeted' => 'Orçado', - 'period' => 'Período', - 'balance' => 'Saldo', - 'summary' => 'Sumário', - 'sum' => 'Soma', - 'average' => 'Média', - 'balanceFor' => 'Saldo para ":name"', + 'dayOfMonth' => 'Dia do mês', + 'month' => 'Mês', + 'budget' => 'Orçamento', + 'spent' => 'Gasto', + 'earned' => 'Ganho', + 'overspent' => 'Gasto excedido', + 'left' => 'Left', + 'noBudget' => '(sem orçamento)', + 'maxAmount' => 'Valor Máximo', + 'minAmount' => 'Valor Mínimo', + 'billEntry' => 'Current bill entry', + 'name' => 'Nome', + 'date' => 'Data', + 'paid' => 'Pago', + 'unpaid' => 'Não pago', + 'day' => 'Dia', + 'budgeted' => 'Orçado', + 'period' => 'Período', + 'balance' => 'Saldo', + 'summary' => 'Sumário', + 'sum' => 'Soma', + 'average' => 'Média', + 'balanceFor' => 'Saldo para ":name"', // piggy banks: - 'piggy_bank' => 'Cofrinho', - 'new_piggy_bank' => 'Criar novo cofrinho', - 'store_piggy_bank' => 'Store new piggy bank', - 'account_status' => 'Account status', - 'left_for_piggy_banks' => 'Left for piggy banks', - 'sum_of_piggy_banks' => 'Sum of piggy banks', - 'saved_so_far' => 'Saved so far', - 'left_to_save' => 'Left to save', - 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', - 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', - 'add' => 'Adicionar', - 'remove' => 'Remover', - 'max_amount_add' => 'The maximum amount you can add is', - 'max_amount_remove' => 'The maximum amount you can remove is', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'details' => 'Detalhes', - 'events' => 'Eventos', - 'target_amount' => 'Valor alvo', - 'start_date' => 'Data de Início', - 'target_date' => 'Data Alvo', - 'no_target_date' => 'Nenhum data', - 'todo' => 'A fazer', - 'table' => 'Tabela', - 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', - 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', - 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', - 'delete_piggy_bank' => 'Apagar cofrinho ":name"', + 'piggy_bank' => 'Cofrinho', + 'new_piggy_bank' => 'Criar novo cofrinho', + 'store_piggy_bank' => 'Store new piggy bank', + 'account_status' => 'Account status', + 'left_for_piggy_banks' => 'Left for piggy banks', + 'sum_of_piggy_banks' => 'Sum of piggy banks', + 'saved_so_far' => 'Saved so far', + 'left_to_save' => 'Left to save', + 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', + 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', + 'add' => 'Adicionar', + 'remove' => 'Remover', + 'max_amount_add' => 'The maximum amount you can add is', + 'max_amount_remove' => 'The maximum amount you can remove is', + 'update_piggy_button' => 'Update piggy bank', + 'update_piggy_title' => 'Update piggy bank ":name"', + 'details' => 'Detalhes', + 'events' => 'Eventos', + 'target_amount' => 'Valor alvo', + 'start_date' => 'Data de Início', + 'target_date' => 'Data Alvo', + 'no_target_date' => 'Nenhum data', + 'todo' => 'A fazer', + 'table' => 'Tabela', + 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', + 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', + 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', + 'delete_piggy_bank' => 'Apagar cofrinho ":name"', // tags - 'regular_tag' => 'Just a regular tag.', - 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', - 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', - 'delete_tag' => 'Apagar tag ":tag"', - 'new_tag' => 'Fazer nova tag', - 'edit_tag' => 'Editar tag ":tag"', - 'no_year' => 'Nenhum ano definido', - 'no_month' => 'Nenhum mês definido', - 'tag_title_nothing' => 'Tags padrões', - 'tag_title_balancingAct' => 'Balancing act tags', - 'tag_title_advancePayment' => 'Advance payment tags', - 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', - 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', - 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', + 'regular_tag' => 'Just a regular tag.', + 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', + 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', + 'delete_tag' => 'Apagar tag ":tag"', + 'new_tag' => 'Fazer nova tag', + 'edit_tag' => 'Editar tag ":tag"', + 'no_year' => 'Nenhum ano definido', + 'no_month' => 'Nenhum mês definido', + 'tag_title_nothing' => 'Tags padrões', + 'tag_title_balancingAct' => 'Balancing act tags', + 'tag_title_advancePayment' => 'Advance payment tags', + 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', + 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', ]; diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php old mode 100644 new mode 100755 index 322c358c9c..c12730d20a --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -1,7 +1,14 @@ 'Nome do banco', 'bank_balance' => 'Saldo', 'savings_balance' => 'Salda da Poupança', @@ -37,6 +44,9 @@ return [ 'revenue_account' => 'Conta de Receita', 'amount' => 'Valor', 'date' => 'Data', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'category' => 'Categoria', 'tags' => 'Etiquetas', 'deletePermanently' => 'Apagar permanentemente', @@ -47,6 +57,7 @@ return [ 'symbol' => 'Símbolo', 'code' => 'Código', 'iban' => 'IBAN', + 'accountNumber' => 'Account number', 'csv' => 'Arquivo CSV', 'has_headers' => 'Cabeçalhos', 'date_format' => 'Formato da Data', @@ -70,40 +81,44 @@ return [ 'size' => 'Tamanho', 'trigger' => 'Trigger', 'stop_processing' => 'Stop processing', - - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - - - 'delete_account' => 'Apagar conta ":name"', - 'delete_bill' => 'Apagar fatura ":name"', - 'delete_budget' => 'Delete budget ":name"', - 'delete_category' => 'Delete category ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'delete_journal' => 'Delete transaction with description ":description"', - 'delete_attachment' => 'Apagar anexo ":name"', - 'delete_rule' => 'Delete rule ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - - 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', - 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', - 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', - 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', - 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', - 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', - 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', - 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', - 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', - 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', - 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', - - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', - 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', - 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_config' => 'Include configuration file', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'csv_comma' => 'A comma (,)', + 'csv_semicolon' => 'A semicolon (;)', + 'csv_tab' => 'A tab (invisible)', + 'delete_account' => 'Apagar conta ":name"', + 'delete_bill' => 'Apagar fatura ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Apagar anexo ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', + 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', ]; diff --git a/resources/lang/pt_BR/help.php b/resources/lang/pt_BR/help.php old mode 100644 new mode 100755 index 99e2295f4f..024d615c86 --- a/resources/lang/pt_BR/help.php +++ b/resources/lang/pt_BR/help.php @@ -1,25 +1,30 @@ 'Bem Vindo ao Firefly III', - 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', - 'sidebar-toggle-title' => 'Sidebar to create stuff', - 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', - 'account-menu-title' => 'All your accounts', - 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', - 'budget-menu-title' => 'Budgets', - 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', - 'report-menu-title' => 'Relatórios', - 'report-menu-text' => 'Check this out when you want a solid overview of your fiances.', - 'transaction-menu-title' => 'Transações', - 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', - 'option-menu-title' => 'Opções', - 'option-menu-text' => 'This is pretty self-explanatory.', - 'main-content-end-title' => 'Fim!', - 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', - - + 'main-content-title' => 'Bem Vindo ao Firefly III', + 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', + 'sidebar-toggle-title' => 'Sidebar to create stuff', + 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', + 'account-menu-title' => 'All your accounts', + 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', + 'budget-menu-title' => 'Budgets', + 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', + 'report-menu-title' => 'Relatórios', + 'report-menu-text' => 'Check this out when you want a solid overview of your fiances.', + 'transaction-menu-title' => 'Transações', + 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', + 'option-menu-title' => 'Opções', + 'option-menu-text' => 'This is pretty self-explanatory.', + 'main-content-end-title' => 'Fim!', + 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', 'index' => 'index', 'home' => 'home', 'accounts-index' => 'accounts.index', diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php old mode 100644 new mode 100755 index c8c03f946d..64e2bd4bff --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -1,6 +1,11 @@ 'Nome', @@ -19,6 +24,9 @@ return [ 'description' => 'Descrição', 'amount' => 'Total', 'date' => 'Data', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', 'from' => 'De', 'to' => 'Até', 'budget' => 'Orçamento', diff --git a/resources/lang/pt_BR/pagination.php b/resources/lang/pt_BR/pagination.php old mode 100644 new mode 100755 index 6a32f34ac0..b66406464e --- a/resources/lang/pt_BR/pagination.php +++ b/resources/lang/pt_BR/pagination.php @@ -1,19 +1,13 @@ '« Anterior', 'next' => 'Próximo »', - ]; diff --git a/resources/lang/pt_BR/passwords.php b/resources/lang/pt_BR/passwords.php old mode 100644 new mode 100755 index fa70e900ba..8690468e9e --- a/resources/lang/pt_BR/passwords.php +++ b/resources/lang/pt_BR/passwords.php @@ -8,22 +8,10 @@ */ return [ - - /* - |-------------------------------------------------------------------------- - |-------------------------------------------------------------------------- - | - | The following language lines are the default lines which match reasons - | that are given by the password broker for a password update attempt - | has failed, such as for an invalid token or invalid new password. - | - */ - - "password" => "As senhas devem ter pelo menos seis caracteres e devem ser iguais.", - "user" => "Não podemos encontrar um usuário com esse endereço de e-mail.", - "token" => "Este token de redefinição de senha é inválido.", - "sent" => "Nós te enviamos um email com um link para trocar a senha!", - "reset" => "Sua senha foi redefinida!", - 'blocked' => 'Boa tentativa.', - + 'password' => 'Passwords must be at least six characters and match the confirmation.', + 'user' => 'We can\'t find a user with that e-mail address.', + 'token' => 'This password reset token is invalid.', + 'sent' => 'We have e-mailed your password reset link!', + 'reset' => 'Your password has been reset!', + 'blocked' => 'Nice try though.', ]; diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php old mode 100644 new mode 100755 index daf771cd96..ec3e6fa80a --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -1,69 +1,79 @@ 'Este valor é inválido para o disparo selecionado.', - 'rule_action_value' => 'Este valor é inválido para a ação selecionada.', - 'invalid_domain' => 'Devido a restrições de segurança, você não pode registrar deste domínio.', - 'file_already_attached' => 'Arquivo ":name" carregado já está anexado para este objeto.', - 'file_attached' => 'Arquivo carregado com sucesso ":name".', - 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', - 'file_too_large' => 'Arquivo ":name" é muito grande.', - "accepted" => "O campo :attribute deve ser aceito.", - "active_url" => "O campo :attribute não contém um URL válido.", - "after" => "O campo :attribute deverá conter uma data posterior a :date.", - "alpha" => "O campo :attribute deverá conter apenas letras.", - "alpha_dash" => "O campo :attribute deverá conter apenas letras, números e traços.", - "alpha_num" => "O campo :attribute deverá conter apenas letras e números .", - "array" => "O campo :attribute precisa ser um conjunto.", - "unique_for_user" => "Já existe uma entrada com este :attribute.", - "before" => "O campo :attribute deverá conter uma data anterior a :date.", - 'unique_object_for_user' => 'Este nome já está em uso', - 'unique_account_for_user' => 'Este nome de conta já está em uso', - "between.numeric" => "O campo :attribute deverá ter um valor entre :min - :max.", - "between.file" => "O campo :attribute deverá ter um tamanho entre :min - :max kilobytes.", - "between.string" => "O campo :attribute deverá conter entre :min - :max caracteres.", - "between.array" => "O campo :attribute precisar ter entre :min - :max itens.", - "boolean" => "O campo :attribute deverá ter o valor verdadeiro ou falso.", - "confirmed" => "A confirmação para o campo :attribute não coincide.", - "date" => "O campo :attribute não contém uma data válida.", - "date_format" => "A data indicada para o campo :attribute não respeita o formato :format.", - "different" => "Os campos :attribute e :other deverão conter valores diferentes.", - "digits" => "O campo :attribute deverá conter :digits dígitos.", - "digits_between" => "O campo :attribute deverá conter entre :min a :max dígitos.", - "email" => "O campo :attribute não contém um endereço de email válido.", - "filled" => "O campo :attribute é obrigatório.", - "exists" => "O valor selecionado para o campo :attribute é inválido.", - "image" => "O campo :attribute deverá conter uma imagem.", - "in" => "O campo :attribute não contém um valor válido.", - "integer" => "O campo :attribute deverá conter um número inteiro.", - "ip" => "O campo :attribute deverá conter um IP válido.", - 'json' => 'O campo :attribute deverá conter uma string JSON válida.', - "max.numeric" => "O campo :attribute não deverá conter um valor superior a :max.", - "max.file" => "O campo :attribute não deverá ter um tamanho superior a :max kilobytes.", - "max.string" => "O campo :attribute não deverá conter mais de :max caracteres.", - "max.array" => "O campo :attribute deve ter no máximo :max itens.", - "mimes" => "O campo :attribute deverá conter um arquivo do tipo: :values.", - "min.numeric" => "O campo :attribute deverá ter um valor superior ou igual a :min.", - "min.file" => "O campo :attribute deverá ter no mínimo :min kilobytes.", - "min.string" => "O campo :attribute deverá conter no mínimo :min caracteres.", - "min.array" => "O campo :attribute deve ter no mínimo :min itens.", - "not_in" => "O campo :attribute contém um valor inválido.", - "numeric" => "O campo :attribute deverá conter um valor numérico.", - "regex" => "O formato do valor para o campo :attribute é inválido.", - "required" => "O campo :attribute é obrigatório.", - "required_if" => "O campo :attribute é obrigatório quando o valor do campo :other é igual a :value.", - 'required_unless' => 'O campo :attribute é obrigatório a menos que :other esteja presente em :values.', - "required_with" => "O campo :attribute é obrigatório quando :values está presente.", - "required_with_all" => "O campo :attribute é obrigatório quando um dos :values está presente.", - "required_without" => "O campo :attribute é obrigatório quanto :values não está presente.", - "required_without_all" => "O campo :attribute é obrigatório quando nenhum dos :values está presente.", - "same" => "Os campos :attribute e :other deverão conter valores iguais.", - "size.numeric" => "O campo :attribute deverá conter o valor :size.", - "size.file" => "O campo :attribute deverá ter o tamanho de :size kilobytes.", - "size.string" => "O campo :attribute deverá conter :size caracteres.", - "size.array" => "O campo :attribute deve ter :size itens.", - "unique" => "O valor indicado para o campo :attribute já se encontra utilizado.", - 'string' => 'O campo :attribute deve ser uma string.', - "url" => "O formato do URL indicado para o campo :attribute é inválido.", - "timezone" => "O campo :attribute deverá ter um fuso horário válido.", + 'iban' => 'This is not a valid IBAN.', + 'unique_account_number_for_user' => 'It looks like this account number is already in use.', + 'rule_trigger_value' => 'Este valor é inválido para o disparo selecionado.', + 'rule_action_value' => 'Este valor é inválido para a ação selecionada.', + 'invalid_domain' => 'Devido a restrições de segurança, você não pode registrar deste domínio.', + 'file_already_attached' => 'Arquivo ":name" carregado já está anexado para este objeto.', + 'file_attached' => 'Arquivo carregado com sucesso ":name".', + 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', + 'file_too_large' => 'Arquivo ":name" é muito grande.', + 'accepted' => 'O campo :attribute deve ser aceito.', + 'active_url' => 'O campo :attribute não contém um URL válido.', + 'after' => 'O campo :attribute deverá conter uma data posterior a :date.', + 'alpha' => 'O campo :attribute deverá conter apenas letras.', + 'alpha_dash' => 'O campo :attribute deverá conter apenas letras, números e traços.', + 'alpha_num' => 'O campo :attribute deverá conter apenas letras e números .', + 'array' => 'O campo :attribute precisa ser um conjunto.', + 'unique_for_user' => 'Já existe uma entrada com este :attribute.', + 'before' => 'O campo :attribute deverá conter uma data anterior a :date.', + 'unique_object_for_user' => 'Este nome já está em uso', + 'unique_account_for_user' => 'Este nome de conta já está em uso', + 'between.numeric' => 'O campo :attribute deverá ter um valor entre :min - :max.', + 'between.file' => 'O campo :attribute deverá ter um tamanho entre :min - :max kilobytes.', + 'between.string' => 'O campo :attribute deverá conter entre :min - :max caracteres.', + 'between.array' => 'O campo :attribute precisar ter entre :min - :max itens.', + 'boolean' => 'O campo :attribute deverá ter o valor verdadeiro ou falso.', + 'confirmed' => 'A confirmação para o campo :attribute não coincide.', + 'date' => 'O campo :attribute não contém uma data válida.', + 'date_format' => 'A data indicada para o campo :attribute não respeita o formato :format.', + 'different' => 'Os campos :attribute e :other deverão conter valores diferentes.', + 'digits' => 'O campo :attribute deverá conter :digits dígitos.', + 'digits_between' => 'O campo :attribute deverá conter entre :min a :max dígitos.', + 'email' => 'O campo :attribute não contém um endereço de email válido.', + 'filled' => 'O campo :attribute é obrigatório.', + 'exists' => 'O valor selecionado para o campo :attribute é inválido.', + 'image' => 'O campo :attribute deverá conter uma imagem.', + 'in' => 'O campo :attribute não contém um valor válido.', + 'integer' => 'O campo :attribute deverá conter um número inteiro.', + 'ip' => 'O campo :attribute deverá conter um IP válido.', + 'json' => 'O campo :attribute deverá conter uma string JSON válida.', + 'max.numeric' => 'O campo :attribute não deverá conter um valor superior a :max.', + 'max.file' => 'O campo :attribute não deverá ter um tamanho superior a :max kilobytes.', + 'max.string' => 'O campo :attribute não deverá conter mais de :max caracteres.', + 'max.array' => 'O campo :attribute deve ter no máximo :max itens.', + 'mimes' => 'O campo :attribute deverá conter um arquivo do tipo: :values.', + 'min.numeric' => 'O campo :attribute deverá ter um valor superior ou igual a :min.', + 'min.file' => 'O campo :attribute deverá ter no mínimo :min kilobytes.', + 'min.string' => 'O campo :attribute deverá conter no mínimo :min caracteres.', + 'min.array' => 'O campo :attribute deve ter no mínimo :min itens.', + 'not_in' => 'O campo :attribute contém um valor inválido.', + 'numeric' => 'O campo :attribute deverá conter um valor numérico.', + 'regex' => 'O formato do valor para o campo :attribute é inválido.', + 'required' => 'O campo :attribute é obrigatório.', + 'required_if' => 'O campo :attribute é obrigatório quando o valor do campo :other é igual a :value.', + 'required_unless' => 'O campo :attribute é obrigatório a menos que :other esteja presente em :values.', + 'required_with' => 'O campo :attribute é obrigatório quando :values está presente.', + 'required_with_all' => 'O campo :attribute é obrigatório quando um dos :values está presente.', + 'required_without' => 'O campo :attribute é obrigatório quanto :values não está presente.', + 'required_without_all' => 'O campo :attribute é obrigatório quando nenhum dos :values está presente.', + 'same' => 'Os campos :attribute e :other deverão conter valores iguais.', + 'size.numeric' => 'O campo :attribute deverá conter o valor :size.', + 'size.file' => 'O campo :attribute deverá ter o tamanho de :size kilobytes.', + 'size.string' => 'O campo :attribute deverá conter :size caracteres.', + 'size.array' => 'O campo :attribute deve ter :size itens.', + 'unique' => 'O valor indicado para o campo :attribute já se encontra utilizado.', + 'string' => 'O campo :attribute deve ser uma string.', + 'url' => 'O formato do URL indicado para o campo :attribute é inválido.', + 'timezone' => 'O campo :attribute deverá ter um fuso horário válido.', + '2fa_code' => 'The :attribute field is invalid.', ]; diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig index a801fede4a..0541560806 100644 --- a/resources/views/accounts/create.twig +++ b/resources/views/accounts/create.twig @@ -22,22 +22,26 @@
- {% if what == 'asset' %} -
-
-

{{ 'optionalFields'|_ }}

-
-
+
+
+

{{ 'optionalFields'|_ }}

+
+
+ + {{ ExpandedForm.text('iban') }} + {{ ExpandedForm.text('accountNumber') }} + + {% if what == 'asset' %} - {{ ExpandedForm.text('iban') }} {{ ExpandedForm.balance('openingBalance') }} {{ ExpandedForm.date('openingBalanceDate', phpdate('Y-m-d')) }} {{ ExpandedForm.select('accountRole', Config.get('firefly.accountRoles'),null,{'helpText' : 'asset_account_role_help'|_}) }} {{ ExpandedForm.balance('virtualBalance') }} + {% endif %} -
- {% endif %} +
+
diff --git a/resources/views/accounts/edit.twig b/resources/views/accounts/edit.twig index 32e0c55972..92f3065e75 100644 --- a/resources/views/accounts/edit.twig +++ b/resources/views/accounts/edit.twig @@ -28,6 +28,8 @@
{{ ExpandedForm.text('iban') }} + {{ ExpandedForm.text('accountNumber') }} + {% if account.accounttype.type == 'Default account' or account.accounttype.type == 'Asset account' %} {{ ExpandedForm.balance('openingBalance',null, {'currency' : openingBalance ? openingBalance.transactionCurrency : null}) }} {{ ExpandedForm.date('openingBalanceDate') }} diff --git a/resources/views/auth/login.twig b/resources/views/auth/login.twig index 7390c87879..c40e0b50af 100644 --- a/resources/views/auth/login.twig +++ b/resources/views/auth/login.twig @@ -14,6 +14,18 @@
{% endif %} + {% if session('logoutMessage') %} +
+
+ +
+
+ {% endif %} + -
+
- {% include 'list/journals.twig' with {'journals': tag.transactionjournals, 'showPageSum' : true} %} + {% include 'list/journals.twig' %}
diff --git a/resources/views/transactions/create.twig b/resources/views/transactions/create.twig index 05d169e044..d93560c4d5 100644 --- a/resources/views/transactions/create.twig +++ b/resources/views/transactions/create.twig @@ -67,6 +67,14 @@ {{ ExpandedForm.text('category') }} + + {{ ExpandedForm.date('interest_date') }} + + {{ ExpandedForm.date('book_date') }} + + {{ ExpandedForm.date('process_date') }} + + {{ ExpandedForm.text('tags') }} diff --git a/resources/views/transactions/edit.twig b/resources/views/transactions/edit.twig index 1353f3c440..4ae4ce0b47 100644 --- a/resources/views/transactions/edit.twig +++ b/resources/views/transactions/edit.twig @@ -67,6 +67,15 @@ {{ ExpandedForm.text('category',data['category']) }} + + {{ ExpandedForm.date('interest_date',data['interest_date']) }} + + {{ ExpandedForm.date('book_date',data['book_date']) }} + + {{ ExpandedForm.date('process_date',data['process_date']) }} + + + {{ ExpandedForm.text('tags') }} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index ec5913a81b..ee510c2381 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -17,6 +17,25 @@ {{ trans('list.date') }} {{ journal.date.formatLocalized(monthAndDayFormat) }} + {% if journal.interest_date %} + + {{ trans('list.interest_date') }} + {{ journal.interest_date.formatLocalized(monthAndDayFormat) }} + + {% endif %} + {% if journal.book_date %} + + {{ trans('list.book_date') }} + {{ journal.book_date.formatLocalized(monthAndDayFormat) }} + + {% endif %} + {% if journal.process_date %} + + {{ trans('list.process_date') }} + {{ journal.process_date.formatLocalized(monthAndDayFormat) }} + + {% endif %} + {{ trans('list.type') }} {{ journal.transactiontype.type|_ }} diff --git a/storage/build/test-upload.csv b/storage/build/test-upload.csv index 1ad0102bb6..b77b81d550 100644 --- a/storage/build/test-upload.csv +++ b/storage/build/test-upload.csv @@ -1,63 +1,3 @@ -NL11XOLA6707795988,NL10TAPT8906262744,"Suspendisse tempus eros euismod leo consequat, non malesuada arcu pretium.",20160123,15.03,gifts,holiday -NL11XOLA6707795988,NL93UPSZ1261542703,"Morbi quis mauris nec tortor egestas blandit.",20160123,47.38,groceries,studies -NL11XOLA6707795988,NL86IHAL3264575116,"Proin eleifend magna et ante cursus laoreet.",20160123,43.73,"books and such",groceries -NL11XOLA6707795988,NL63BKBO9993148806,"In scelerisque velit eget ante pharetra pretium.",20160123,39.21,dinner,holiday -NL11XOLA6707795988,NL22EZVA6611573534,"Donec tincidunt nibh nec diam sodales, feugiat pulvinar ante consequat.",20160123,24.77,taxes,holiday -NL11XOLA6707795988,NL40KZVR5107424627,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,17.19,dinner,clothes -NL11XOLA6707795988,NL29RHEE6437366575,"Suspendisse tempus eros euismod leo consequat, non malesuada arcu pretium.",20160123,43.14,cash,studies -NL11XOLA6707795988,NL63IEPJ7437420074,"Nunc sed leo scelerisque quam consectetur elementum quis consequat dui.",20160123,35.25,gifts,bills -NL11XOLA6707795988,NL62HQWJ8837203470,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,42.07,taxes,holiday -NL11XOLA6707795988,NL89FPEA1494900858,"Vivamus ultrices tortor vel semper lacinia.",20160123,25.31,gifts,groceries -NL11XOLA6707795988,NL76MDMU3222445567,"In scelerisque velit eget ante pharetra pretium.",20160123,41.76,cash,clothes -NL11XOLA6707795988,NL07TDQA3309954056,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,10.81,food,studies -NL11XOLA6707795988,NL35MKIY9736938778,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,46.82,"books and such",bills -NL11XOLA6707795988,NL63IEPJ7437420074,"Nullam malesuada orci in vulputate imperdiet.",20160123,10.3,groceries,studies -NL11XOLA6707795988,NL56DNGY5455310496,"Morbi quis mauris nec tortor egestas blandit.",20160123,22.39,bike,bills -NL11XOLA6707795988,NL17LKFR7594179781,"Vivamus ultrices tortor vel semper lacinia.",20160123,11.45,gifts,clothes -NL11XOLA6707795988,NL76UJQI9524250446,"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",20160123,49.23,bike,studies -NL11XOLA6707795988,NL45YTPP6157249080,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,22.6,cash,"other stuff" -NL11XOLA6707795988,NL46TFVH5548690248,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,44.34,bike,holiday -NL11XOLA6707795988,NL79XZWN0846248670,"Donec tincidunt nibh nec diam sodales, feugiat pulvinar ante consequat.",20160123,12.96,car,groceries -NL11XOLA6707795988,NL52MKIO8325583045,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,44.47,taxes,bills -NL11XOLA6707795988,NL47YVJU4419567422,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,17.51,car,bills -NL11XOLA6707795988,NL04TNIP4218080631,"Suspendisse tempus eros euismod leo consequat, non malesuada arcu pretium.",20160123,17.36,cash,groceries -NL11XOLA6707795988,NL68SOAC3516744723,"In scelerisque velit eget ante pharetra pretium.",20160123,34.22,taxes,groceries -NL11XOLA6707795988,NL53YVTS7223912863,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,27.9,food,"other stuff" -NL11XOLA6707795988,NL40QRBS9553175317,"Vivamus ultrices tortor vel semper lacinia.",20160123,-10.86,groceries,holiday -NL11XOLA6707795988,NL79XZWN0846248670,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,-19.57,bike,household -NL11XOLA6707795988,NL54TZNZ2922111989,"Pellentesque sit amet enim ac tortor euismod ullamcorper vel et massa.",20160123,-12.05,cash,holiday -NL11XOLA6707795988,NL04BAGX3284775110,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,-15.98,groceries,bills -NL11XOLA6707795988,NL49LULH7261231215,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,-35.67,food,clothes -NL11XOLA6707795988,NL11YHMI8046080217,"Nunc sed leo scelerisque quam consectetur elementum quis consequat dui.",20160123,-48.94,dinner,clothes -NL11XOLA6707795988,NL89BXNF2470379032,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,-33.82,taxes,holiday -NL11XOLA6707795988,NL74SHGG7300113478,"In scelerisque velit eget ante pharetra pretium.",20160123,-17.36,food,"other stuff" -NL11XOLA6707795988,NL48ZPLO1718215436,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,-19.31,food,holiday -NL11XOLA6707795988,NL13BJMO5341591615,"Pellentesque sit amet enim ac tortor euismod ullamcorper vel et massa.",20160123,-28.47,groceries,"other stuff" -NL11XOLA6707795988,NL59PKVU0116767154,"Proin eleifend magna et ante cursus laoreet.",20160123,-39.24,taxes,"other stuff" -NL11XOLA6707795988,NL72BQRL1220175315,"Nulla in neque sed velit ultricies placerat.",20160123,-17.53,groceries,bills -NL11XOLA6707795988,NL53QHDG0329036955,"Nulla a dui euismod nulla convallis dictum.",20160123,-39.45,"books and such",holiday -NL11XOLA6707795988,NL29RHEE6437366575,"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",20160123,-23.47,food,studies -NL11XOLA6707795988,NL48BLDJ9721843563,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,-16.56,food,"other stuff" -NL11XOLA6707795988,NL48BHXI9733658006,"Nulla in neque sed velit ultricies placerat.",20160123,-21.89,gifts,household -NL11XOLA6707795988,NL33VPSU8067103542,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,-11.71,groceries,clothes -NL11XOLA6707795988,NL62UDLY8957139303,"Nunc sed leo scelerisque quam consectetur elementum quis consequat dui.",20160123,-38.08,gifts,groceries -NL11XOLA6707795988,NL28EDMD2653501341,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,-39.57,cash,holiday -NL11XOLA6707795988,NL92QMZD6854625548,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,-48.8,car,groceries -NL11XOLA6707795988,NL37VSLJ0853659915,"Nulla a dui euismod nulla convallis dictum.",20160123,-34.55,bike,household -NL11XOLA6707795988,NL95NQHS4363870109,"Suspendisse tempus eros euismod leo consequat, non malesuada arcu pretium.",20160123,-34.95,dinner,studies -NL11XOLA6707795988,NL81LEJP9477634344,"Vivamus ultrices tortor vel semper lacinia.",20160123,-13.18,dinner,"other stuff" -NL11XOLA6707795988,NL14JYVJ1041891180,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,-12.79,groceries,bills -NL11XOLA6707795988,NL57SPBS0788124528,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,-35.53,bike,groceries -NL11XOLA6707795988,NL96DZCO4665940223,"Proin eleifend magna et ante cursus laoreet.",20160123,34.41,groceries, -NL11XOLA6707795988,NL96DZCO4665940223,"Curabitur vitae nisi consectetur, feugiat ipsum vitae, hendrerit sem.",20160123,35.8,groceries, -NL11XOLA6707795988,NL96DZCO4665940223,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,22.46,car, -NL11XOLA6707795988,NL96DZCO4665940223,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,43.74,bike, -NL11XOLA6707795988,NL96DZCO4665940223,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,28.68,dinner, -NL11XOLA6707795988,NL96DZCO4665940223,"Duis fringilla libero eget quam pharetra, non gravida ex fermentum.",20160123,32.36,car, -NL11XOLA6707795988,NL96DZCO4665940223,"Nam in purus porta, accumsan felis vitae, bibendum leo.",20160123,37.76,groceries, -NL11XOLA6707795988,NL96DZCO4665940223,"Nulla in neque sed velit ultricies placerat.",20160123,19.98,"books and such", -NL11XOLA6707795988,NL96DZCO4665940223,"Aenean dictum sem pellentesque, semper est eget, ultricies magna.",20160123,26.57,gifts, -NL11XOLA6707795988,NL96DZCO4665940223,"Morbi quis mauris nec tortor egestas blandit.",20160123,12.09,"books and such", NL11XOLA6707795988,NL96DZCO4665940223,"Vivamus ultrices tortor vel semper lacinia.",20160123,34.96,dinner, NL11XOLA6707795988,NL96DZCO4665940223,"Nullam malesuada orci in vulputate imperdiet.",20160123,16.62,food, NL11XOLA6707795988,NL96DZCO4665940223,"Nulla a dui euismod nulla convallis dictum.",20160123,31.14,car, diff --git a/storage/export/.gitignore b/storage/export/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/storage/export/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/BasicTest.php b/tests/BasicTest.php index ee499b9359..d32c69e252 100644 --- a/tests/BasicTest.php +++ b/tests/BasicTest.php @@ -3,11 +3,13 @@ /** * Class BasicTest */ + class BasicTest extends TestCase { /** - * A basic test example. + * A basic test example. May take a minute because the test data is being generated. * + * @slowThreshold 60000 * @return void */ public function testExample() diff --git a/tests/TestCase.php b/tests/TestCase.php index 9ed7149f88..a0245e08d8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,10 +1,13 @@ id)->where('name', 'viewRange')->delete(); + Preference::create( + [ + 'user_id' => $user->id, + 'name' => 'viewRange', + 'data' => $range, + ] + ); + // set period to match? + + } + // custom is a weird range + // (20 days): + if ($range === 'custom') { + $this->session( + [ + 'start' => Carbon::now()->subDays(20), + 'end' => Carbon::now(), + ] + ); + } + } + /** * Creates the application. * @@ -28,6 +62,22 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase return $app; } + /** + * @return array + */ + public function dateRangeProvider() + { + return [ + 'one day' => ['1D'], + 'one week' => ['1W'], + 'one month' => ['1M'], + 'three months' => ['3M'], + 'six months' => ['6M'], + 'one year' => ['1Y'], + 'custom range' => ['custom'], + ]; + } + /** * @return User */ @@ -65,6 +115,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase } // if the database copy does exists, copy back as original. + $this->session( [ 'start' => Carbon::now()->startOfMonth(), @@ -83,11 +134,14 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase public function tearDown() { parent::tearDown(); + } - // delete copy original. - //$original = __DIR__.'/../storage/database/testing.db'; - //unlink($original); - + /** + * @return User + */ + public function toBeDeletedUser() + { + return User::find(3); } /** @@ -95,7 +149,9 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase */ public function user() { - return User::find(1); + $user = User::find(1); + + return $user; } /** diff --git a/tests/acceptance/Controllers/AccountControllerTest.php b/tests/acceptance/Controllers/AccountControllerTest.php index e38518d2f2..fe4a6085d4 100644 --- a/tests/acceptance/Controllers/AccountControllerTest.php +++ b/tests/acceptance/Controllers/AccountControllerTest.php @@ -6,6 +6,7 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +use Illuminate\Pagination\LengthAwarePaginator; /** * Class AccountControllerTest @@ -13,11 +14,16 @@ class AccountControllerTest extends TestCase { /** - * @covers FireflyIII\Http\Controllers\AccountController::create + * @covers FireflyIII\Http\Controllers\AccountController::create + * @covers FireflyIII\Http\Controllers\AccountController::__construct + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testCreate() + public function testCreate($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/accounts/create/asset'); $this->assertResponseStatus(200); } @@ -55,28 +61,42 @@ class AccountControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\AccountController::index - * @covers FireflyIII\Http\Controllers\AccountController::isInArray + * @covers FireflyIII\Http\Controllers\AccountController::index + * @covers FireflyIII\Http\Controllers\AccountController::isInArray + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/accounts/asset'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\AccountController::show + * @covers FireflyIII\Http\Controllers\AccountController::show + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { + $repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/accounts/show/1'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\AccountController::store + * @covers FireflyIII\Http\Requests\AccountFormRequest::authorize + * @covers FireflyIII\Http\Requests\AccountFormRequest::rules + * */ public function testStore() { @@ -97,6 +117,8 @@ class AccountControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\AccountController::update + * @covers FireflyIII\Http\Requests\AccountFormRequest::authorize + * @covers FireflyIII\Http\Requests\AccountFormRequest::rules */ public function testUpdate() { diff --git a/tests/acceptance/Controllers/AttachmentControllerTest.php b/tests/acceptance/Controllers/AttachmentControllerTest.php index 6ad10b9bb2..7907eae095 100644 --- a/tests/acceptance/Controllers/AttachmentControllerTest.php +++ b/tests/acceptance/Controllers/AttachmentControllerTest.php @@ -15,6 +15,7 @@ class AttachmentControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\AttachmentController::delete + * @covers FireflyIII\Http\Controllers\AttachmentController::__construct */ public function testDelete() { @@ -42,6 +43,8 @@ class AttachmentControllerTest extends TestCase $this->be($this->user()); $this->call('GET', '/attachment/download/1'); $this->assertResponseStatus(200); + // must have certain headers + } /** @@ -66,6 +69,8 @@ class AttachmentControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\AttachmentController::update + * @covers FireflyIII\Http\Requests\AttachmentFormRequest::authorize + * @covers FireflyIII\Http\Requests\AttachmentFormRequest::rules */ public function testUpdate() { diff --git a/tests/acceptance/Controllers/Auth/AuthControllerTest.php b/tests/acceptance/Controllers/Auth/AuthControllerTest.php index 0631aee89f..8c97ee4abb 100644 --- a/tests/acceptance/Controllers/Auth/AuthControllerTest.php +++ b/tests/acceptance/Controllers/Auth/AuthControllerTest.php @@ -10,46 +10,10 @@ /** * Generated by PHPUnit_SkeletonGenerator on 2016-01-19 at 15:40:28. */ + class AuthControllerTest extends TestCase { - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::getLogin - */ - public function testGetLogin() - { - $this->call('GET', '/login'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::getLogout - */ - public function testGetLogout() - { - $this->be($this->user()); - $this->call('GET', '/logout'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::getRegister - */ - public function testGetRegister() - { - $this->call('GET', '/register'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::login - */ - public function testLogin() - { - $this->call('GET', '/login'); - $this->assertResponseStatus(200); - } - /** * @covers FireflyIII\Http\Controllers\Auth\AuthController::logout */ @@ -62,14 +26,16 @@ class AuthControllerTest extends TestCase // index should now redirect: $this->call('GET', '/'); $this->assertResponseStatus(302); - - } /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::postLogin + * @covers FireflyIII\Http\Controllers\Auth\AuthController::login + * @covers FireflyIII\Http\Controllers\Auth\AuthController::__construct + * @covers FireflyIII\Http\Controllers\Auth\AuthController::sendFailedLoginResponse + * @covers FireflyIII\Http\Controllers\Auth\AuthController::getFailedLoginMessage + * */ - public function testPostLogin() + public function testLogin() { $args = [ 'email' => 'thegrumpydictator@gmail.com', @@ -86,9 +52,13 @@ class AuthControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::postRegister + * @covers FireflyIII\Http\Controllers\Auth\AuthController::register + * @covers FireflyIII\Http\Controllers\Auth\AuthController::create + * @covers FireflyIII\Http\Controllers\Auth\AuthController::isBlockedDomain + * @covers FireflyIII\Http\Controllers\Auth\AuthController::getBlockedDomains + * @covers FireflyIII\Http\Controllers\Auth\AuthController::validator */ - public function testPostRegister() + public function testRegister() { $args = [ 'email' => 'thegrumpydictator+test@gmail.com', @@ -101,9 +71,9 @@ class AuthControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::register + * @covers FireflyIII\Http\Controllers\Auth\AuthController::showRegistrationForm */ - public function testRegister() + public function testShowRegistrationForm() { $this->call('GET', '/register'); $this->assertResponseStatus(200); diff --git a/tests/acceptance/Controllers/BillControllerTest.php b/tests/acceptance/Controllers/BillControllerTest.php index 9917620b5b..4c06d4a045 100644 --- a/tests/acceptance/Controllers/BillControllerTest.php +++ b/tests/acceptance/Controllers/BillControllerTest.php @@ -6,6 +6,8 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +use Carbon\Carbon; +use Illuminate\Support\Collection; /** @@ -16,6 +18,7 @@ class BillControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\BillController::create + * @covers FireflyIII\Http\Controllers\BillController::__construct */ public function testCreate() { @@ -26,7 +29,6 @@ class BillControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\BillController::delete - * @todo Implement testDelete(). */ public function testDelete() { @@ -58,38 +60,56 @@ class BillControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\BillController::index + * @covers FireflyIII\Http\Controllers\BillController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/bills'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\BillController::rescan + * @covers FireflyIII\Http\Controllers\BillController::rescan + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testRescan() + public function testRescan($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/bills/rescan/1'); $this->assertSessionHas('success'); $this->assertResponseStatus(302); } /** - * @covers FireflyIII\Http\Controllers\BillController::show + * @covers FireflyIII\Http\Controllers\BillController::show + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository->shouldReceive('getJournals')->once()->andReturn(new Collection); + $repository->shouldReceive('nextExpectedMatch')->once()->andReturn(new Carbon); $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/bills/show/1'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\BillController::store + * @covers FireflyIII\Http\Requests\BillFormRequest::authorize + * @covers FireflyIII\Http\Requests\BillFormRequest::getBillData + * @covers FireflyIII\Http\Requests\BillFormRequest::rules */ public function testStore() { @@ -115,6 +135,9 @@ class BillControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\BillController::update + * @covers FireflyIII\Http\Requests\BillFormRequest::authorize + * @covers FireflyIII\Http\Requests\BillFormRequest::getBillData + * @covers FireflyIII\Http\Requests\BillFormRequest::rules */ public function testUpdate() { diff --git a/tests/acceptance/Controllers/BudgetControllerTest.php b/tests/acceptance/Controllers/BudgetControllerTest.php index 806805cb0f..ab7c9e8463 100644 --- a/tests/acceptance/Controllers/BudgetControllerTest.php +++ b/tests/acceptance/Controllers/BudgetControllerTest.php @@ -6,6 +6,8 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +use Carbon\Carbon; +use Illuminate\Pagination\LengthAwarePaginator; /** @@ -14,14 +16,19 @@ class BudgetControllerTest extends TestCase { /** - * @covers FireflyIII\Http\Controllers\BudgetController::amount + * @covers FireflyIII\Http\Controllers\BudgetController::amount + * @covers FireflyIII\Http\Controllers\BudgetController::__construct + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testAmount() + public function testAmount($range) { $args = [ 'amount' => 1200, ]; $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('POST', '/budgets/amount/1', $args); $this->assertResponseStatus(200); @@ -71,58 +78,82 @@ class BudgetControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\BudgetController::index + * @covers FireflyIII\Http\Controllers\BudgetController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/budgets'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\BudgetController::noBudget + * @covers FireflyIII\Http\Controllers\BudgetController::noBudget + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testNoBudget() + public function testNoBudget($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/budgets/list/noBudget'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\BudgetController::postUpdateIncome + * @covers FireflyIII\Http\Controllers\BudgetController::postUpdateIncome + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testPostUpdateIncome() + public function testPostUpdateIncome($range) { $args = [ 'amount' => 1200, ]; $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('POST', '/budgets/income', $args); $this->assertResponseStatus(302); } /** - * @covers FireflyIII\Http\Controllers\BudgetController::show + * @covers FireflyIII\Http\Controllers\BudgetController::show + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { + // mock some stuff: + $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); + $repository->shouldReceive('firstActivity')->once()->andReturn(new Carbon); + $repository->shouldReceive('spentPerDay')->once()->andReturn([]); + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/budgets/show/1'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\BudgetController::store + * @covers FireflyIII\Http\Requests\BudgetFormRequest::authorize + * @covers FireflyIII\Http\Requests\BudgetFormRequest::rules */ public function testStore() { $this->be($this->user()); $this->session(['budgets.create.url' => 'http://localhost']); $args = [ - 'name' => 'Some kind of test budget.', + 'name' => 'Some kind of test budget.', ]; $this->call('POST', '/budgets/store', $args); @@ -132,13 +163,16 @@ class BudgetControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\BudgetController::update + * @covers FireflyIII\Http\Requests\BudgetFormRequest::authorize + * @covers FireflyIII\Http\Requests\BudgetFormRequest::rules */ public function testUpdate() { $this->be($this->user()); $this->session(['budgets.edit.url' => 'http://localhost']); $args = [ - 'name' => 'Some kind of test budget.', + 'name' => 'Some kind of test budget.', + 'id' => 1, ]; $this->call('POST', '/budgets/update/1', $args); @@ -147,11 +181,15 @@ class BudgetControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\BudgetController::updateIncome + * @covers FireflyIII\Http\Controllers\BudgetController::updateIncome + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testUpdateIncome() + public function testUpdateIncome($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/budgets/income'); $this->assertResponseStatus(200); } diff --git a/tests/acceptance/Controllers/CategoryControllerTest.php b/tests/acceptance/Controllers/CategoryControllerTest.php index 2d2bb291e3..66e6074c8f 100644 --- a/tests/acceptance/Controllers/CategoryControllerTest.php +++ b/tests/acceptance/Controllers/CategoryControllerTest.php @@ -16,6 +16,7 @@ class CategoryControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\CategoryController::create + * @covers FireflyIII\Http\Controllers\CategoryController::__construct */ public function testCreate() { @@ -59,56 +60,74 @@ class CategoryControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\CategoryController::index + * @covers FireflyIII\Http\Controllers\CategoryController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/categories'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\CategoryController::noCategory + * @covers FireflyIII\Http\Controllers\CategoryController::noCategory + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testNoCategory() + public function testNoCategory($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/categories/list/noCategory'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\CategoryController::show - * @covers FireflyIII\Http\Controllers\Controller::getSumOfRange + * @covers FireflyIII\Http\Controllers\CategoryController::show + * @covers FireflyIII\Http\Controllers\Controller::getSumOfRange + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/categories/show/1'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\CategoryController::showWithDate + * @covers FireflyIII\Http\Controllers\CategoryController::showWithDate + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShowWithDate() + public function testShowWithDate($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/categories/show/1/20150101'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\CategoryController::store + * @covers FireflyIII\Http\Requests\CategoryFormRequest::authorize + * @covers FireflyIII\Http\Requests\CategoryFormRequest::rules */ public function testStore() { $this->be($this->user()); $this->session(['categories.create.url' => 'http://localhost']); $args = [ - 'name' => 'Some kind of test cat.', + 'name' => 'Some kind of test cat.', ]; $this->call('POST', '/categories/store', $args); @@ -118,13 +137,16 @@ class CategoryControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\CategoryController::update + * @covers FireflyIII\Http\Requests\CategoryFormRequest::authorize + * @covers FireflyIII\Http\Requests\CategoryFormRequest::rules */ public function testUpdate() { $this->be($this->user()); $this->session(['categories.edit.url' => 'http://localhost']); $args = [ - 'name' => 'Some kind of test category.', + 'name' => 'Some kind of test category.', + 'id' => 1, ]; $this->call('POST', '/categories/update/1', $args); diff --git a/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php b/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php index 4f02f165f8..6956629e10 100644 --- a/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php @@ -14,6 +14,7 @@ class ChartAccountControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\Chart\AccountController::expenseAccounts + * @covers FireflyIII\Http\Controllers\Chart\AccountController::__construct */ public function testExpenseAccounts() { diff --git a/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php b/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php index 97fb4704eb..b361bd8834 100644 --- a/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php @@ -15,6 +15,7 @@ class ChartBillControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\Chart\BillController::frontpage + * @covers FireflyIII\Http\Controllers\Chart\BillController::__construct */ public function testFrontpage() { diff --git a/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php b/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php index ab8cc339ee..c5f05623ab 100644 --- a/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php @@ -18,12 +18,13 @@ class ChartBudgetControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\Chart\BudgetController::budget + * @covers FireflyIII\Http\Controllers\Chart\BudgetController::__construct */ public function testBudget() { $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $repository->shouldReceive('getExpensesPerMonth')->once()->andReturn(new Collection([new Budget])); + $repository->shouldReceive('spentPerDay')->once()->andReturn([]); $repository->shouldReceive('getFirstBudgetLimitDate')->once()->andReturn(new Carbon); $this->be($this->user()); diff --git a/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php b/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php index 0a6ccb44b0..7c8ebc3ebb 100644 --- a/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php @@ -16,6 +16,7 @@ class ChartCategoryControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\Chart\CategoryController::all + * @covers FireflyIII\Http\Controllers\Chart\CategoryController::__construct */ public function testAll() { diff --git a/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php b/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php index ce3ae5beba..861f901ddb 100644 --- a/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php @@ -15,6 +15,7 @@ class ChartPiggyBankControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\Chart\PiggyBankController::history + * @covers FireflyIII\Http\Controllers\Chart\PiggyBankController::__construct */ public function testHistory() { diff --git a/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php b/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php index 93fdba8cd1..6a36e48337 100644 --- a/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php +++ b/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php @@ -14,6 +14,7 @@ class ChartReportControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\Chart\ReportController::yearInOut + * @covers FireflyIII\Http\Controllers\Chart\ReportController::__construct */ public function testYearInOut() { diff --git a/tests/acceptance/Controllers/CsvControllerTest.php b/tests/acceptance/Controllers/CsvControllerTest.php index 9b46be9a58..0034d9c77a 100644 --- a/tests/acceptance/Controllers/CsvControllerTest.php +++ b/tests/acceptance/Controllers/CsvControllerTest.php @@ -16,13 +16,27 @@ class CsvControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\CsvController::columnRoles + * @covers FireflyIII\Http\Controllers\CsvController::__construct */ public function testColumnRoles() { $this->be($this->user()); - // create session data: - $this->session($this->getSessionData()); + // mock wizard + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-import-account', 'csv-specifix', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + + // mock Data and Reader + $reader = $this->mock('League\Csv\Reader'); + $data = $this->mock('FireflyIII\Helpers\Csv\Data'); + $data->shouldReceive('getReader')->times(2)->andReturn($reader); + $reader->shouldReceive('fetchOne')->withNoArgs()->once()->andReturn([1, 2, 3, 4]); + $reader->shouldReceive('fetchOne')->with(1)->once()->andReturn([1, 2, 3, 4]); + + $data->shouldReceive('getRoles')->once()->andReturn([]); + $data->shouldReceive('getMap')->once()->andReturn([]); + $data->shouldReceive('hasHeaders')->once()->andReturn([]); $this->call('GET', '/csv/column_roles'); @@ -35,7 +49,11 @@ class CsvControllerTest extends TestCase public function testDownloadConfig() { $this->be($this->user()); - $this->session($this->getSessionData()); + + $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + $this->call('GET', '/csv/download-config'); $this->assertResponseStatus(200); } @@ -46,7 +64,11 @@ class CsvControllerTest extends TestCase public function testDownloadConfigPage() { $this->be($this->user()); - $this->session($this->getSessionData()); + + $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + $this->call('GET', '/csv/download'); $this->assertResponseStatus(200); } @@ -68,25 +90,15 @@ class CsvControllerTest extends TestCase public function testInitialParse() { $this->be($this->user()); - // post data: - $postData = [ - 'role' => [ - 0 => 'account-iban', - 1 => 'opposing-iban', - 2 => 'description', - 3 => 'date-transaction', - 4 => 'amount', - 5 => 'category-name', - 6 => 'budget-name', - ], - 'map' => [0 => 1, 1 => 1], - ]; - // create session data: - $this->session($this->getSessionData()); + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + $wizard->shouldReceive('processSelectedRoles')->once()->with([])->andReturn([1, 2, 3]); + $wizard->shouldReceive('processSelectedMapping')->once()->with([1, 2, 3], [])->andReturn([1, 2, 3]); - $this->call('POST', '/csv/initial_parse', $postData); + $this->call('POST', '/csv/initial_parse'); // should be redirect $this->assertResponseStatus(302); @@ -100,25 +112,15 @@ class CsvControllerTest extends TestCase public function testInitialParseNoMap() { $this->be($this->user()); - // post data: - $postData = [ - 'role' => [ - 0 => 'account-iban', - 1 => 'opposing-iban', - 2 => 'description', - 3 => 'date-transaction', - 4 => 'amount', - 5 => 'category-name', - 6 => 'budget-name', - ], - 'map' => [], - ]; - // create session data: - $this->session($this->getSessionData()); + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + $wizard->shouldReceive('processSelectedRoles')->once()->with([])->andReturn([1, 2, 3]); + $wizard->shouldReceive('processSelectedMapping')->once()->with([1, 2, 3], [])->andReturn([]); - $this->call('POST', '/csv/initial_parse', $postData); + $this->call('POST', '/csv/initial_parse'); // should be redirect $this->assertResponseStatus(302); @@ -133,7 +135,23 @@ class CsvControllerTest extends TestCase { $this->be($this->user()); - $this->session($this->getSessionData()); + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + + $wizard->shouldReceive('showOptions')->once()->with([])->andReturn([]); + + // mock Data and Reader + $reader = $this->mock('League\Csv\Reader'); + $data = $this->mock('FireflyIII\Helpers\Csv\Data'); + $data->shouldReceive('getReader')->once()->andReturn($reader); + $data->shouldReceive('getMap')->times(3)->andReturn([]); + $data->shouldReceive('hasHeaders')->once()->andReturn(true); + + $wizard->shouldReceive('getMappableValues')->with($reader, [], true)->andReturn([]); + + $data->shouldReceive('getMapped')->once()->andReturn([]); + $this->call('GET', '/csv/map'); $this->assertResponseStatus(200); @@ -146,36 +164,41 @@ class CsvControllerTest extends TestCase public function testProcess() { $this->be($this->user()); - $this->session($this->getSessionData()); + + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-mapped', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + + // mock + $data = $this->mock('FireflyIII\Helpers\Csv\Data'); + + // mock + $importer = $this->mock('FireflyIII\Helpers\Csv\Importer'); + $importer->shouldReceive('setData')->once()->with($data); + $importer->shouldReceive('run')->once()->withNoArgs(); + + $importer->shouldReceive('getRows')->once()->withNoArgs(); + $importer->shouldReceive('getErrors')->once()->withNoArgs(); + $importer->shouldReceive('getImported')->once()->withNoArgs(); + $importer->shouldReceive('getJournals')->once()->withNoArgs(); + $this->call('GET', '/csv/process'); $this->assertResponseStatus(200); } + /** * @covers FireflyIII\Http\Controllers\CsvController::saveMapping */ public function testSaveMapping() { $this->be($this->user()); - $this->session($this->getSessionData()); - $postData = [ - 'mapping' - => [0 => ['NL11XOLA6707795988' => '1',], - 1 => ['NL10TAPT8906262744' => '0', 'NL93UPSZ1261542703' => '0', 'NL86IHAL3264575116' => '0', 'NL63BKBO9993148806' => '0', - 'NL22EZVA6611573534' => '0', 'NL40KZVR5107424627' => '0', 'NL29RHEE6437366575' => '0', 'NL63IEPJ7437420074' => '0', - 'NL62HQWJ8837203470' => '0', 'NL89FPEA1494900858' => '0', 'NL76MDMU3222445567' => '0', 'NL07TDQA3309954056' => '0', - 'NL35MKIY9736938778' => '0', 'NL56DNGY5455310496' => '0', 'NL17LKFR7594179781' => '0', 'NL76UJQI9524250446' => '0', - 'NL45YTPP6157249080' => '0', 'NL46TFVH5548690248' => '0', 'NL79XZWN0846248670' => '0', 'NL52MKIO8325583045' => '0', - 'NL47YVJU4419567422' => '0', 'NL04TNIP4218080631' => '0', 'NL68SOAC3516744723' => '0', 'NL53YVTS7223912863' => '0', - 'NL40QRBS9553175317' => '0', 'NL54TZNZ2922111989' => '0', 'NL04BAGX3284775110' => '0', 'NL49LULH7261231215' => '0', - 'NL11YHMI8046080217' => '0', 'NL89BXNF2470379032' => '0', 'NL74SHGG7300113478' => '0', 'NL48ZPLO1718215436' => '0', - 'NL13BJMO5341591615' => '0', 'NL59PKVU0116767154' => '0', 'NL72BQRL1220175315' => '0', 'NL53QHDG0329036955' => '0', - 'NL48BLDJ9721843563' => '0', 'NL48BHXI9733658006' => '0', 'NL33VPSU8067103542' => '0', 'NL62UDLY8957139303' => '0', - 'NL28EDMD2653501341' => '0', 'NL92QMZD6854625548' => '0', 'NL37VSLJ0853659915' => '0', 'NL95NQHS4363870109' => '0', - 'NL81LEJP9477634344' => '0', 'NL14JYVJ1041891180' => '0', 'NL57SPBS0788124528' => '0', - 'NL96DZCO4665940223' => '2',],],]; - $this->call('POST', '/csv/save_mapping', $postData); + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-delimiter']; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); + + $this->call('POST', '/csv/save_mapping', ['mapping' => []]); $this->assertResponseStatus(302); $this->assertRedirectedToRoute('csv.download-config-page'); @@ -189,63 +212,27 @@ class CsvControllerTest extends TestCase { $this->be($this->user()); - $file = new UploadedFile(storage_path('build/test-upload.csv'), 'test-file.csv', 'text/plain', 446); - $args = [ - 'date_format' => 'Ymd', - 'csv_import_account' => 1, - ]; + $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); + $wizard->shouldReceive('storeCsvFile')->andReturn(''); - $this->call('POST', '/csv/upload', $args, [], ['csv' => $file]); + + // mock Data and Reader + $data = $this->mock('FireflyIII\Helpers\Csv\Data'); + $data->shouldReceive('setCsvFileLocation')->once()->with(''); + $data->shouldReceive('setDateFormat')->once()->with('Ymd'); + $data->shouldReceive('setHasHeaders')->once()->with(''); + $data->shouldReceive('setMap')->once()->with([]); + $data->shouldReceive('setMapped')->once()->with([]); + $data->shouldReceive('setRoles')->once()->with([]); + $data->shouldReceive('setSpecifix')->once()->with([]); + $data->shouldReceive('setImportAccount')->once()->with(0); + $data->shouldReceive('setDelimiter')->once()->with(','); + + + $file = new UploadedFile(storage_path('build/test-upload.csv'), 'test-file.csv', 'text/plain', 446); + $this->call('POST', '/csv/upload', ['date_format' => 'Ymd'], [], ['csv' => $file]); // csv data set: - //$this->assertSessionHas('csv-file', 'abc'); - $this->assertSessionHas('csv-date-format', 'Ymd'); - $this->assertSessionHas('csv-has-headers', false); - $this->assertSessionHas('csv-map', []); - $this->assertSessionHas('csv-mapped', []); - $this->assertSessionHas('csv-roles', []); - $this->assertSessionHas('csv-specifix', []); - $this->assertSessionHas('csv-import-account', 1); - $this->assertSessionHas('csv-delimiter', ','); - $this->assertResponseStatus(302); } - - /** - * @return string - */ - protected function createUploadedFile() - { - $original = storage_path('build/test-upload.csv'); - $time = '12345'; - $fileName = 'csv-upload-' . $this->user()->id . '-' . $time . '.csv.encrypted'; - $fullPath = storage_path('build') . DIRECTORY_SEPARATOR . $fileName; - - if (!file_exists($fullPath)) { - $content = file_get_contents($original); - $contentEncrypted = Crypt::encrypt($content); - file_put_contents($fullPath, $contentEncrypted); - } - - return $fullPath; - } - - /** - * @return array - */ - protected function getSessionData() - { - return [ - 'csv-file' => $this->createUploadedFile(), - 'csv-date-format' => 'Ymd', - 'csv-has-headers' => false, - 'csv-import-account' => 1, - 'csv-delimiter' => ',', - 'csv-specifix' => ['Dummy'], - 'csv-roles' => [0 => 'account-iban', 1 => 'opposing-iban', 2 => 'description', 3 => 'date-transaction', 4 => 'amount', - 5 => 'category-name', 6 => 'budget-name',], - 'csv-map' => [0 => 'account-iban', 1 => 'opposing-iban',], - 'csv-mapped' => [0 => ['NL11XOLA6707795988' => '1',], 1 => ['NL96DZCO4665940223' => '2',]], - ]; - } } diff --git a/tests/acceptance/Controllers/CurrencyControllerTest.php b/tests/acceptance/Controllers/CurrencyControllerTest.php index eafd8d193e..32f620e79c 100644 --- a/tests/acceptance/Controllers/CurrencyControllerTest.php +++ b/tests/acceptance/Controllers/CurrencyControllerTest.php @@ -16,6 +16,7 @@ class CurrencyControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\CurrencyController::create + * @covers FireflyIII\Http\Controllers\CurrencyController::__construct */ public function testCreate() { @@ -71,17 +72,24 @@ class CurrencyControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\CurrencyController::index + * @covers FireflyIII\Http\Controllers\CurrencyController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/currency'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\CurrencyController::store + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::authorize + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::rules + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::getCurrencyData */ public function testStore() { @@ -101,6 +109,9 @@ class CurrencyControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\CurrencyController::update + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::authorize + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::rules + * @covers FireflyIII\Http\Requests\CurrencyFormRequest::getCurrencyData */ public function testUpdate() { diff --git a/tests/acceptance/Controllers/HelpControllerTest.php b/tests/acceptance/Controllers/HelpControllerTest.php index e327a9f57f..aa44217e2a 100644 --- a/tests/acceptance/Controllers/HelpControllerTest.php +++ b/tests/acceptance/Controllers/HelpControllerTest.php @@ -16,6 +16,7 @@ class HelpControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\HelpController::show + * @covers FireflyIII\Http\Controllers\HelpController::__construct */ public function testShow() { diff --git a/tests/acceptance/Controllers/HomeControllerTest.php b/tests/acceptance/Controllers/HomeControllerTest.php index ba2d8cc4c6..9800585e23 100644 --- a/tests/acceptance/Controllers/HomeControllerTest.php +++ b/tests/acceptance/Controllers/HomeControllerTest.php @@ -7,10 +7,12 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ + class HomeControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\HomeController::dateRange + * @covers FireflyIII\Http\Controllers\HomeController::__construct */ public function testDateRange() { @@ -39,12 +41,16 @@ class HomeControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\HomeController::index - * @covers FireflyIII\Http\Controllers\Controller::__construct + * @covers FireflyIII\Http\Controllers\HomeController::index + * @covers FireflyIII\Http\Controllers\Controller::__construct + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/'); $this->assertResponseStatus(200); } diff --git a/tests/acceptance/Controllers/JsonControllerTest.php b/tests/acceptance/Controllers/JsonControllerTest.php index e8ba9f8c16..f4b1dfb988 100644 --- a/tests/acceptance/Controllers/JsonControllerTest.php +++ b/tests/acceptance/Controllers/JsonControllerTest.php @@ -17,6 +17,7 @@ class JsonControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\JsonController::action + * @covers FireflyIII\Http\Controllers\JsonController::__construct */ public function testAction() { @@ -26,51 +27,71 @@ class JsonControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\JsonController::boxBillsPaid + * @covers FireflyIII\Http\Controllers\JsonController::boxBillsPaid + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testBoxBillsPaid() + public function testBoxBillsPaid($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/box/bills-paid'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\JsonController::boxBillsUnpaid + * @covers FireflyIII\Http\Controllers\JsonController::boxBillsUnpaid + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testBoxBillsUnpaid() + public function testBoxBillsUnpaid($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/box/bills-unpaid'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\JsonController::boxIn + * @covers FireflyIII\Http\Controllers\JsonController::boxIn + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testBoxIn() + public function testBoxIn($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/box/in'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\JsonController::boxOut + * @covers FireflyIII\Http\Controllers\JsonController::boxOut + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testBoxOut() + public function testBoxOut($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/box/out'); $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\JsonController::categories + * @covers FireflyIII\Http\Controllers\JsonController::categories + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testCategories() + public function testCategories($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/categories'); $this->assertResponseStatus(200); } @@ -127,9 +148,12 @@ class JsonControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\JsonController::transactionJournals + * @covers FireflyIII\Http\Controllers\JsonController::transactionJournals + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testTransactionJournals() + public function testTransactionJournals($range) { $type = factory(FireflyIII\Models\TransactionType::class)->make(); $repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); @@ -138,6 +162,7 @@ class JsonControllerTest extends TestCase $repository->shouldReceive('getJournalsOfType')->with($type)->once()->andReturn(new Collection); $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/json/transaction-journals/deposit'); $this->assertResponseStatus(200); } diff --git a/tests/acceptance/Controllers/NewUserControllerTest.php b/tests/acceptance/Controllers/NewUserControllerTest.php index 01841352f4..5563c8c92b 100644 --- a/tests/acceptance/Controllers/NewUserControllerTest.php +++ b/tests/acceptance/Controllers/NewUserControllerTest.php @@ -16,6 +16,7 @@ class NewUserControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\NewUserController::index + * @covers FireflyIII\Http\Controllers\NewUserController::__construct */ public function testIndex() { @@ -37,14 +38,18 @@ class NewUserControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\NewUserController::submit + * @covers FireflyIII\Http\Requests\NewUserFormRequest::authorize + * @covers FireflyIII\Http\Requests\NewUserFormRequest::rules */ public function testSubmit() { $this->be($this->emptyUser()); $args = [ - 'bank_name' => 'New bank', - 'bank_balance' => 100, + 'bank_name' => 'New bank', + 'bank_balance' => 100, + 'savings_balance' => 200, + 'credit_card_limit' => 1000, ]; $this->call('POST', '/new-user/submit', $args); diff --git a/tests/acceptance/Controllers/PiggyBankControllerTest.php b/tests/acceptance/Controllers/PiggyBankControllerTest.php index 2ab8691f3d..829ef8a3f4 100644 --- a/tests/acceptance/Controllers/PiggyBankControllerTest.php +++ b/tests/acceptance/Controllers/PiggyBankControllerTest.php @@ -14,11 +14,16 @@ class PiggyBankControllerTest extends TestCase { /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::add + * @covers FireflyIII\Http\Controllers\PiggyBankController::add + * @covers FireflyIII\Http\Controllers\PiggyBankController::__construct + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testAdd() + public function testAdd($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/piggy-banks/add/1'); $this->assertResponseStatus(200); } @@ -35,7 +40,6 @@ class PiggyBankControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\PiggyBankController::delete - * @todo Implement testDelete(). */ public function testDelete() { @@ -67,11 +71,15 @@ class PiggyBankControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::index + * @covers FireflyIII\Http\Controllers\PiggyBankController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/piggy-banks'); $this->assertResponseStatus(200); } @@ -93,7 +101,6 @@ class PiggyBankControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\PiggyBankController::postAdd - * @todo Implement testPostAdd(). */ public function testPostAdd() { @@ -133,17 +140,23 @@ class PiggyBankControllerTest extends TestCase } /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::show + * @covers FireflyIII\Http\Controllers\PiggyBankController::show + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { $this->be($this->user()); + $this->changeDateRange($this->user(), $range); $this->call('GET', '/piggy-banks/show/1'); $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\PiggyBankController::store + * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::authorize + * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::rules */ public function testStore() { @@ -163,6 +176,8 @@ class PiggyBankControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\PiggyBankController::update + * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::authorize + * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::rules */ public function testUpdate() { @@ -173,6 +188,7 @@ class PiggyBankControllerTest extends TestCase 'name' => 'Updated', 'targetamount' => 100, 'account_id' => 2, + 'id' => 1, ]; $this->call('POST', '/piggy-banks/update/1', $args); diff --git a/tests/acceptance/Controllers/PreferencesControllerTest.php b/tests/acceptance/Controllers/PreferencesControllerTest.php index 4fbf98a051..ecfdc9a9a5 100644 --- a/tests/acceptance/Controllers/PreferencesControllerTest.php +++ b/tests/acceptance/Controllers/PreferencesControllerTest.php @@ -16,25 +16,35 @@ class PreferencesControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\PreferencesController::index - * @todo Implement testIndex(). + * @covers FireflyIII\Http\Controllers\PreferencesController::__construct */ public function testIndex() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/preferences'); + $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\PreferencesController::postIndex - * @todo Implement testPostIndex(). + * @covers FireflyIII\Http\Controllers\PreferencesController::postIndex + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testPostIndex() + public function testPostIndex($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'frontPageAccounts' => [1], + 'viewRange' => $range, + 'budgetMaximum' => 100, + 'customFiscalYear' => 1, + 'fiscalYearStart' => '01-01', + 'language' => 'en_US', + ]; + + $this->be($this->user()); + $this->call('POST', '/preferences', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } } diff --git a/tests/acceptance/Controllers/ProfileControllerTest.php b/tests/acceptance/Controllers/ProfileControllerTest.php index ffdf216572..38f7799e93 100644 --- a/tests/acceptance/Controllers/ProfileControllerTest.php +++ b/tests/acceptance/Controllers/ProfileControllerTest.php @@ -15,61 +15,69 @@ class ProfileControllerTest extends TestCase { /** * @covers FireflyIII\Http\Controllers\ProfileController::changePassword - * @todo Implement testChangePassword(). + * @covers FireflyIII\Http\Controllers\ProfileController::__construct */ public function testChangePassword() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/profile/change-password'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\ProfileController::deleteAccount - * @todo Implement testDeleteAccount(). */ public function testDeleteAccount() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + + $this->be($this->user()); + $this->call('GET', '/profile/delete-account'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\ProfileController::index - * @todo Implement testIndex(). */ public function testIndex() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/profile'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\ProfileController::postChangePassword - * @todo Implement testPostChangePassword(). + * @covers FireflyIII\Http\Requests\ProfileFormRequest::authorize + * @covers FireflyIII\Http\Requests\ProfileFormRequest::rules */ public function testPostChangePassword() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'current_password' => 'james', + 'new_password' => 'sander', + 'new_password_confirmation' => 'sander', + ]; + $this->be($this->user()); + $this->call('POST', '/profile/change-password', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\ProfileController::postDeleteAccount - * @todo Implement testPostDeleteAccount(). + * @covers FireflyIII\Http\Requests\DeleteAccountFormRequest::authorize + * @covers FireflyIII\Http\Requests\DeleteAccountFormRequest::rules */ public function testPostDeleteAccount() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'password' => 'james', + ]; + + $this->be($this->toBeDeletedUser()); + $this->call('POST', '/profile/delete-account', $args); + $this->assertResponseStatus(302); + $this->assertRedirectedToRoute('index'); + $this->assertNull(DB::table('users')->find(3)); } } diff --git a/tests/acceptance/Controllers/ReportControllerTest.php b/tests/acceptance/Controllers/ReportControllerTest.php index 641f7def38..60246642b0 100644 --- a/tests/acceptance/Controllers/ReportControllerTest.php +++ b/tests/acceptance/Controllers/ReportControllerTest.php @@ -7,69 +7,123 @@ * of the MIT license. See the LICENSE file for details. */ +use FireflyIII\Helpers\Collection\Account as AccountCollection; +use FireflyIII\Helpers\Collection\Balance; +use FireflyIII\Helpers\Collection\Bill as BillCollection; +use FireflyIII\Helpers\Collection\Budget as BudgetCollection; +use FireflyIII\Helpers\Collection\Category as CategoryCollection; +use FireflyIII\Helpers\Collection\Expense; +use FireflyIII\Helpers\Collection\Income; +use Illuminate\Support\Collection; /** * Generated by PHPUnit_SkeletonGenerator on 2016-01-19 at 15:39:28. */ class ReportControllerTest extends TestCase { + /** - * @covers FireflyIII\Http\Controllers\ReportController::defaultMonth - * @todo Implement testDefaultMonth(). + * @covers FireflyIII\Http\Controllers\ReportController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testDefaultMonth() + public function testIndex($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/reports'); + $this->assertResponseStatus(200); + } /** - * @covers FireflyIII\Http\Controllers\ReportController::defaultMultiYear - * @todo Implement testDefaultMultiYear(). + * @covers FireflyIII\Http\Controllers\ReportController::__construct + * @covers FireflyIII\Http\Controllers\ReportController::report + * @covers FireflyIII\Http\Controllers\ReportController::defaultMonth + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testDefaultMultiYear() + public function testReportDefaultMonth($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + // mock some stuff. + $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); + $budgetHelper = $this->mock('FireflyIII\Helpers\Report\BudgetReportHelperInterface'); + $balanceHelper = $this->mock('FireflyIII\Helpers\Report\BalanceReportHelperInterface'); + $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); + + + $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); + $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); + $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); + $budgetHelper->shouldReceive('getBudgetReport')->once()->andReturn(new BudgetCollection); + $defaultHelper->shouldReceive('getCategoryReport')->once()->andReturn(new CategoryCollection); + $balanceHelper->shouldReceive('getBalanceReport')->once()->andReturn(new Balance); + $defaultHelper->shouldReceive('getBillReport')->once()->andReturn(new BillCollection); + $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); + + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/reports/report/default/20160101/20160131/1,2'); + $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\ReportController::defaultYear - * @todo Implement testDefaultYear(). + * @covers FireflyIII\Http\Controllers\ReportController::report + * @covers FireflyIII\Http\Controllers\ReportController::defaultMultiYear + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testDefaultYear() + public function testReportDefaultMultiYear($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + + // mock some stuff. + $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); + $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); + $budgetRepos = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $categoryRepos = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); + + $budgetRepos->shouldReceive('getActiveBudgets')->once()->andReturn(new Collection); + $categoryRepos->shouldReceive('listCategories')->once()->andReturn(new Collection); + + + $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); + $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); + $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); + $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); + + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/reports/report/default/20160101/20171231/1,2'); + $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\ReportController::index - * @todo Implement testIndex(). + * @covers FireflyIII\Http\Controllers\ReportController::report + * @covers FireflyIII\Http\Controllers\ReportController::defaultYear + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testReportDefaultYear($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - /** - * @covers FireflyIII\Http\Controllers\ReportController::report - * @todo Implement testReport(). - */ - public function testReport() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + // mock some stuff. + $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); + $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); + + + $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); + $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); + $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); + $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); + + + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/reports/report/default/20160101/20161231/1,2'); + $this->assertResponseStatus(200); } } diff --git a/tests/acceptance/Controllers/RuleControllerTest.php b/tests/acceptance/Controllers/RuleControllerTest.php index b33352c035..ab8ed0a935 100644 --- a/tests/acceptance/Controllers/RuleControllerTest.php +++ b/tests/acceptance/Controllers/RuleControllerTest.php @@ -15,134 +15,163 @@ class RuleControllerTest extends TestCase { /** + * @covers FireflyIII\Http\Controllers\RuleController::__construct * @covers FireflyIII\Http\Controllers\RuleController::create - * @todo Implement testCreate(). */ public function testCreate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/create/1'); + $this->assertResponseStatus(200); + } /** * @covers FireflyIII\Http\Controllers\RuleController::delete - * @todo Implement testDelete(). */ public function testDelete() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/rules/delete/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleController::destroy - * @todo Implement testDestroy(). */ public function testDestroy() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule.delete.url' => 'http://localhost']); + + $this->be($this->user()); + $this->call('POST', '/rules/destroy/1'); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\RuleController::down - * @todo Implement testDown(). */ public function testDown() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/rules/down/1'); + $this->assertResponseStatus(302); } /** * @covers FireflyIII\Http\Controllers\RuleController::edit - * @todo Implement testEdit(). + * @covers FireflyIII\Http\Controllers\RuleController::getCurrentTriggers + * @covers FireflyIII\Http\Controllers\RuleController::getCurrentActions */ public function testEdit() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/rules/edit/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleController::index - * @todo Implement testIndex(). + * @covers FireflyIII\Http\Controllers\RuleController::createDefaultRuleGroup + * @covers FireflyIII\Http\Controllers\RuleController::createDefaultRule */ public function testIndex() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleController::reorderRuleActions - * @todo Implement testReorderRuleActions(). */ public function testReorderRuleActions() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $args = ['actions' => [1, 2, 3]]; + $this->call('POST', '/rules/rules/action/reorder/1', $args); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleController::reorderRuleTriggers - * @todo Implement testReorderRuleTriggers(). */ public function testReorderRuleTriggers() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $args = ['actions' => [1, 2]]; + $this->call('POST', '/rules/rules/trigger/reorder/1', $args); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleController::store - * @todo Implement testStore(). + * @covers FireflyIII\Http\Requests\RuleFormRequest::authorize + * @covers FireflyIII\Http\Requests\RuleFormRequest::rules */ public function testStore() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule.create.url' => 'http://localhost']); + $this->be($this->user()); + $args = [ + 'rule_group_id' => 1, + 'title' => 'Some new rule', + 'user_id' => 1, + 'trigger' => 'store-journal', + 'description' => 'Some new rule', + 'rule-trigger' => ['description_is'], + 'rule-trigger-value' => ['something'], + 'rule-trigger-stop' => [], + 'rule-action' => ['set_category'], + 'rule-action-value' => ['something'], + 'rule-action-stop' => [], + 'stop_processing' => 0, + ]; + $this->call('POST', '/rules/store/1', $args); + + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\RuleController::up - * @todo Implement testUp(). */ public function testUp() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + + $this->be($this->user()); + $this->call('GET', '/rules/rules/up/1'); + $this->assertResponseStatus(302); } /** * @covers FireflyIII\Http\Controllers\RuleController::update - * @todo Implement testUpdate(). + * @covers FireflyIII\Http\Requests\RuleFormRequest::authorize + * @covers FireflyIII\Http\Requests\RuleFormRequest::rules */ public function testUpdate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule.edit.url' => 'http://localhost']); + + $this->be($this->user()); + $args = [ + 'title' => 'Some new rule update', + 'rule_group_id' => 1, + 'id' => 1, + 'active' => 1, + 'trigger' => 'store-journal', + 'description' => 'Some new rule', + 'rule-trigger' => ['description_is'], + 'rule-trigger-value' => ['something'], + 'rule-trigger-stop' => [], + 'rule-action' => ['set_category'], + 'rule-action-value' => ['something'], + 'rule-action-stop' => [], + 'stop_processing' => 0, + ]; + $this->call('POST', '/rules/update/1', $args); + $this->assertSessionHas('success'); + $this->assertResponseStatus(302); } } diff --git a/tests/acceptance/Controllers/RuleGroupControllerTest.php b/tests/acceptance/Controllers/RuleGroupControllerTest.php index f23e52417c..838b110815 100644 --- a/tests/acceptance/Controllers/RuleGroupControllerTest.php +++ b/tests/acceptance/Controllers/RuleGroupControllerTest.php @@ -16,97 +16,105 @@ class RuleGroupControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\RuleGroupController::create - * @todo Implement testCreate(). + * @covers FireflyIII\Http\Controllers\RuleGroupController::__construct */ public function testCreate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/groups/create'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::delete - * @todo Implement testDelete(). */ public function testDelete() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/groups/delete/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::destroy - * @todo Implement testDestroy(). */ public function testDestroy() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule-group.delete.url' => 'http://localhost']); + + $this->be($this->user()); + $this->call('POST', '/rules/groups/destroy/1'); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::down - * @todo Implement testDown(). */ public function testDown() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/groups/down/1'); + $this->assertResponseStatus(302); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::edit - * @todo Implement testEdit(). */ public function testEdit() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/groups/edit/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::store - * @todo Implement testStore(). + * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::authorize + * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::rules */ public function testStore() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule-group.create.url' => 'http://localhost']); + $args = [ + 'title' => 'Some new rule group', + 'description' => 'New rules', + ]; + + $this->be($this->user()); + $this->call('POST', '/rules/groups/store', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::up - * @todo Implement testUp(). */ public function testUp() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/rules/groups/up/1'); + $this->assertResponseStatus(302); } /** * @covers FireflyIII\Http\Controllers\RuleGroupController::update - * @todo Implement testUpdate(). + * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::authorize + * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::rules */ public function testUpdate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['rules.rule-group.edit.url' => 'http://localhost']); + $args = [ + 'id' => 1, + 'title' => 'Some new rule group X', + 'description' => 'New rules', + 'active' => 1, + ]; + + $this->be($this->user()); + $this->call('POST', '/rules/groups/update/1', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } } diff --git a/tests/acceptance/Controllers/SearchControllerTest.php b/tests/acceptance/Controllers/SearchControllerTest.php index 70f44bc0bc..9e94467fa6 100644 --- a/tests/acceptance/Controllers/SearchControllerTest.php +++ b/tests/acceptance/Controllers/SearchControllerTest.php @@ -6,6 +6,7 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +use Illuminate\Support\Collection; /** @@ -14,14 +15,20 @@ class SearchControllerTest extends TestCase { /** + * @covers FireflyIII\Http\Controllers\SearchController::__construct * @covers FireflyIII\Http\Controllers\SearchController::index - * @todo Implement testIndex(). */ public function testIndex() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $searcher = $this->mock('FireflyIII\Support\Search\SearchInterface'); + $searcher->shouldReceive('searchTransactions')->once()->with(['test'])->andReturn(new Collection); + $searcher->shouldReceive('searchAccounts')->once()->with(['test'])->andReturn(new Collection); + $searcher->shouldReceive('searchCategories')->once()->with(['test'])->andReturn(new Collection); + $searcher->shouldReceive('searchBudgets')->once()->with(['test'])->andReturn(new Collection); + $searcher->shouldReceive('searchTags')->once()->with(['test'])->andReturn(new Collection); + + $this->be($this->user()); + $this->call('GET', '/search?q=test&search='); + $this->assertResponseStatus(200); } } diff --git a/tests/acceptance/Controllers/TagControllerTest.php b/tests/acceptance/Controllers/TagControllerTest.php index 8aa3cad951..d323a08669 100644 --- a/tests/acceptance/Controllers/TagControllerTest.php +++ b/tests/acceptance/Controllers/TagControllerTest.php @@ -15,110 +15,115 @@ class TagControllerTest extends TestCase { /** + * @covers FireflyIII\Http\Controllers\TagController::__construct * @covers FireflyIII\Http\Controllers\TagController::create - * @todo Implement testCreate(). */ public function testCreate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/tags/create'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::delete - * @todo Implement testDelete(). */ public function testDelete() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/tags/delete/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::destroy - * @todo Implement testDestroy(). */ public function testDestroy() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('POST', '/tags/destroy/1'); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\TagController::edit - * @todo Implement testEdit(). */ public function testEdit() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/tags/edit/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::hideTagHelp - * @todo Implement testHideTagHelp(). */ public function testHideTagHelp() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('POST', '/tags/hideTagHelp/true'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::index - * @todo Implement testIndex(). */ public function testIndex() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/tags'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::show - * @todo Implement testShow(). */ public function testShow() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/tags/show/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TagController::store - * @todo Implement testStore(). + * @covers FireflyIII\Http\Requests\TagFormRequest::authorize + * @covers FireflyIII\Http\Requests\TagFormRequest::rules */ public function testStore() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'tag' => 'Some new tag', + 'tagMode' => 'nothing', + ]; + + $this->session(['tags.create.url' => 'http://localhost']); + + $this->be($this->user()); + $this->call('POST', '/tags/store', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\TagController::update - * @todo Implement testUpdate(). + * @covers FireflyIII\Http\Requests\TagFormRequest::authorize + * @covers FireflyIII\Http\Requests\TagFormRequest::rules */ public function testUpdate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'tag' => 'Some new tag yay', + 'id' => 1, + 'tagMode' => 'nothing', + ]; + + $this->session(['tags.edit.url' => 'http://localhost']); + + $this->be($this->user()); + $this->call('POST', '/tags/update/1', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } } diff --git a/tests/acceptance/Controllers/TransactionControllerTest.php b/tests/acceptance/Controllers/TransactionControllerTest.php index 937acaccb8..83613b9fdb 100644 --- a/tests/acceptance/Controllers/TransactionControllerTest.php +++ b/tests/acceptance/Controllers/TransactionControllerTest.php @@ -6,6 +6,7 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +use Illuminate\Pagination\LengthAwarePaginator; /** @@ -16,109 +17,141 @@ class TransactionControllerTest extends TestCase /** * @covers FireflyIII\Http\Controllers\TransactionController::create - * @todo Implement testCreate(). + * @covers FireflyIII\Http\Controllers\TransactionController::__construct */ public function testCreate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/transactions/create/withdrawal'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TransactionController::delete - * @todo Implement testDelete(). */ public function testDelete() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/transaction/delete/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TransactionController::destroy - * @todo Implement testDestroy(). */ public function testDestroy() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['transactions.delete.url' => 'http://localhost']); + + $this->be($this->user()); + $this->call('POST', '/transaction/destroy/1'); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\TransactionController::edit - * @todo Implement testEdit(). */ public function testEdit() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->call('GET', '/transaction/edit/1'); + $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\TransactionController::index - * @todo Implement testIndex(). + * @covers FireflyIII\Http\Controllers\TransactionController::index + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testIndex() + public function testIndex($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $journals = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); + $journals->shouldReceive('getJournalsOfTypes')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); + + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/transactions/deposit'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TransactionController::reorder - * @todo Implement testReorder(). */ public function testReorder() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $args = [ + 'ids' => [1], + 'date' => '2015-01-01', + ]; + $this->be($this->user()); + $this->call('POST', '/transaction/reorder', $args); + $this->assertResponseStatus(200); } /** - * @covers FireflyIII\Http\Controllers\TransactionController::show - * @todo Implement testShow(). + * @covers FireflyIII\Http\Controllers\TransactionController::show + * @dataProvider dateRangeProvider + * + * @param $range */ - public function testShow() + public function testShow($range) { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $this->call('GET', '/transaction/show/1'); + $this->assertResponseStatus(200); } /** * @covers FireflyIII\Http\Controllers\TransactionController::store - * @todo Implement testStore(). + * @covers FireflyIII\Http\Requests\JournalFormRequest::authorize + * @covers FireflyIII\Http\Requests\JournalFormRequest::rules + * @covers FireflyIII\Http\Requests\JournalFormRequest::getJournalData */ public function testStore() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['transactions.create.url' => 'http://localhost']); + + $args = [ + 'what' => 'withdrawal', + 'description' => 'Something', + 'account_id' => '1', + 'expense_account' => 'Some expense', + 'amount' => 100, + 'amount_currency_id_amount' => 1, + 'date' => '2015-01-01', + ]; + $this->be($this->user()); + $this->call('POST', '/transactions/store/withdrawal', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } /** * @covers FireflyIII\Http\Controllers\TransactionController::update - * @todo Implement testUpdate(). + * @covers FireflyIII\Http\Requests\JournalFormRequest::authorize + * @covers FireflyIII\Http\Requests\JournalFormRequest::rules + * @covers FireflyIII\Http\Requests\JournalFormRequest::getJournalData */ public function testUpdate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + $this->session(['transactions.edit.url' => 'http://localhost']); + + $args = [ + 'what' => 'withdrawal', + 'id' => 2, + 'description' => 'Something new', + 'account_id' => '1', + 'expense_account' => 'Some expense', + 'amount' => 100, + 'amount_currency_id_amount' => 1, + 'date' => '2015-01-01', + ]; + $this->be($this->user()); + $this->call('POST', '/transaction/update/1', $args); + $this->assertResponseStatus(302); + $this->assertSessionHas('success'); } }