diff --git a/.env.example b/.env.example index eaa69b5d82..9a28c9f373 100755 --- a/.env.example +++ b/.env.example @@ -41,6 +41,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false CACHE_PREFIX=firefly +EXCHANGE_RATE_SERVICE=fixerio + GOOGLE_MAPS_API_KEY= ANALYTICS_ID= SITE_OWNER=mail@example.com diff --git a/.env.testing b/.env.testing index 3c9802e829..2cafd4c2f8 100755 --- a/.env.testing +++ b/.env.testing @@ -3,6 +3,7 @@ APP_DEBUG=true APP_FORCE_SSL=false APP_FORCE_ROOT= APP_KEY=TestTestTestTestTestTestTestTest +APP_LOG=daily APP_LOG_LEVEL=debug APP_URL=http://localhost @@ -25,7 +26,7 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 -MAIL_DRIVER=smtp +MAIL_DRIVER=log MAIL_HOST=mailtrap.io MAIL_PORT=2525 MAIL_FROM=changeme@example.com diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 612661a00f..6df27b4876 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -17,3 +17,7 @@ Take the time to read the [installation guide FAQ](https://firefly-iii.github.io ## Pull requests I can only accept pull requests against the `develop` branch, never the `master` branch. + +## Translations :us: :fr: :de: + +If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b366227eb..b1e27bfc74 100755 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,7 @@ /node_modules /public/storage /vendor -/.idea Homestead.json Homestead.yaml .env -_development -.env.local -result.html -test-import.sh -test-import-report.txt public/google*.html -.env.backup diff --git a/.sandstorm/sandstorm-files.list b/.sandstorm/sandstorm-files.list index 91b1256d53..0c097bee32 100644 --- a/.sandstorm/sandstorm-files.list +++ b/.sandstorm/sandstorm-files.list @@ -232,27 +232,52 @@ opt/app/app/Console/Kernel.php opt/app/app/Exceptions/Handler.php opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php opt/app/app/Generator/Chart/Basic/GeneratorInterface.php +opt/app/app/Generator/Report/ReportGeneratorFactory.php +opt/app/app/Generator/Report/ReportGeneratorInterface.php +opt/app/app/Generator/Report/Standard/MonthReportGenerator.php opt/app/app/Helpers/Attachments/AttachmentHelper.php opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php +opt/app/app/Helpers/Collection/Balance.php +opt/app/app/Helpers/Collection/BalanceEntry.php +opt/app/app/Helpers/Collection/BalanceHeader.php +opt/app/app/Helpers/Collection/BalanceLine.php +opt/app/app/Helpers/Collection/Bill.php opt/app/app/Helpers/Collector/JournalCollector.php opt/app/app/Helpers/Collector/JournalCollectorInterface.php opt/app/app/Helpers/FiscalHelper.php opt/app/app/Helpers/FiscalHelperInterface.php +opt/app/app/Helpers/Report/BalanceReportHelper.php +opt/app/app/Helpers/Report/BalanceReportHelperInterface.php +opt/app/app/Helpers/Report/BudgetReportHelper.php +opt/app/app/Helpers/Report/BudgetReportHelperInterface.php opt/app/app/Helpers/Report/ReportHelper.php opt/app/app/Helpers/Report/ReportHelperInterface.php +opt/app/app/Http/Controllers/AccountController.php opt/app/app/Http/Controllers/Auth/LoginController.php +opt/app/app/Http/Controllers/BillController.php opt/app/app/Http/Controllers/BudgetController.php +opt/app/app/Http/Controllers/CategoryController.php opt/app/app/Http/Controllers/Chart/AccountController.php opt/app/app/Http/Controllers/Chart/BudgetController.php opt/app/app/Http/Controllers/Chart/CategoryController.php opt/app/app/Http/Controllers/Controller.php opt/app/app/Http/Controllers/HomeController.php +opt/app/app/Http/Controllers/ImportController.php opt/app/app/Http/Controllers/JavascriptController.php opt/app/app/Http/Controllers/JsonController.php opt/app/app/Http/Controllers/NewUserController.php +opt/app/app/Http/Controllers/PiggyBankController.php opt/app/app/Http/Controllers/ProfileController.php +opt/app/app/Http/Controllers/Report/AccountController.php +opt/app/app/Http/Controllers/Report/BalanceController.php +opt/app/app/Http/Controllers/Report/BudgetController.php +opt/app/app/Http/Controllers/Report/CategoryController.php +opt/app/app/Http/Controllers/Report/OperationsController.php opt/app/app/Http/Controllers/ReportController.php +opt/app/app/Http/Controllers/RuleController.php +opt/app/app/Http/Controllers/TagController.php opt/app/app/Http/Controllers/Transaction/SingleController.php +opt/app/app/Http/Controllers/TransactionController.php opt/app/app/Http/Kernel.php opt/app/app/Http/Middleware/Authenticate.php opt/app/app/Http/Middleware/AuthenticateTwoFactor.php @@ -263,10 +288,16 @@ opt/app/app/Http/Middleware/RedirectIfAuthenticated.php opt/app/app/Http/Middleware/Sandstorm.php opt/app/app/Http/Middleware/StartFireflySession.php opt/app/app/Http/Middleware/VerifyCsrfToken.php +opt/app/app/Http/Requests/BudgetFormRequest.php +opt/app/app/Http/Requests/BudgetIncomeRequest.php +opt/app/app/Http/Requests/CategoryFormRequest.php opt/app/app/Http/Requests/JournalFormRequest.php opt/app/app/Http/Requests/NewUserFormRequest.php +opt/app/app/Http/Requests/PiggyBankFormRequest.php opt/app/app/Http/Requests/ProfileFormRequest.php +opt/app/app/Http/Requests/ReportFormRequest.php opt/app/app/Http/Requests/Request.php +opt/app/app/Http/Requests/TagFormRequest.php opt/app/app/Http/breadcrumbs.php opt/app/app/Jobs/Job.php opt/app/app/Jobs/MailError.php @@ -279,9 +310,15 @@ opt/app/app/Models/Budget.php opt/app/app/Models/BudgetLimit.php opt/app/app/Models/Category.php opt/app/app/Models/Configuration.php +opt/app/app/Models/Note.php opt/app/app/Models/PiggyBank.php +opt/app/app/Models/PiggyBankRepetition.php opt/app/app/Models/Preference.php opt/app/app/Models/Role.php +opt/app/app/Models/Rule.php +opt/app/app/Models/RuleAction.php +opt/app/app/Models/RuleGroup.php +opt/app/app/Models/RuleTrigger.php opt/app/app/Models/Tag.php opt/app/app/Models/Transaction.php opt/app/app/Models/TransactionCurrency.php @@ -317,14 +354,24 @@ opt/app/app/Repositories/Budget/BudgetRepository.php opt/app/app/Repositories/Budget/BudgetRepositoryInterface.php opt/app/app/Repositories/Category/CategoryRepository.php opt/app/app/Repositories/Category/CategoryRepositoryInterface.php +opt/app/app/Repositories/Currency/CurrencyRepository.php +opt/app/app/Repositories/Currency/CurrencyRepositoryInterface.php opt/app/app/Repositories/Journal/JournalRepository.php opt/app/app/Repositories/Journal/JournalRepositoryInterface.php opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +opt/app/app/Repositories/Rule/RuleRepository.php +opt/app/app/Repositories/Rule/RuleRepositoryInterface.php +opt/app/app/Repositories/RuleGroup/RuleGroupRepository.php +opt/app/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php opt/app/app/Repositories/Tag/TagRepository.php opt/app/app/Repositories/Tag/TagRepositoryInterface.php +opt/app/app/Repositories/User/UserRepository.php opt/app/app/Repositories/User/UserRepositoryInterface.php opt/app/app/Support/Amount.php +opt/app/app/Support/Binder/AccountList.php +opt/app/app/Support/Binder/BinderInterface.php +opt/app/app/Support/Binder/Date.php opt/app/app/Support/CacheProperties.php opt/app/app/Support/Domain.php opt/app/app/Support/ExpandedForm.php @@ -386,25 +433,40 @@ opt/app/database/seeds/PermissionSeeder.php opt/app/database/seeds/TransactionCurrencySeeder.php opt/app/database/seeds/TransactionTypeSeeder.php opt/app/public/css/bootstrap-multiselect.css +opt/app/public/css/bootstrap-sortable.css opt/app/public/css/bootstrap-tagsinput.css opt/app/public/css/bootstrap-tour.min.css opt/app/public/css/daterangepicker.css opt/app/public/css/firefly.css opt/app/public/css/jquery-ui/jquery-ui.structure.min.css opt/app/public/css/jquery-ui/jquery-ui.theme.min.css +opt/app/public/favicon-16x16.png +opt/app/public/favicon-32x32.png opt/app/public/index.php +opt/app/public/js/ff/accounts/edit.js +opt/app/public/js/ff/bills/create.js opt/app/public/js/ff/budgets/index.js +opt/app/public/js/ff/categories/index.js opt/app/public/js/ff/charts.defaults.js opt/app/public/js/ff/charts.js opt/app/public/js/ff/firefly.js opt/app/public/js/ff/guest.js opt/app/public/js/ff/help.js opt/app/public/js/ff/index.js +opt/app/public/js/ff/piggy-banks/create.js +opt/app/public/js/ff/piggy-banks/index.js +opt/app/public/js/ff/reports/default/all.js +opt/app/public/js/ff/reports/default/month.js opt/app/public/js/ff/reports/index.js +opt/app/public/js/ff/rules/index.js +opt/app/public/js/ff/tags/create-edit.js +opt/app/public/js/ff/tags/index.js +opt/app/public/js/ff/transactions/list.js opt/app/public/js/ff/transactions/single/create.js opt/app/public/js/lib/Chart.bundle.min.js opt/app/public/js/lib/accounting.min.js opt/app/public/js/lib/bootstrap-multiselect.js +opt/app/public/js/lib/bootstrap-sortable.js opt/app/public/js/lib/bootstrap-tagsinput.min.js opt/app/public/js/lib/bootstrap-tour.min.js opt/app/public/js/lib/bootstrap3-typeahead.min.js @@ -417,6 +479,7 @@ opt/app/public/lib/adminlte/css/AdminLTE.min.css opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css opt/app/public/lib/adminlte/js/app.min.js opt/app/public/lib/bootstrap/css/bootstrap.min.css +opt/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 opt/app/public/lib/bootstrap/js/bootstrap.min.js opt/app/public/lib/font-awesome/css/font-awesome.min.css opt/app/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 @@ -425,9 +488,19 @@ opt/app/resources/lang/en_US/config.php opt/app/resources/lang/en_US/firefly.php opt/app/resources/lang/en_US/form.php opt/app/resources/lang/en_US/help.php +opt/app/resources/lang/en_US/list.php opt/app/resources/lang/en_US/validation.php +opt/app/resources/views/accounts/delete.twig +opt/app/resources/views/accounts/edit.twig +opt/app/resources/views/accounts/index.twig opt/app/resources/views/auth/login.twig +opt/app/resources/views/bills/create.twig +opt/app/resources/views/bills/index.twig +opt/app/resources/views/budgets/create.twig +opt/app/resources/views/budgets/income.twig opt/app/resources/views/budgets/index.twig +opt/app/resources/views/categories/create.twig +opt/app/resources/views/categories/index.twig opt/app/resources/views/emails/error-html.twig opt/app/resources/views/emails/error-text.twig opt/app/resources/views/emails/footer-html.twig @@ -437,18 +510,31 @@ opt/app/resources/views/emails/header-text.twig opt/app/resources/views/error.twig opt/app/resources/views/form/amount.twig opt/app/resources/views/form/balance.twig +opt/app/resources/views/form/checkbox.twig opt/app/resources/views/form/date.twig opt/app/resources/views/form/feedback.twig +opt/app/resources/views/form/file.twig opt/app/resources/views/form/help.twig +opt/app/resources/views/form/integer.twig +opt/app/resources/views/form/location.twig +opt/app/resources/views/form/multiRadio.twig opt/app/resources/views/form/options.twig opt/app/resources/views/form/select.twig +opt/app/resources/views/form/tags.twig opt/app/resources/views/form/text.twig +opt/app/resources/views/form/textarea.twig +opt/app/resources/views/import/index.twig opt/app/resources/views/index.twig opt/app/resources/views/javascript/variables.twig opt/app/resources/views/json/tour.twig opt/app/resources/views/layout/default.twig opt/app/resources/views/layout/guest.twig +opt/app/resources/views/list/accounts.twig +opt/app/resources/views/list/bills.twig +opt/app/resources/views/list/categories.twig +opt/app/resources/views/list/journals-tasker.twig opt/app/resources/views/list/journals-tiny-tasker.twig +opt/app/resources/views/list/piggy-banks.twig opt/app/resources/views/new-user/index.twig opt/app/resources/views/partials/boxes.twig opt/app/resources/views/partials/control-bar.twig @@ -456,11 +542,25 @@ opt/app/resources/views/partials/favicons.twig opt/app/resources/views/partials/flashes.twig opt/app/resources/views/partials/menu-sidebar.twig opt/app/resources/views/partials/page-header.twig +opt/app/resources/views/piggy-banks/create.twig +opt/app/resources/views/piggy-banks/index.twig opt/app/resources/views/profile/change-password.twig opt/app/resources/views/profile/delete-account.twig opt/app/resources/views/profile/index.twig +opt/app/resources/views/reports/default/month.twig opt/app/resources/views/reports/index.twig opt/app/resources/views/reports/options/no-options.twig +opt/app/resources/views/reports/partials/accounts.twig +opt/app/resources/views/reports/partials/balance.twig +opt/app/resources/views/reports/partials/bills.twig +opt/app/resources/views/reports/partials/budgets.twig +opt/app/resources/views/reports/partials/categories.twig +opt/app/resources/views/reports/partials/income-expenses.twig +opt/app/resources/views/reports/partials/operations.twig +opt/app/resources/views/rules/index.twig +opt/app/resources/views/tags/create.twig +opt/app/resources/views/tags/index.twig +opt/app/resources/views/transactions/index.twig opt/app/resources/views/transactions/single/create.twig opt/app/routes/api.php opt/app/routes/console.php @@ -612,6 +712,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Http/Kernel.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php +opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/LengthAwarePaginator.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php @@ -682,6 +783,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Conc opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php +opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php @@ -808,8 +911,11 @@ opt/app/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php opt/app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php +opt/app/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php +opt/app/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php +opt/app/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/default.blade.php opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php @@ -884,12 +990,14 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php +opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cookie.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php +opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php @@ -914,6 +1022,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php opt/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php +opt/app/vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/Translator.php opt/app/vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -1214,14 +1323,17 @@ opt/app/vendor/twig/twig/lib/Twig/Node/Expression.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Array.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php +opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php +opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php +opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Call.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index c0493e5fb6..30f3d425e1 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -16,7 +16,7 @@ const pkgdef :Spk.PackageDefinition = ( manifest = ( appTitle = (defaultText = "Firefly III"), appVersion = 1, - appMarketingVersion = (defaultText = "3.4.3"), + appMarketingVersion = (defaultText = "3.4.6"), actions = [ # Define your "new document" handlers here. ( nounPhrase = (defaultText = "administration"), @@ -97,7 +97,7 @@ const pkgdef :Spk.PackageDefinition = ( # `spk dev` will write a list of all the files your app uses to this file. # You should review it later, before shipping your app. - alwaysInclude = [], + alwaysInclude = ["app","bootstrap","config","database","public","resources","routes"], # Fill this list with more names of files or directories that should be # included in your package, even if not listed in sandstorm-files.list. # Use this to force-include stuff that you know you need but which may diff --git a/.travis.yml b/.travis.yml index df725e203a..9e633c9bb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ cache: - $HOME/.composer/cache install: - - if [[ "$(php -v | grep 'PHP 7')" ]]; then phpenv config-rm xdebug.ini; fi - rm composer.lock - composer update --no-scripts - cp .env.testing .env @@ -18,9 +17,13 @@ install: - php artisan env - cp .env.testing .env - mv storage/database/databasecopy.sqlite storage/database/database.sqlite + - mkdir -p build/logs script: - - phpunit + - phpunit -c phpunit.xml + +#after_success: +# - travis_retry php vendor/bin/coveralls -x storage/build/clover.xml # safelist branches: diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c018a7db..a64c684432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,95 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.4.3] - 2017-05-03 +### Added +- Added support for Slovenian +- Removed support for Spanish. No translations whatsoever by the guy who requested it. +- Removed support for Russian. Same thing. +- Removed support for Croatian. Same thing. +- Removed support for Chinese Traditional, Hong Kong. Same thing. + +### Changed +- The journal collector, an internal piece of code to collect transactions, now uses a slightly different method of collecting journals. This may cause problems. + +### Fixed +- Issue #638 as reported by [worldworm](https://github.com/worldworm). +- Possible fix for #624 + +## [4.4.2] - 2017-04-27 +### Fixed +- Fixed a bug where the opening balance could not be stored. + +## [4.4.1] - 2017-04-27 + +### Added +- Support for deployment on Heroku + +### Fixed +- Bug in new-user routine. + +## [4.4.0] - 2017-04-23 +### Added +- Firefly III can now handle foreign currencies better, including some code to get the exchange rate live from the web. +- Can now make rules for attachments, see #608, as suggested by dzaikos. + +### Fixed +- Fixed #629, reported by forcaeluz +- Fixed #630, reported by welbert +- And more various bug fixes. + +## [4.3.8] - 2017-04-08 + +### Added +- Better overview / show pages. +- #628, as reported by [xzaz](https://github.com/xzaz). +- Greatly expanded test coverage + +### Fixed +- #619, as reported by [dfiel](https://github.com/dfiel). +- #620, as reported by [forcaeluz](https://github.com/forcaeluz). +- Attempt to fix #624, as reported by [TheSerenin](https://github.com/TheSerenin). +- Favicon link is relative now, fixed by [welbert](https://github.com/welbert). +- Some search bugs + +## [4.3.7] - 2017-03-06 +### Added +- Nice user friendly views for empty lists. +- Extended contribution guidelines. +- First version of financial report filtered on tags. +- Suggested monthly savings for piggy banks, by [Zsub](https://github.com/Zsub) +- Better test coverage. + +### Changed +- Slightly changed tag overview. +- Consistent icon for bill in list. +- Slightly changed account overview. + +### Removed +- Removed IDE specific views from .gitignore, issue #598 + +### Fixed +- Force key generation during installation. +- The `date` function takes the fieldname where a date is stored, not the literal date by [Zsub](https://github.com/Zsub) +- Improved budget frontpage chart, as suggested by [skibbipl](https://github.com/skibbipl) +- Issue #602 and #607, as reported by [skibbipl](https://github.com/skibbipl) and [dzaikos](https://github.com/dzaikos). +- Issue #605, as reported by [Zsub](https://github.com/Zsub). +- Issue #599, as reported by [leander091](https://github.com/leander091). +- Issue #610, as reported by [skibbipl](https://github.com/skibbipl). +- Issue #611, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson). +- Issue #612, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson). +- Issue #614, as reported by [worldworm](https://github.com/worldworm). +- Various other bug fixes. + +## [4.3.6] - 2017-02-20 +### Fixed +- #578, reported by [xpfgsyb](https://github.com/xpfgsyb). + ## [4.3.5] - 2017-02-19 ### Added - Beta support for Sandstorm.IO - Docker support by [@schoentoon](https://github.com/schoentoon), [@elohmeier](https://github.com/elohmeier), [@patrickkostjens](https://github.com/patrickkostjens) and [@crash7](https://github.com/crash7)! +- Can now use special keywords in the search to search for specic dates, categories, etc. ### Changed - Updated to laravel 5.4! @@ -153,13 +238,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i - Updated all email messages. - Made some fonts local - -### Deprecated -- Initial release. - -### Removed -- Initial release. - ### Fixed - Issue #408 - Various issues with split journals @@ -168,11 +246,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i - Issue #422, thx [xzaz](https://github.com/xzaz) - Various import bugs, such as #416 ([zjean](https://github.com/zjean)) - -### Security -- Initial release. - - ## [4.1.7] - 2016-11-19 ### Added - Check for database table presence in console commands. @@ -295,15 +368,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i - New Presidents Choice specific to fix #307 - Added some trimming (#335) -### Changed -- Initial release. - -### Deprecated -- Initial release. - -### Removed -- Initial release. - ### Fixed - Fixed a bug where incoming transactions would not be properly filtered in several reports. - #334 by [cyberkov](https://github.com/cyberkov) @@ -311,12 +375,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i - #336 - #338 found by [roberthorlings](https://github.com/roberthorlings) -### Security -- Initial release. - - - - ## [4.0.0] - 2015-09-26 ### Added - Upgraded to Laravel 5.3, most other libraries upgraded as well. diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..3191e014de --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: vendor/bin/heroku-php-nginx -C nginx_app.conf public/ diff --git a/README.md b/README.md index 5013f18eab..069c7cbee8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Firefly III: A personal finances manager -[![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) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) +[![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-CC%20BY--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) [![The index of Firefly III](https://i.nder.be/hurdhgyg/400)](https://i.nder.be/h2b37243) [![The account overview of Firefly III](https://i.nder.be/hnkfkdpr/400)](https://i.nder.be/hv70pbwc) @@ -10,11 +10,15 @@ ## Try it out! -Try out Firefly III on the [demo site](https://firefly-iii.nder.be/). +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master) + +Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. + +There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present. ## Installation -To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/installation-guide/). +To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html). ## More about Firefly III @@ -34,3 +38,5 @@ Firefly is pretty awesome. [You can read more about Firefly III, and its feature If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!) If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com). + +[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master) diff --git a/app.json b/app.json new file mode 100644 index 0000000000..35680aa38b --- /dev/null +++ b/app.json @@ -0,0 +1,59 @@ +{ + "name": "Firefly III", + "description": "A free and open source personal finances manager", + "repository": "https://github.com/firefly-iii/firefly-iii", + "logo": "https://raw.githubusercontent.com/firefly-iii/firefly-iii/master/public/mstile-150x150.png", + "keywords": [ + "finance", + "finances", + "manager", + "management", + "euro", + "dollar", + "laravel", + "money", + "currency", + "financials", + "financial", + "budgets", + "administration", + "tool", + "tooling", + "help", + "helper", + "assistant", + "planning", + "organizing", + "bills", + "personal finance", + "budgets", + "budgeting", + "budgeting tool", + "budgeting application", + "transactions", + "self hosted", + "self-hosted", + "transfers", + "management" + ], + "website": "https://firefly-iii.github.io/", + "addons": [ + { + "plan": "heroku-postgresql" + } + ], + "scripts": { + "postdeploy": "export APP_KEY=$(php artisan --no-ansi key:generate --show)" + }, + "buildpacks": [ + { + "url": "heroku/php" + } + ], + "env": { + "APP_KEY": { + "description": "This key is used to encrypt your data.", + "value": "base64:If1gJN4pyycXTq+WS5TjneDympKuu+8SKvTl6RZnhJg=" + } + } +} \ No newline at end of file diff --git a/app/Bootstrap/ConfigureLogging.php b/app/Bootstrap/ConfigureLogging.php deleted file mode 100644 index 03b704d0f2..0000000000 --- a/app/Bootstrap/ConfigureLogging.php +++ /dev/null @@ -1,63 +0,0 @@ -make('config'); - - $maxFiles = $config->get('app.log_max_files'); - - $log->useDailyFiles( - $app->storagePath() . '/logs/firefly-iii.log', is_null($maxFiles) ? 5 : $maxFiles, - $config->get('app.log_level', 'debug') - ); - } - - /** - * Configure the Monolog handlers for the application. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @param \Illuminate\Log\Writer $log - * - * @return void - */ - protected function configureSingleHandler(Application $app, Writer $log) - { - $log->useFiles( - $app->storagePath() . '/logs/firefly-iii.log', - $app->make('config')->get('app.log_level', 'debug') - ); - } -} diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index 00ff5094c6..3c0d14fb18 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; @@ -81,10 +81,10 @@ class CreateImport extends Command /** @var ImportJobRepositoryInterface $jobRepository */ $jobRepository = app(ImportJobRepositoryInterface::class); $jobRepository->setUser($user); - $job = $jobRepository->create($type); + $job = $jobRepository->create($type); $this->line(sprintf('Created job "%s"...', $job->key)); - Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]); + Artisan::call('firefly:encrypt-file', ['file' => $file, 'key' => $job->key]); $this->line('Stored import data...'); $job->configuration = $configurationData; diff --git a/app/Console/Commands/EncryptFile.php b/app/Console/Commands/EncryptFile.php index 1ff271a9e5..42e79d8337 100644 --- a/app/Console/Commands/EncryptFile.php +++ b/app/Console/Commands/EncryptFile.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index c67f39000f..d2ff9b8997 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; @@ -58,7 +58,12 @@ class Import extends Command { Log::debug('Start start-import command'); $jobKey = $this->argument('key'); - $job = ImportJob::whereKey($jobKey)->first(); + $job = ImportJob::where('key', $jobKey)->first(); + if (is_null($job)) { + $this->error(sprintf('No job found with key "%s"', $jobKey)); + + return; + } if (!$this->isValid($job)) { Log::error('Job is not valid for some reason. Exit.'); diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php index 0dc03631f1..23364b129f 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/ScanAttachments.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 7b3e905b88..d313c55ec6 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -9,19 +9,28 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; use DB; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountMeta; +use FireflyIII\Models\AccountType; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Console\Command; +use Illuminate\Database\Query\JoinClause; use Illuminate\Database\QueryException; use Log; +use Preferences; use Schema; /** @@ -60,8 +69,15 @@ class UpgradeDatabase extends Command { $this->setTransactionIdentifier(); $this->migrateRepetitions(); + $this->repairPiggyBanks(); + $this->updateAccountCurrencies(); + $this->updateJournalCurrencies(); + $this->info('Firefly III database is up to date.'); } + /** + * Migrate budget repetitions to new format. + */ private function migrateRepetitions() { if (!Schema::hasTable('budget_limits')) { @@ -69,7 +85,9 @@ class UpgradeDatabase extends Command } // get all budget limits with end_date NULL $set = BudgetLimit::whereNull('end_date')->get(); - $this->line(sprintf('Found %d budget limit(s) to update', $set->count())); + if ($set->count() > 0) { + $this->line(sprintf('Found %d budget limit(s) to update', $set->count())); + } /** @var BudgetLimit $budgetLimit */ foreach ($set as $budgetLimit) { // get limit repetition (should be just one): @@ -84,6 +102,37 @@ class UpgradeDatabase extends Command } } + /** + * Make sure there are only transfers linked to piggy bank events. + */ + private function repairPiggyBanks() + { + // if table does not exist, return false + if (!Schema::hasTable('piggy_bank_events')) { + return; + } + $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); + /** @var PiggyBankEvent $event */ + foreach ($set as $event) { + + if (is_null($event->transaction_journal_id)) { + continue; + } + /** @var TransactionJournal $journal */ + $journal = $event->transactionJournal()->first(); + if (is_null($journal)) { + continue; + } + + $type = $journal->transactionType->type; + if ($type !== TransactionType::TRANSFER) { + $event->transaction_journal_id = null; + $event->save(); + $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); + } + } + } + /** * This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird. */ @@ -112,6 +161,57 @@ class UpgradeDatabase extends Command } } + /** + * + */ + private function updateAccountCurrencies() + { + $accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']); + + /** @var Account $account */ + foreach ($accounts as $account) { + // get users preference, fall back to system pref. + $defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; + $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); + $accountCurrency = intval($account->getMeta('currency_id')); + $openingBalance = $account->getOpeningBalance(); + $openingBalanceCurrency = intval($openingBalance->transaction_currency_id); + + // both 0? set to default currency: + if ($accountCurrency === 0 && $openingBalanceCurrency === 0) { + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); + continue; + } + + // opening balance 0, account not zero? just continue: + if ($accountCurrency > 0 && $openingBalanceCurrency === 0) { + continue; + } + // account is set to 0, opening balance is not? + if ($accountCurrency === 0 && $openingBalanceCurrency > 0) { + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $openingBalanceCurrency]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); + continue; + } + + // both are equal, just continue: + if ($accountCurrency === $openingBalanceCurrency) { + continue; + } + // do not match: + if ($accountCurrency !== $openingBalanceCurrency) { + // update opening balance: + $openingBalance->transaction_currency_id = $accountCurrency; + $openingBalance->save(); + $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); + continue; + } + } + + } + /** * grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one * which has 0 as an identifier and give it the same identifier. @@ -151,9 +251,87 @@ class UpgradeDatabase extends Command $opposing->save(); $processed[] = $transaction->id; $processed[] = $opposing->id; - $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id)); } $identifier++; } } + + /** + * Makes sure that withdrawals, deposits and transfers have + * a currency setting matching their respective accounts + */ + private function updateJournalCurrencies() + { + $types = [ + TransactionType::WITHDRAWAL => '<', + TransactionType::DEPOSIT => '>', + ]; + $repository = app(CurrencyRepositoryInterface::class); + $notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.'; + $transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.'; + + foreach ($types as $type => $operator) { + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin( + 'transactions', function (JoinClause $join) use ($operator) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0'); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') + ->where('transaction_types.type', $type) + ->where('account_meta.name', 'currency_id') + ->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')) + ->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']); + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $expectedCurrency = $repository->find(intval($journal->expected_currency_id)); + $line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code); + + $journal->setMeta('foreign_amount', $journal->transaction_amount); + $journal->setMeta('foreign_currency_id', $journal->transaction_currency_id); + $journal->transaction_currency_id = $expectedCurrency->id; + $journal->save(); + $this->line($line); + } + } + /* + * For transfers it's slightly different. Both source and destination + * must match the respective currency preference. So we must verify ALL + * transactions. + */ + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->where('transaction_types.type', TransactionType::TRANSFER) + ->get(['transaction_journals.*']); + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $updated = false; + /** @var Transaction $sourceTransaction */ + $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); + $sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id'))); + + if ($sourceCurrency->id !== $journal->transaction_currency_id) { + $updated = true; + $journal->transaction_currency_id = $sourceCurrency->id; + $journal->save(); + } + + // destination + $destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first(); + $destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id'))); + + if ($destinationCurrency->id !== $journal->transaction_currency_id) { + $updated = true; + $journal->deleteMeta('foreign_amount'); + $journal->deleteMeta('foreign_currency_id'); + $journal->setMeta('foreign_amount', $destinationTransaction->amount); + $journal->setMeta('foreign_currency_id', $destinationCurrency->id); + } + if ($updated) { + $line = sprintf($transfer, $journal->id); + $this->line($line); + } + } + } } diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index a88c8ab79c..f98e84f86a 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; @@ -84,21 +84,8 @@ class UpgradeFireflyInstructions extends Command } } - /** - * Show a line - */ - private function showLine() + private function installInstructions() { - $line = '+'; - for ($i = 0; $i < 78; $i++) { - $line .= '-'; - } - $line .= '+'; - $this->line($line); - - } - - private function installInstructions() { /** @var string $version */ $version = config('firefly.version'); $config = config('upgrade.text.install'); @@ -120,6 +107,7 @@ class UpgradeFireflyInstructions extends Command $this->boxed('Firefly III should be ready for use.'); $this->boxed(''); $this->showLine(); + return; } @@ -129,6 +117,20 @@ class UpgradeFireflyInstructions extends Command $this->showLine(); } + /** + * Show a line + */ + private function showLine() + { + $line = '+'; + for ($i = 0; $i < 78; $i++) { + $line .= '-'; + } + $line .= '+'; + $this->line($line); + + } + private function updateInstructions() { /** @var string $version */ @@ -152,6 +154,7 @@ class UpgradeFireflyInstructions extends Command $this->boxed('Firefly III should be ready for use.'); $this->boxed(''); $this->showLine(); + return; } diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 8c73968caa..52cf80c265 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console\Commands; @@ -93,6 +93,7 @@ class VerifyDatabase extends Command // report on journals with the wrong types of accounts. $this->reportIncorrectJournals(); + } /** @@ -131,7 +132,7 @@ class VerifyDatabase extends Command /** @var Budget $entry */ foreach ($set as $entry) { $line = sprintf( - 'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.', + 'User #%d (%s) has budget #%d ("%s") which has no budget limits.', $entry->user_id, $entry->email, $entry->id, $entry->name ); $this->line($line); @@ -277,7 +278,7 @@ class VerifyDatabase extends Command } $line = sprintf( - 'Notice: User #%d (%s) has %s #%d ("%s") which has no transactions.', + 'User #%d (%s) has %s #%d ("%s") which has no transactions.', $entry->user_id, $entry->email, $name, $entry->id, $objName ); $this->line($line); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index f4a1e32559..ea6b8598ba 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Console; @@ -41,7 +41,6 @@ class Kernel extends ConsoleKernel = [ 'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables', 'Illuminate\Foundation\Bootstrap\LoadConfiguration', - //'FireflyIII\Bootstrap\ConfigureLogging', 'Illuminate\Foundation\Bootstrap\HandleExceptions', 'Illuminate\Foundation\Bootstrap\RegisterFacades', 'Illuminate\Foundation\Bootstrap\SetRequestForConsole', diff --git a/app/Events/Event.php b/app/Events/Event.php index feaaa07846..c80c8ea3b0 100644 --- a/app/Events/Event.php +++ b/app/Events/Event.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Events; /** diff --git a/app/Events/RegisteredUser.php b/app/Events/RegisteredUser.php index 3afc5e05e0..034430e8f2 100644 --- a/app/Events/RegisteredUser.php +++ b/app/Events/RegisteredUser.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Events; diff --git a/app/Events/RequestedNewPassword.php b/app/Events/RequestedNewPassword.php index 52919a646b..c519add49e 100644 --- a/app/Events/RequestedNewPassword.php +++ b/app/Events/RequestedNewPassword.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Events; diff --git a/app/Events/StoredTransactionJournal.php b/app/Events/StoredTransactionJournal.php index 1bc73c5f7a..d1d6805cd2 100644 --- a/app/Events/StoredTransactionJournal.php +++ b/app/Events/StoredTransactionJournal.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Events; @@ -26,7 +26,9 @@ class StoredTransactionJournal extends Event use SerializesModels; + /** @var TransactionJournal */ public $journal; + /** @var int */ public $piggyBankId; /** diff --git a/app/Events/UpdatedTransactionJournal.php b/app/Events/UpdatedTransactionJournal.php index 985aa44fd7..ce21810bc2 100644 --- a/app/Events/UpdatedTransactionJournal.php +++ b/app/Events/UpdatedTransactionJournal.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Events; @@ -26,6 +26,7 @@ class UpdatedTransactionJournal extends Event use SerializesModels; + /** @var TransactionJournal */ public $journal; /** diff --git a/app/Exceptions/FireflyException.php b/app/Exceptions/FireflyException.php index 7b288959a2..37ba66d3db 100644 --- a/app/Exceptions/FireflyException.php +++ b/app/Exceptions/FireflyException.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Exceptions; diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 00b359d148..14cb6b0df5 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Exceptions; use ErrorException; @@ -97,6 +98,7 @@ class Handler extends ExceptionHandler 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'code' => $exception->getCode(), + 'version' => config('firefly.version'), ]; // create job that will mail. diff --git a/app/Exceptions/NotImplementedException.php b/app/Exceptions/NotImplementedException.php index 406989bf09..1c31424dd9 100644 --- a/app/Exceptions/NotImplementedException.php +++ b/app/Exceptions/NotImplementedException.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Exceptions; diff --git a/app/Exceptions/ValidationException.php b/app/Exceptions/ValidationException.php index 5de31b4fdd..22fc7ce1d8 100644 --- a/app/Exceptions/ValidationException.php +++ b/app/Exceptions/ValidationException.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Exceptions; /** diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php index 810dc1db85..e3b8f295e9 100644 --- a/app/Export/Collector/AttachmentCollector.php +++ b/app/Export/Collector/AttachmentCollector.php @@ -9,14 +9,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Collector; use Carbon\Carbon; use Crypt; use FireflyIII\Models\Attachment; -use FireflyIII\Models\ExportJob; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Collection; diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php index c80e8ec280..1780f43246 100644 --- a/app/Export/Collector/BasicCollector.php +++ b/app/Export/Collector/BasicCollector.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Collector; diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php index 54a3fa88a1..fbe25edb72 100644 --- a/app/Export/Collector/CollectorInterface.php +++ b/app/Export/Collector/CollectorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Collector; @@ -33,13 +33,6 @@ interface CollectorInterface */ public function run(): bool; - /** - * @param ExportJob $job - * - * @return mixed - */ - public function setJob(ExportJob $job); - /** * @param Collection $entries * @@ -48,4 +41,11 @@ interface CollectorInterface */ public function setEntries(Collection $entries); + /** + * @param ExportJob $job + * + * @return mixed + */ + public function setJob(ExportJob $job); + } diff --git a/app/Export/Collector/JournalExportCollector.php b/app/Export/Collector/JournalExportCollector.php index e9bfbca879..175b405f79 100644 --- a/app/Export/Collector/JournalExportCollector.php +++ b/app/Export/Collector/JournalExportCollector.php @@ -9,16 +9,16 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Collector; use Carbon\Carbon; -use Crypt; use DB; use FireflyIII\Models\Transaction; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Steam; /** * Class JournalExportCollector @@ -118,7 +118,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac ); $set->each( function ($obj) { - $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + $obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name); } ); $array = []; @@ -159,7 +159,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac ); $set->each( function ($obj) { - $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + $obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name); } ); $array = []; @@ -202,7 +202,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac ); $set->each( function ($obj) { - $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + $obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name); } ); $array = []; @@ -243,7 +243,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac ); $set->each( function ($obj) { - $obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name; + $obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name); } ); $array = []; diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php index 35c2e75188..17f863e721 100644 --- a/app/Export/Collector/UploadCollector.php +++ b/app/Export/Collector/UploadCollector.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Collector; diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index 0afc40ada4..f4061f703b 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -9,11 +9,11 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Entry; -use Crypt; +use Steam; /** * To extend the exported object, in case of new features in Firefly III for example, @@ -73,15 +73,15 @@ final class Entry { $entry = new self; $entry->journal_id = $object->transaction_journal_id; - $entry->description = self::decrypt(intval($object->journal_encrypted), $object->journal_description); + $entry->description = Steam::decrypt(intval($object->journal_encrypted), $object->journal_description); $entry->amount = $object->amount; $entry->date = $object->date; $entry->transaction_type = $object->transaction_type; $entry->currency_code = $object->transaction_currency_code; $entry->source_account_id = $object->account_id; - $entry->source_account_name = self::decrypt(intval($object->account_name_encrypted), $object->account_name); + $entry->source_account_name = Steam::decrypt(intval($object->account_name_encrypted), $object->account_name); $entry->destination_account_id = $object->opposing_account_id; - $entry->destination_account_name = self::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name); + $entry->destination_account_name = Steam::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name); $entry->category_id = $object->category_id ?? ''; $entry->category_name = $object->category_name ?? ''; $entry->budget_id = $object->budget_id ?? ''; @@ -95,19 +95,5 @@ final class Entry return $entry; } - /** - * @param int $isEncrypted - * @param $value - * - * @return string - */ - protected static function decrypt(int $isEncrypted, $value) - { - if ($isEncrypted === 1) { - return Crypt::decrypt($value); - } - - return $value; - } } diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php index 256fa3b544..a2ce5a32a0 100644 --- a/app/Export/Exporter/BasicExporter.php +++ b/app/Export/Exporter/BasicExporter.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Exporter; diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php index 200bf8116d..34a56182e0 100644 --- a/app/Export/Exporter/CsvExporter.php +++ b/app/Export/Exporter/CsvExporter.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Exporter; diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php index 9e267b4812..c72f8de3ef 100644 --- a/app/Export/Exporter/ExporterInterface.php +++ b/app/Export/Exporter/ExporterInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export\Exporter; diff --git a/app/Export/Processor.php b/app/Export/Processor.php index cbbdd2736b..8df3a93dce 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export; diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php index 540dbcaf37..137c8e4c8a 100644 --- a/app/Export/ProcessorInterface.php +++ b/app/Export/ProcessorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Export; diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index b7588d2da2..b63f9e9c01 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Chart\Basic; @@ -81,6 +81,9 @@ class ChartJsGenerator implements GeneratorInterface if (isset($set['fill'])) { $currentSet['fill'] = $set['fill']; } + if (isset($set['currency_symbol'])) { + $currentSet['currency_symbol'] = $set['currency_symbol']; + } $chartData['datasets'][] = $currentSet; } @@ -105,6 +108,10 @@ class ChartJsGenerator implements GeneratorInterface ], 'labels' => [], ]; + + // sort by value, keep keys. + asort($data); + $index = 0; foreach ($data as $key => $value) { diff --git a/app/Generator/Chart/Basic/GeneratorInterface.php b/app/Generator/Chart/Basic/GeneratorInterface.php index 8b4b90af8a..92b1b29d93 100644 --- a/app/Generator/Chart/Basic/GeneratorInterface.php +++ b/app/Generator/Chart/Basic/GeneratorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Chart\Basic; diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index 25f255f3a7..0b12e60fe0 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Audit; diff --git a/app/Generator/Report/Audit/MultiYearReportGenerator.php b/app/Generator/Report/Audit/MultiYearReportGenerator.php index f8baa6e66f..f2313d4114 100644 --- a/app/Generator/Report/Audit/MultiYearReportGenerator.php +++ b/app/Generator/Report/Audit/MultiYearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Audit; diff --git a/app/Generator/Report/Audit/YearReportGenerator.php b/app/Generator/Report/Audit/YearReportGenerator.php index b44bae8820..aa730a1d0f 100644 --- a/app/Generator/Report/Audit/YearReportGenerator.php +++ b/app/Generator/Report/Audit/YearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Audit; diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 87557c8682..07d34f9524 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Budget; @@ -18,6 +18,9 @@ use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; @@ -141,52 +144,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this; } - /** - * @param Collection $collection - * @param int $sortFlag - * - * @return array - */ - private function getAverages(Collection $collection, int $sortFlag): array - { - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - // opposing name and ID: - $opposingId = $transaction->opposing_account_id; - - // is not set? - if (!isset($result[$opposingId])) { - $name = $transaction->opposing_account_name; - $result[$opposingId] = [ - 'name' => $name, - 'count' => 1, - 'id' => $opposingId, - 'average' => $transaction->transaction_amount, - 'sum' => $transaction->transaction_amount, - ]; - continue; - } - $result[$opposingId]['count']++; - $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); - $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count'])); - } - - // sort result by average: - $average = []; - foreach ($result as $key => $row) { - $average[$key] = floatval($row['average']); - } - - array_multisort($average, $sortFlag, $result); - - return $result; - } - /** * @return Collection */ - private function getExpenses(): Collection + protected function getExpenses(): Collection { if ($this->expenses->count() > 0) { Log::debug('Return previous set of expenses.'); @@ -198,44 +159,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $collector = app(JournalCollectorInterface::class); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL]) - ->setBudgets($this->budgets)->withOpposingAccount()->disableFilter(); + ->setBudgets($this->budgets)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); - $accountIds = $this->accounts->pluck('id')->toArray(); $transactions = $collector->getJournals(); - $transactions = self::filterExpenses($transactions, $accountIds); $this->expenses = $transactions; return $transactions; } - /** - * @return Collection - */ - private function getTopExpenses(): Collection - { - $transactions = $this->getExpenses()->sortBy('transaction_amount'); - - return $transactions; - } - - /** - * @param Collection $collection - * - * @return array - */ - private function summarizeByAccount(Collection $collection): array - { - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $accountId = $transaction->account_id; - $result[$accountId] = $result[$accountId] ?? '0'; - $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); - } - - return $result; - } - /** * @param Collection $collection * diff --git a/app/Generator/Report/Budget/MultiYearReportGenerator.php b/app/Generator/Report/Budget/MultiYearReportGenerator.php index b3820f88dd..cea5926ce2 100644 --- a/app/Generator/Report/Budget/MultiYearReportGenerator.php +++ b/app/Generator/Report/Budget/MultiYearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Budget; diff --git a/app/Generator/Report/Budget/YearReportGenerator.php b/app/Generator/Report/Budget/YearReportGenerator.php index 30f51e599c..38cd127d00 100644 --- a/app/Generator/Report/Budget/YearReportGenerator.php +++ b/app/Generator/Report/Budget/YearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Budget; diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index 0a3c60be3a..654d819417 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Category; @@ -18,6 +18,10 @@ use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; @@ -151,52 +155,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this; } - /** - * @param Collection $collection - * @param int $sortFlag - * - * @return array - */ - private function getAverages(Collection $collection, int $sortFlag): array - { - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - // opposing name and ID: - $opposingId = $transaction->opposing_account_id; - - // is not set? - if (!isset($result[$opposingId])) { - $name = $transaction->opposing_account_name; - $result[$opposingId] = [ - 'name' => $name, - 'count' => 1, - 'id' => $opposingId, - 'average' => $transaction->transaction_amount, - 'sum' => $transaction->transaction_amount, - ]; - continue; - } - $result[$opposingId]['count']++; - $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); - $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count'])); - } - - // sort result by average: - $average = []; - foreach ($result as $key => $row) { - $average[$key] = floatval($row['average']); - } - - array_multisort($average, $sortFlag, $result); - - return $result; - } - /** * @return Collection */ - private function getExpenses(): Collection + protected function getExpenses(): Collection { if ($this->expenses->count() > 0) { Log::debug('Return previous set of expenses.'); @@ -208,11 +170,13 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $collector = app(JournalCollectorInterface::class); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setCategories($this->categories)->withOpposingAccount()->disableFilter(); + ->setCategories($this->categories)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); - $accountIds = $this->accounts->pluck('id')->toArray(); $transactions = $collector->getJournals(); - $transactions = self::filterExpenses($transactions, $accountIds); $this->expenses = $transactions; return $transactions; @@ -221,7 +185,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface /** * @return Collection */ - private function getIncome(): Collection + protected function getIncome(): Collection { if ($this->income->count() > 0) { return $this->income; @@ -232,93 +196,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->setCategories($this->categories)->withOpposingAccount(); - $accountIds = $this->accounts->pluck('id')->toArray(); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(NegativeAmountFilter::class); + $transactions = $collector->getJournals(); - $transactions = self::filterIncome($transactions, $accountIds); $this->income = $transactions; return $transactions; } - /** - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. - * @param array $spent - * @param array $earned - * - * @return array - */ - private function getObjectSummary(array $spent, array $earned): array - { - $return = []; - - /** - * @var int $accountId - * @var string $entry - */ - foreach ($spent as $objectId => $entry) { - if (!isset($return[$objectId])) { - $return[$objectId] = ['spent' => 0, 'earned' => 0]; - } - - $return[$objectId]['spent'] = $entry; - } - unset($entry); - - /** - * @var int $accountId - * @var string $entry - */ - foreach ($earned as $objectId => $entry) { - if (!isset($return[$objectId])) { - $return[$objectId] = ['spent' => 0, 'earned' => 0]; - } - - $return[$objectId]['earned'] = $entry; - } - - - return $return; - } - - - /** - * @return Collection - */ - private function getTopExpenses(): Collection - { - $transactions = $this->getExpenses()->sortBy('transaction_amount'); - - return $transactions; - } - - /** - * @return Collection - */ - private function getTopIncome(): Collection - { - $transactions = $this->getIncome()->sortByDesc('transaction_amount'); - - return $transactions; - } - - /** - * @param Collection $collection - * - * @return array - */ - private function summarizeByAccount(Collection $collection): array - { - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $accountId = $transaction->account_id; - $result[$accountId] = $result[$accountId] ?? '0'; - $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); - } - - return $result; - } - /** * @param Collection $collection * diff --git a/app/Generator/Report/Category/MultiYearReportGenerator.php b/app/Generator/Report/Category/MultiYearReportGenerator.php index 62b0e32af9..5d5c501697 100644 --- a/app/Generator/Report/Category/MultiYearReportGenerator.php +++ b/app/Generator/Report/Category/MultiYearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Category; @@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category; /** * Class MultiYearReportGenerator * - * @package FireflyIII\Generator\Report\Audit + * @package FireflyIII\Generator\Report\Category */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Category/YearReportGenerator.php b/app/Generator/Report/Category/YearReportGenerator.php index a118c4c5b0..4d53762990 100644 --- a/app/Generator/Report/Category/YearReportGenerator.php +++ b/app/Generator/Report/Category/YearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Category; @@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category; /** * Class YearReportGenerator * - * @package FireflyIII\Generator\Report\Audit + * @package FireflyIII\Generator\Report\Category */ class YearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/ReportGeneratorFactory.php b/app/Generator/Report/ReportGeneratorFactory.php index 9dbf45ed43..8884c21863 100644 --- a/app/Generator/Report/ReportGeneratorFactory.php +++ b/app/Generator/Report/ReportGeneratorFactory.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report; @@ -49,7 +49,7 @@ class ReportGeneratorFactory $class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period); if (class_exists($class)) { /** @var ReportGeneratorInterface $obj */ - $obj = new $class; + $obj = app($class); $obj->setStartDate($start); $obj->setEndDate($end); diff --git a/app/Generator/Report/ReportGeneratorInterface.php b/app/Generator/Report/ReportGeneratorInterface.php index 12942190f4..300eca098c 100644 --- a/app/Generator/Report/ReportGeneratorInterface.php +++ b/app/Generator/Report/ReportGeneratorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report; diff --git a/app/Generator/Report/Standard/MonthReportGenerator.php b/app/Generator/Report/Standard/MonthReportGenerator.php index bf4d1889ca..93f5d11d2d 100644 --- a/app/Generator/Report/Standard/MonthReportGenerator.php +++ b/app/Generator/Report/Standard/MonthReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Standard; diff --git a/app/Generator/Report/Standard/MultiYearReportGenerator.php b/app/Generator/Report/Standard/MultiYearReportGenerator.php index a5b1702a95..228edba2a7 100644 --- a/app/Generator/Report/Standard/MultiYearReportGenerator.php +++ b/app/Generator/Report/Standard/MultiYearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Standard; diff --git a/app/Generator/Report/Standard/YearReportGenerator.php b/app/Generator/Report/Standard/YearReportGenerator.php index e755b45057..3c8ac07cff 100644 --- a/app/Generator/Report/Standard/YearReportGenerator.php +++ b/app/Generator/Report/Standard/YearReportGenerator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report\Standard; diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php index 573129ef97..563e72f12b 100644 --- a/app/Generator/Report/Support.php +++ b/app/Generator/Report/Support.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Generator\Report; @@ -25,57 +25,122 @@ use Log; */ class Support { - /** - * @param Collection $collection - * @param array $accounts - * * @return Collection */ - public static function filterExpenses(Collection $collection, array $accounts): Collection + public function getTopExpenses(): Collection { - return self::filterTransactions($collection, $accounts, 1); + $transactions = $this->getExpenses()->sortBy('transaction_amount'); + + return $transactions; + } + + /** + * @return Collection + */ + public function getTopIncome(): Collection + { + $transactions = $this->getIncome()->sortByDesc('transaction_amount'); + + return $transactions; } /** * @param Collection $collection - * @param array $accounts + * @param int $sortFlag * - * @return Collection + * @return array */ - public static function filterIncome(Collection $collection, array $accounts): Collection + protected function getAverages(Collection $collection, int $sortFlag): array { - return self::filterTransactions($collection, $accounts, -1); - } + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + // opposing name and ID: + $opposingId = $transaction->opposing_account_id; - /** - * @param Collection $collection - * @param array $accounts - * @param int $modifier - * - * @return Collection - */ - public static function filterTransactions(Collection $collection, array $accounts, int $modifier): Collection - { - $result = $collection->filter( - function (Transaction $transaction) use ($accounts, $modifier) { - $opposing = $transaction->opposing_account_id; - // remove internal transfer - if (in_array($opposing, $accounts)) { - Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id)); - - return null; - } - // remove positive amount - if (bccomp($transaction->transaction_amount, '0') === $modifier) { - Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); - - return null; - } - - return $transaction; + // is not set? + if (!isset($result[$opposingId])) { + $name = $transaction->opposing_account_name; + $result[$opposingId] = [ + 'name' => $name, + 'count' => 1, + 'id' => $opposingId, + 'average' => $transaction->transaction_amount, + 'sum' => $transaction->transaction_amount, + ]; + continue; } - ); + $result[$opposingId]['count']++; + $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); + $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count'])); + } + + // sort result by average: + $average = []; + foreach ($result as $key => $row) { + $average[$key] = floatval($row['average']); + } + + array_multisort($average, $sortFlag, $result); + + return $result; + } + + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. + * @param array $spent + * @param array $earned + * + * @return array + */ + protected function getObjectSummary(array $spent, array $earned): array + { + $return = []; + + /** + * @var int $accountId + * @var string $entry + */ + foreach ($spent as $objectId => $entry) { + if (!isset($return[$objectId])) { + $return[$objectId] = ['spent' => 0, 'earned' => 0]; + } + + $return[$objectId]['spent'] = $entry; + } + unset($entry); + + /** + * @var int $accountId + * @var string $entry + */ + foreach ($earned as $objectId => $entry) { + if (!isset($return[$objectId])) { + $return[$objectId] = ['spent' => 0, 'earned' => 0]; + } + + $return[$objectId]['earned'] = $entry; + } + + + return $return; + } + + /** + * @param Collection $collection + * + * @return array + */ + protected function summarizeByAccount(Collection $collection): array + { + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + $accountId = $transaction->account_id; + $result[$accountId] = $result[$accountId] ?? '0'; + $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); + } return $result; } diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php new file mode 100644 index 0000000000..f8a253defd --- /dev/null +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -0,0 +1,227 @@ +expenses = new Collection; + $this->income = new Collection; + } + + /** + * @return string + */ + public function generate(): string + { + $accountIds = join(',', $this->accounts->pluck('id')->toArray()); + $tagTags = join(',', $this->tags->pluck('tag')->toArray()); + $reportType = 'tag'; + $expenses = $this->getExpenses(); + $income = $this->getIncome(); + $accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income)); + $tagSummary = $this->getObjectSummary($this->summarizeByTag($expenses), $this->summarizeByTag($income)); + $averageExpenses = $this->getAverages($expenses, SORT_ASC); + $averageIncome = $this->getAverages($income, SORT_DESC); + $topExpenses = $this->getTopExpenses(); + $topIncome = $this->getTopIncome(); + + + // render! + return view( + 'reports.tag.month', compact( + 'accountIds', 'tagTags', 'reportType', 'accountSummary', 'tagSummary', 'averageExpenses', 'averageIncome', 'topIncome', + 'topExpenses' + ) + )->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render(); + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Collection $budgets + * + * @return ReportGeneratorInterface + */ + public function setBudgets(Collection $budgets): ReportGeneratorInterface + { + return $this; + } + + /** + * @param Collection $categories + * + * @return ReportGeneratorInterface + */ + public function setCategories(Collection $categories): ReportGeneratorInterface + { + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } + + /** + * @param Collection $tags + * + * @return ReportGeneratorInterface + */ + public function setTags(Collection $tags): ReportGeneratorInterface + { + $this->tags = $tags; + + return $this; + } + + /** + * @return Collection + */ + protected function getExpenses(): Collection + { + if ($this->expenses->count() > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setTags($this->tags)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); + + $transactions = $collector->getJournals(); + + $this->expenses = $transactions; + + return $transactions; + } + + /** + * @return Collection + */ + protected function getIncome(): Collection + { + if ($this->income->count() > 0) { + return $this->income; + } + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->setTags($this->tags)->withOpposingAccount(); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(NegativeAmountFilter::class); + + $transactions = $collector->getJournals(); + $this->income = $transactions; + + return $transactions; + } + + /** + * @param Collection $collection + * + * @return array + */ + protected function summarizeByTag(Collection $collection): array + { + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + $journal = $transaction->transactionJournal; + $journalTags = $journal->tags; + /** @var Tag $journalTag */ + foreach ($journalTags as $journalTag) { + $journalTagId = $journalTag->id; + $result[$journalTagId] = $result[$journalTagId] ?? '0'; + $result[$journalTagId] = bcadd($transaction->transaction_amount, $result[$journalTagId]); + } + } + + return $result; + } +} diff --git a/app/Generator/Report/Tag/MultiYearReportGenerator.php b/app/Generator/Report/Tag/MultiYearReportGenerator.php new file mode 100644 index 0000000000..5e7028286d --- /dev/null +++ b/app/Generator/Report/Tag/MultiYearReportGenerator.php @@ -0,0 +1,25 @@ +repository = $repository; + $this->journalRepository = $journalRepository; + $this->ruleGroupRepository = $ruleGroupRepository; + } + /** * This method connects a new transfer to a piggy bank. * + * + * * @param StoredTransactionJournal $event * * @return bool */ public function connectToPiggyBank(StoredTransactionJournal $event): bool { - /** @var TransactionJournal $journal */ $journal = $event->journal; $piggyBankId = $event->piggyBankId; - Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId)); + $piggyBank = $this->repository->find($piggyBankId); - /* - * Verify existence of piggy bank: - */ - if (!$this->verifyExistence($event)) { - Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d'))); + // is a transfer? + if (!$this->journalRepository->isTransfer($journal)) { + Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id)); return true; } - /* - * Get relevant data: - */ - $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - $amount = $this->getExactAmount($journal, $piggyBank, $repetition); - $repetition->currentamount = bcadd($repetition->currentamount, $amount); - $repetition->save(); + // piggy exists? + if (is_null($piggyBank->id)) { + Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId)); + + return true; + } + + // repetition exists? + $repetition = $this->repository->getRepetition($piggyBank, $journal->date); + if (is_null($repetition->id)) { + Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); + + return true; + } + + // get the amount + $amount = $this->repository->getExactAmount($piggyBank, $repetition, $journal); + if (bccomp($amount, '0') === 0) { + Log::debug('Amount is zero, will not create event.'); + + return true; + } + + // update amount + $this->repository->addAmountToRepetition($repetition, $amount); + $event = $this->repository->createEventWithJournal($piggyBank, $amount, $journal); - /** @var PiggyBankEvent $event */ - $event = PiggyBankEvent::create( - ['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount] - ); Log::debug(sprintf('Created piggy bank event #%d', $event->id)); return true; @@ -83,16 +115,11 @@ class StoredJournalEventHandler { // get all the user's rule groups, with the rules, order by 'order'. $journal = $storedJournalEvent->journal; - $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); - // + $groups = $this->ruleGroupRepository->getActiveGroups($journal->user); + /** @var RuleGroup $group */ foreach ($groups as $group) { - $rules = $group->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.*']); + $rules = $this->ruleGroupRepository->getActiveStoreRules($group); /** @var Rule $rule */ foreach ($rules as $rule) { @@ -100,9 +127,8 @@ class StoredJournalEventHandler $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { - return true; + break; } - } } @@ -123,81 +149,4 @@ class StoredJournalEventHandler return true; } - - /** - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it. - * @param TransactionJournal $journal - * @param PiggyBank $piggyBank - * @param PiggyBankRepetition $repetition - * - * @return string - */ - private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string - { - $amount = TransactionJournal::amountPositive($journal); - $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); - $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); - $compare = bcmul($repetition->currentamount, '-1'); - - Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - - // if piggy account matches source account, the amount is positive - if (in_array($piggyBank->account_id, $sources)) { - $amount = bcmul($amount, '-1'); - Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); - } - - - // if the amount is positive, make sure it fits in piggy bank: - if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) { - // amount is positive and $room is smaller than $amount - Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); - Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $room)); - - return $room; - } - - // amount is negative and $currentamount is smaller than $amount - if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) { - Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); - Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $compare)); - - return $compare; - } - - return $amount; - } - - /** - * @param StoredTransactionJournal $event - * - * @return bool - */ - private function verifyExistence(StoredTransactionJournal $event): bool - { - /** @var TransactionJournal $journal */ - $journal = $event->journal; - $piggyBankId = $event->piggyBankId; - - /** @var PiggyBank $piggyBank */ - $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - - if (is_null($piggyBank)) { - Log::error('No such piggy bank!'); - - return false; - } - Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name)); - // update piggy bank rep for date of transaction journal. - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - if (is_null($repetition)) { - Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); - - return false; - } - - return true; - } } diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php index 0deea00280..1cdadd6da8 100644 --- a/app/Handlers/Events/UpdatedJournalEventHandler.php +++ b/app/Handlers/Events/UpdatedJournalEventHandler.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Handlers\Events; @@ -17,16 +17,29 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Rules\Processor; use FireflyIII\Support\Events\BillScanner; /** + * @codeCoverageIgnore + * * Class UpdatedJournalEventHandler * * @package FireflyIII\Handlers\Events */ class UpdatedJournalEventHandler { + /** @var RuleGroupRepositoryInterface */ + public $repository; + + /** + * StoredJournalEventHandler constructor. + */ + public function __construct(RuleGroupRepositoryInterface $ruleGroupRepository) + { + $this->repository = $ruleGroupRepository; + } /** * This method will check all the rules when a journal is updated. @@ -39,16 +52,11 @@ class UpdatedJournalEventHandler { // get all the user's rule groups, with the rules, order by 'order'. $journal = $updatedJournalEvent->journal; - $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); - // + $groups = $this->repository->getActiveGroups($journal->user); + /** @var RuleGroup $group */ foreach ($groups as $group) { - $rules = $group->rules() - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'update-journal') - ->where('rules.active', 1) - ->get(['rules.*']); + $rules = $this->repository->getActiveUpdateRules($group); /** @var Rule $rule */ foreach ($rules as $rule) { $processor = Processor::make($rule); diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 5144843bf7..4b02a2a455 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -9,17 +9,17 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Handlers\Events; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Mail\RegisteredUser as RegisteredUserMail; +use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail; use FireflyIII\Repositories\User\UserRepositoryInterface; -use Illuminate\Mail\Message; use Log; use Mail; -use Session; use Swift_TransportException; /** @@ -54,20 +54,6 @@ class UserEventHandler return true; } - /** - * Handle user logout events. - * - * @return bool - */ - public function logoutUser(): bool - { - // dump stuff from the session: - Session::forget('twofactor-authenticated'); - Session::forget('twofactor-authenticated-date'); - - return true; - } - /** * @param RequestedNewPassword $event * @@ -83,14 +69,12 @@ class UserEventHandler // send email. try { - Mail::send( - ['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) { - $message->to($email, $email)->subject('Your password reset request'); - } - ); + Mail::to($email)->send(new RequestedNewPasswordMail($url, $ipAddress)); + // @codeCoverageIgnoreStart } catch (Swift_TransportException $e) { Log::error($e->getMessage()); } + // @codeCoverageIgnoreEnd return true; } @@ -108,22 +92,21 @@ class UserEventHandler $sendMail = env('SEND_REGISTRATION_MAIL', true); if (!$sendMail) { - return true; + return true; // @codeCoverageIgnore } // get the email address $email = $event->user->email; $address = route('index'); $ipAddress = $event->ipAddress; + // send email. try { - Mail::send( - ['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) { - $message->to($email, $email)->subject('Welcome to Firefly III!'); - } - ); + Mail::to($email)->send(new RegisteredUserMail($address, $ipAddress)); + // @codeCoverageIgnoreStart } catch (Swift_TransportException $e) { Log::error($e->getMessage()); } + // @codeCoverageIgnoreEnd return true; } diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index c7f7902109..3189055bb2 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -9,16 +9,18 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Attachments; use Crypt; use FireflyIII\Models\Attachment; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use Storage; use Symfony\Component\HttpFoundation\File\UploadedFile; - +use Log; /** * Class AttachmentHelper * @@ -27,6 +29,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachmentHelper implements AttachmentHelperInterface { + /** @var Collection */ + public $attachments; /** @var MessageBag */ public $errors; /** @var MessageBag */ @@ -45,9 +49,10 @@ class AttachmentHelper implements AttachmentHelperInterface public function __construct() { $this->maxUploadSize = intval(config('firefly.maxUploadSize')); - $this->allowedMimes = (array) config('firefly.allowedMimes'); + $this->allowedMimes = (array)config('firefly.allowedMimes'); $this->errors = new MessageBag; $this->messages = new MessageBag; + $this->attachments = new Collection; $this->uploadDisk = Storage::disk('upload'); } @@ -63,6 +68,14 @@ class AttachmentHelper implements AttachmentHelperInterface return $path; } + /** + * @return Collection + */ + public function getAttachments(): Collection + { + return $this->attachments; + } + /** * @return MessageBag */ @@ -109,7 +122,7 @@ class AttachmentHelper implements AttachmentHelperInterface $md5 = md5_file($file->getRealPath()); $name = $file->getClientOriginalName(); $class = get_class($model); - $count = auth()->user()->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); + $count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); if ($count > 0) { $msg = (string)trans('validation.file_already_attached', ['name' => $name]); @@ -136,7 +149,7 @@ class AttachmentHelper implements AttachmentHelperInterface } $attachment = new Attachment; // create Attachment object. - $attachment->user()->associate(auth()->user()); + $attachment->user()->associate($model->user); $attachment->attachable()->associate($model); $attachment->md5 = md5_file($file->getRealPath()); $attachment->filename = $file->getClientOriginalName(); @@ -155,6 +168,7 @@ class AttachmentHelper implements AttachmentHelperInterface $attachment->uploaded = 1; // update attachment $attachment->save(); + $this->attachments->push($attachment); $name = e($file->getClientOriginalName()); // add message: $msg = (string)trans('validation.file_attached', ['name' => $name]); @@ -187,6 +201,7 @@ class AttachmentHelper implements AttachmentHelperInterface } /** + * @codeCoverageIgnore * @param UploadedFile $file * * @return bool @@ -217,7 +232,7 @@ class AttachmentHelper implements AttachmentHelperInterface return false; } if (!$this->validSize($file)) { - return false; + return false; // @codeCoverageIgnore } if ($this->hasFile($file, $model)) { return false; diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index dc6f9420aa..ad1c80fd1b 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -9,11 +9,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Attachments; use FireflyIII\Models\Attachment; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; /** @@ -31,6 +33,11 @@ interface AttachmentHelperInterface */ public function getAttachmentLocation(Attachment $attachment): string; + /** + * @return Collection + */ + public function getAttachments(): Collection; + /** * @return MessageBag */ @@ -42,7 +49,9 @@ interface AttachmentHelperInterface public function getMessages(): MessageBag; /** - * @param Model $model + * @param Model $model + * + * @param null|array $files * * @return bool */ diff --git a/app/Helpers/Chart/MetaPieChart.php b/app/Helpers/Chart/MetaPieChart.php index 6c340b5389..c3b8297cd1 100644 --- a/app/Helpers/Chart/MetaPieChart.php +++ b/app/Helpers/Chart/MetaPieChart.php @@ -7,18 +7,23 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Chart; use Carbon\Carbon; -use FireflyIII\Generator\Report\Support; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; +use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; use Illuminate\Support\Collection; use Steam; @@ -46,19 +51,20 @@ class MetaPieChart implements MetaPieChartInterface 'account' => ['opposing_account_id'], 'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'], 'category' => ['transaction_journal_category_id', 'transaction_category_id'], + 'tag' => [], ]; - /** @var array */ protected $repositories = [ 'account' => AccountRepositoryInterface::class, 'budget' => BudgetRepositoryInterface::class, 'category' => CategoryRepositoryInterface::class, + 'tag' => TagRepositoryInterface::class, ]; - - /** @var Carbon */ protected $start; + /** @var Collection */ + protected $tags; /** @var string */ protected $total = '0'; /** @var User */ @@ -69,6 +75,7 @@ class MetaPieChart implements MetaPieChartInterface $this->accounts = new Collection; $this->budgets = new Collection; $this->categories = new Collection; + $this->tags = new Collection; } /** @@ -99,6 +106,7 @@ class MetaPieChart implements MetaPieChartInterface if ($this->collectOtherObjects && $direction === 'income') { /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); + $collector->setUser($this->user); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]); $journals = $collector->getJournals(); $sum = strval($journals->sum('transaction_amount')); @@ -111,6 +119,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param Collection $accounts * * @return MetaPieChartInterface @@ -123,6 +133,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param Collection $budgets * * @return MetaPieChartInterface @@ -135,6 +147,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param Collection $categories * * @return MetaPieChartInterface @@ -147,6 +161,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param bool $collectOtherObjects * * @return MetaPieChartInterface @@ -159,6 +175,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param Carbon $end * * @return MetaPieChartInterface @@ -171,6 +189,8 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * * @param Carbon $start * * @return MetaPieChartInterface @@ -183,6 +203,22 @@ class MetaPieChart implements MetaPieChartInterface } /** + * @codeCoverageIgnore + * + * @param Collection $tags + * + * @return MetaPieChartInterface + */ + public function setTags(Collection $tags): MetaPieChartInterface + { + $this->tags = $tags; + + return $this; + } + + /** + * @codeCoverageIgnore + * * @param User $user * * @return MetaPieChartInterface @@ -194,23 +230,32 @@ class MetaPieChart implements MetaPieChartInterface return $this; } - protected function getTransactions(string $direction) + /** + * @param string $direction + * + * @return Collection + */ + protected function getTransactions(string $direction): Collection { - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - $modifier = -1; - if ($direction === 'expense') { - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - $modifier = 1; - } /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $collector->addFilter(NegativeAmountFilter::class); + if ($direction === 'expense') { + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $collector->addFilter(PositiveAmountFilter::class); + $collector->removeFilter(NegativeAmountFilter::class); + } + + $collector->setUser($this->user); $collector->setAccounts($this->accounts); $collector->setRange($this->start, $this->end); $collector->setTypes($types); $collector->withOpposingAccount(); + $collector->addFilter(OpposingAccountFilter::class); if ($direction === 'income') { - $collector->disableFilter(); + $collector->removeFilter(TransferFilter::class); } if ($this->budgets->count() > 0) { @@ -219,12 +264,13 @@ class MetaPieChart implements MetaPieChartInterface if ($this->categories->count() > 0) { $collector->setCategories($this->categories); } + if ($this->tags->count() > 0) { + $collector->setTags($this->tags); + $collector->withCategoryInformation(); + $collector->withBudgetInformation(); + } - $accountIds = $this->accounts->pluck('id')->toArray(); - $transactions = $collector->getJournals(); - $set = Support::filterTransactions($transactions, $accountIds, $modifier); - - return $set; + return $collector->getJournals(); } /** @@ -233,8 +279,13 @@ class MetaPieChart implements MetaPieChartInterface * * @return array */ - protected function groupByFields(Collection $set, array $fields) + protected function groupByFields(Collection $set, array $fields): array { + if (count($fields) === 0 && $this->tags->count() > 0) { + // do a special group on tags: + return $this->groupByTag($set); + } + $grouped = []; /** @var Transaction $transaction */ foreach ($set as $transaction) { @@ -261,10 +312,11 @@ class MetaPieChart implements MetaPieChartInterface $chartData = []; $names = []; $repository = app($this->repositories[$type]); + $repository->setUser($this->user); foreach ($array as $objectId => $amount) { if (!isset($names[$objectId])) { $object = $repository->find(intval($objectId)); - $names[$objectId] = $object->name; + $names[$objectId] = $object->name ?? $object->tag; } $amount = Steam::positive($amount); $this->total = bcadd($this->total, $amount); @@ -274,4 +326,27 @@ class MetaPieChart implements MetaPieChartInterface return $chartData; } + + /** + * @param Collection $set + * + * @return array + */ + private function groupByTag(Collection $set): array + { + $grouped = []; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + $journal = $transaction->transactionJournal; + $tags = $journal->tags; + /** @var Tag $tag */ + foreach ($tags as $tag) { + $tagId = $tag->id; + $grouped[$tagId] = $grouped[$tagId] ?? '0'; + $grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]); + } + } + + return $grouped; + } } diff --git a/app/Helpers/Chart/MetaPieChartInterface.php b/app/Helpers/Chart/MetaPieChartInterface.php index 006300d308..30e691a299 100644 --- a/app/Helpers/Chart/MetaPieChartInterface.php +++ b/app/Helpers/Chart/MetaPieChartInterface.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Chart; @@ -72,6 +72,13 @@ interface MetaPieChartInterface */ public function setStart(Carbon $start): MetaPieChartInterface; + /** + * @param Collection $tags + * + * @return MetaPieChartInterface + */ + public function setTags(Collection $tags): MetaPieChartInterface; + /** * @param User $user * diff --git a/app/Helpers/Collection/Balance.php b/app/Helpers/Collection/Balance.php index b708321536..8a946f2200 100644 --- a/app/Helpers/Collection/Balance.php +++ b/app/Helpers/Collection/Balance.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use Illuminate\Support\Collection; diff --git a/app/Helpers/Collection/BalanceEntry.php b/app/Helpers/Collection/BalanceEntry.php index 85701ea48b..23fbdf0c62 100644 --- a/app/Helpers/Collection/BalanceEntry.php +++ b/app/Helpers/Collection/BalanceEntry.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use FireflyIII\Models\Account as AccountModel; diff --git a/app/Helpers/Collection/BalanceHeader.php b/app/Helpers/Collection/BalanceHeader.php index 66a6145b44..d0a4e8e934 100644 --- a/app/Helpers/Collection/BalanceHeader.php +++ b/app/Helpers/Collection/BalanceHeader.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use FireflyIII\Models\Account as AccountModel; diff --git a/app/Helpers/Collection/BalanceLine.php b/app/Helpers/Collection/BalanceLine.php index 9b14902d40..73b1a0f486 100644 --- a/app/Helpers/Collection/BalanceLine.php +++ b/app/Helpers/Collection/BalanceLine.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use Carbon\Carbon; diff --git a/app/Helpers/Collection/Bill.php b/app/Helpers/Collection/Bill.php index 1cfac94d20..e78e31631b 100644 --- a/app/Helpers/Collection/Bill.php +++ b/app/Helpers/Collection/Bill.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; diff --git a/app/Helpers/Collection/BillLine.php b/app/Helpers/Collection/BillLine.php index 3b7bd692b6..10a24dbcb8 100644 --- a/app/Helpers/Collection/BillLine.php +++ b/app/Helpers/Collection/BillLine.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use Carbon\Carbon; diff --git a/app/Helpers/Collection/Category.php b/app/Helpers/Collection/Category.php index faa4119a86..7a4a8b6415 100644 --- a/app/Helpers/Collection/Category.php +++ b/app/Helpers/Collection/Category.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Collection; use FireflyIII\Models\Category as CategoryModel; diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index f709eff9ba..56d53d7446 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Collector; @@ -18,12 +18,17 @@ use Carbon\Carbon; use Crypt; use DB; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Filter\FilterInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; @@ -74,6 +79,9 @@ class JournalCollector implements JournalCollectorInterface private $filterInternalTransfers; /** @var bool */ private $filterTransfers = false; + /** @var array */ + private $filters = [InternalTransferFilter::class]; + /** @var bool */ private $joinedBudget = false; /** @var bool */ @@ -95,6 +103,22 @@ class JournalCollector implements JournalCollectorInterface /** @var User */ private $user; + /** + * @param string $filter + * + * @return JournalCollectorInterface + */ + public function addFilter(string $filter): JournalCollectorInterface + { + $interfaces = class_implements($filter); + if (in_array(FilterInterface::class, $interfaces)) { + Log::debug(sprintf('Enabled filter %s', $filter)); + $this->filters[] = $filter; + } + + return $this; + } + /** * @return int * @throws FireflyException @@ -119,36 +143,6 @@ class JournalCollector implements JournalCollectorInterface return $this->count; } - /** - * @return JournalCollectorInterface - */ - public function disableFilter(): JournalCollectorInterface - { - $this->filterTransfers = false; - - return $this; - } - - /** - * @return JournalCollectorInterface - */ - public function disableInternalFilter(): JournalCollectorInterface - { - $this->filterInternalTransfers = false; - - return $this; - } - - /** - * @return JournalCollectorInterface - */ - public function enableInternalFilter(): JournalCollectorInterface - { - $this->filterInternalTransfers = true; - - return $this; - } - /** * @return Collection */ @@ -157,23 +151,18 @@ class JournalCollector implements JournalCollectorInterface $this->run = true; /** @var Collection $set */ $set = $this->query->get(array_values($this->fields)); - Log::debug(sprintf('Count of set is %d', $set->count())); - $set = $this->filterTransfers($set); - Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count())); - - // possibly filter "internal" transfers: - $set = $this->filterInternalTransfers($set); - Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count())); + // run all filters: + $set = $this->filter($set); // loop for decryption. $set->each( function (Transaction $transaction) { $transaction->date = new Carbon($transaction->date); - $transaction->description = $transaction->encrypted ? Crypt::decrypt($transaction->description) : $transaction->description; + $transaction->description = Steam::decrypt(intval($transaction->encrypted), $transaction->description); if (!is_null($transaction->bill_name)) { - $transaction->bill_name = $transaction->bill_name_encrypted ? Crypt::decrypt($transaction->bill_name) : $transaction->bill_name; + $transaction->bill_name = Steam::decrypt(intval($transaction->bill_name_encrypted), $transaction->bill_name); } try { @@ -204,6 +193,22 @@ class JournalCollector implements JournalCollectorInterface return $journals; } + /** + * @param string $filter + * + * @return JournalCollectorInterface + */ + public function removeFilter(string $filter): JournalCollectorInterface + { + $key = array_search($filter, $this->filters, true); + if (!($key === false)) { + Log::debug(sprintf('Removed filter %s', $filter)); + unset($this->filters[$key]); + } + + return $this; + } + /** * @param Collection $accounts * @@ -219,6 +224,7 @@ class JournalCollector implements JournalCollectorInterface } if ($accounts->count() > 1) { + $this->addFilter(TransferFilter::class); $this->filterTransfers = true; } @@ -242,6 +248,7 @@ class JournalCollector implements JournalCollectorInterface } if ($accounts->count() > 1) { + $this->addFilter(TransferFilter::class); $this->filterTransfers = true; } @@ -430,6 +437,20 @@ class JournalCollector implements JournalCollectorInterface return $this; } + /** + * @param Collection $tags + * + * @return JournalCollectorInterface + */ + public function setTags(Collection $tags): JournalCollectorInterface + { + $this->joinTagTables(); + $tagIds = $tags->pluck('id')->toArray(); + $this->query->whereIn('tag_transaction_journal.tag_id', $tagIds); + + return $this; + } + /** * @param array $types * @@ -450,7 +471,9 @@ class JournalCollector implements JournalCollectorInterface */ public function setUser(User $user) { + Log::debug(sprintf('Journal collector now collecting for user #%d', $user->id)); $this->user = $user; + $this->startQuery(); } /** @@ -458,6 +481,7 @@ class JournalCollector implements JournalCollectorInterface */ public function startQuery() { + Log::debug('journalCollector::startQuery'); /** @var EloquentBuilder $query */ $query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id') @@ -503,6 +527,7 @@ class JournalCollector implements JournalCollectorInterface public function withOpposingAccount(): JournalCollectorInterface { $this->joinOpposingTables(); + return $this; } @@ -545,79 +570,23 @@ class JournalCollector implements JournalCollectorInterface * * @return Collection */ - private function filterInternalTransfers(Collection $set): Collection + private function filter(Collection $set): Collection { - if ($this->filterInternalTransfers === false) { - Log::debug('Did NO filtering for internal transfers on given set.'); - - return $set; - } - if ($this->joinedOpposing === false) { - Log::info('Cannot filter internal transfers because no opposing information is present.'); - - return $set; - } - - $accountIds = $this->accountIds; - $set = $set->filter( - function (Transaction $transaction) use ($accountIds) { - // both id's in $accountids? - if (in_array($transaction->account_id, $accountIds) && in_array($transaction->opposing_account_id, $accountIds)) { - Log::debug( - sprintf( - 'Transaction #%d has #%d and #%d in set, so removed', - $transaction->id, $transaction->account_id, $transaction->opposing_account_id - ), $accountIds - ); - - return false; - } - - return $transaction; - + // create all possible filters: + $filters = [ + InternalTransferFilter::class => new InternalTransferFilter($this->accountIds), + OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds), + TransferFilter::class => new TransferFilter, + PositiveAmountFilter::class => new PositiveAmountFilter, + NegativeAmountFilter::class => new NegativeAmountFilter, + ]; + Log::debug(sprintf('Will run %d filters on the set.', count($this->filters))); + foreach ($this->filters as $enabled) { + if (isset($filters[$enabled])) { + Log::debug(sprintf('Before filter %s: %d', $enabled, $set->count())); + $set = $filters[$enabled]->filter($set); + Log::debug(sprintf('After filter %s: %d', $enabled, $set->count())); } - ); - - return $set; - } - - /** - * If the set of accounts used by the collector includes more than one asset - * account, chances are the set include double entries: transfers get selected - * on both the source, and then again on the destination account. - * - * This method filters them out by removing transfers that have been selected twice. - * - * @param Collection $set - * - * @return Collection - */ - private function filterTransfers(Collection $set): Collection - { - if ($this->filterTransfers) { - $count = []; - $new = new Collection; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - if ($transaction->transaction_type_type !== TransactionType::TRANSFER) { - $new->push($transaction); - continue; - } - // make property string: - $journalId = $transaction->transaction_journal_id; - $amount = Steam::positive($transaction->transaction_amount); - $accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)]; - sort($accountIds); - $key = $journalId . '-' . join(',', $accountIds) . '-' . $amount; - Log::debug(sprintf('Key is %s', $key)); - if (!isset($count[$key])) { - // not yet counted? add to new set and count it: - $new->push($transaction); - $count[$key] = 1; - } - } - - return $new; } return $set; diff --git a/app/Helpers/Collector/JournalCollectorInterface.php b/app/Helpers/Collector/JournalCollectorInterface.php index 80e41eb781..3e2c2aa6db 100644 --- a/app/Helpers/Collector/JournalCollectorInterface.php +++ b/app/Helpers/Collector/JournalCollectorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Collector; @@ -28,26 +28,18 @@ use Illuminate\Support\Collection; */ interface JournalCollectorInterface { + /** + * @param string $filter + * + * @return JournalCollectorInterface + */ + public function addFilter(string $filter): JournalCollectorInterface; + /** * @return int */ public function count(): int; - /** - * @return JournalCollectorInterface - */ - public function disableFilter(): JournalCollectorInterface; - - /** - * @return JournalCollectorInterface - */ - public function disableInternalFilter(): JournalCollectorInterface; - - /** - * @return JournalCollectorInterface - */ - public function enableInternalFilter(): JournalCollectorInterface; - /** * @return Collection */ @@ -58,6 +50,13 @@ interface JournalCollectorInterface */ public function getPaginatedJournals(): LengthAwarePaginator; + /** + * @param string $filter + * + * @return JournalCollectorInterface + */ + public function removeFilter(string $filter): JournalCollectorInterface; + /** * @param Collection $accounts * @@ -141,6 +140,13 @@ interface JournalCollectorInterface */ public function setTag(Tag $tag): JournalCollectorInterface; + /** + * @param Collection $tags + * + * @return JournalCollectorInterface + */ + public function setTags(Collection $tags): JournalCollectorInterface; + /** * @param array $types * diff --git a/app/Helpers/Filter/AmountFilter.php b/app/Helpers/Filter/AmountFilter.php new file mode 100644 index 0000000000..eb4ab55662 --- /dev/null +++ b/app/Helpers/Filter/AmountFilter.php @@ -0,0 +1,56 @@ +modifier = $modifier; + } + + /** + * @param Collection $set + * + * @return Collection + */ + public function filter(Collection $set): Collection + { + return $set->filter( + function (Transaction $transaction) { + // remove by amount + if (bccomp($transaction->transaction_amount, '0') === $this->modifier) { + Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); + + return null; + } + + return $transaction; + } + ); + } +} \ No newline at end of file diff --git a/app/Helpers/Filter/EmptyFilter.php b/app/Helpers/Filter/EmptyFilter.php new file mode 100644 index 0000000000..23a4172e88 --- /dev/null +++ b/app/Helpers/Filter/EmptyFilter.php @@ -0,0 +1,34 @@ +accounts = $accounts; + } + + /** + * @param Collection $set + * + * @return Collection + */ + public function filter(Collection $set): Collection + { + return $set->filter( + function (Transaction $transaction) { + if (is_null($transaction->opposing_account_id)) { + return $transaction; + } + // both id's in $parameters? + if (in_array($transaction->account_id, $this->accounts) && in_array($transaction->opposing_account_id, $this->accounts)) { + Log::debug( + sprintf( + 'Transaction #%d has #%d and #%d in set, so removed', + $transaction->id, $transaction->account_id, $transaction->opposing_account_id + ), $this->accounts + ); + + return false; + } + + return $transaction; + + } + ); + + + } +} \ No newline at end of file diff --git a/app/Helpers/Filter/NegativeAmountFilter.php b/app/Helpers/Filter/NegativeAmountFilter.php new file mode 100644 index 0000000000..f2ef34bcc4 --- /dev/null +++ b/app/Helpers/Filter/NegativeAmountFilter.php @@ -0,0 +1,47 @@ +filter( + function (Transaction $transaction) { + // remove by amount + if (bccomp($transaction->transaction_amount, '0') === -1) { + Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); + + return null; + } + + return $transaction; + } + ); + } +} \ No newline at end of file diff --git a/app/Helpers/Filter/OpposingAccountFilter.php b/app/Helpers/Filter/OpposingAccountFilter.php new file mode 100644 index 0000000000..69a1567943 --- /dev/null +++ b/app/Helpers/Filter/OpposingAccountFilter.php @@ -0,0 +1,63 @@ +accounts = $accounts; + } + + /** + * @param Collection $set + * + * @return Collection + */ + public function filter(Collection $set): Collection + { + return $set->filter( + function (Transaction $transaction) { + $opposing = $transaction->opposing_account_id; + // remove internal transfer + if (in_array($opposing, $this->accounts)) { + Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id), $this->accounts); + + return null; + } + + return $transaction; + } + ); + } +} \ No newline at end of file diff --git a/app/Helpers/Filter/PositiveAmountFilter.php b/app/Helpers/Filter/PositiveAmountFilter.php new file mode 100644 index 0000000000..ddb71841da --- /dev/null +++ b/app/Helpers/Filter/PositiveAmountFilter.php @@ -0,0 +1,50 @@ +filter( + function (Transaction $transaction) { + // remove by amount + if (bccomp($transaction->transaction_amount, '0') === 1) { + Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); + + return null; + } + + return $transaction; + } + ); + } +} \ No newline at end of file diff --git a/app/Helpers/Filter/TransferFilter.php b/app/Helpers/Filter/TransferFilter.php new file mode 100644 index 0000000000..d33d5e41ed --- /dev/null +++ b/app/Helpers/Filter/TransferFilter.php @@ -0,0 +1,58 @@ +transaction_type_type !== TransactionType::TRANSFER) { + $new->push($transaction); + continue; + } + // make property string: + $journalId = $transaction->transaction_journal_id; + $amount = Steam::positive($transaction->transaction_amount); + $accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)]; + sort($accountIds); + $key = $journalId . '-' . join(',', $accountIds) . '-' . $amount; + if (!isset($count[$key])) { + // not yet counted? add to new set and count it: + $new->push($transaction); + $count[$key] = 1; + } + } + + return $new; + } +} \ No newline at end of file diff --git a/app/Helpers/FiscalHelper.php b/app/Helpers/FiscalHelper.php index 16e965f82b..2e8f9cd534 100644 --- a/app/Helpers/FiscalHelper.php +++ b/app/Helpers/FiscalHelper.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers; diff --git a/app/Helpers/FiscalHelperInterface.php b/app/Helpers/FiscalHelperInterface.php index 830610fa9f..24673f2423 100644 --- a/app/Helpers/FiscalHelperInterface.php +++ b/app/Helpers/FiscalHelperInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers; diff --git a/app/Helpers/Help/Help.php b/app/Helpers/Help/Help.php index 12f01670dd..c7c97bcb96 100644 --- a/app/Helpers/Help/Help.php +++ b/app/Helpers/Help/Help.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Help; use Cache; @@ -43,12 +44,12 @@ class Help implements HelpInterface } /** - * @param string $language * @param string $route + * @param string $language * * @return string */ - public function getFromGithub(string $language, string $route): string + public function getFromGithub(string $route, string $language): string { $uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route); @@ -123,6 +124,7 @@ class Help implements HelpInterface if (strlen($content) > 0) { Log::debug(sprintf('Will store entry in cache: %s', $key)); Cache::put($key, $content, 10080); // a week. + return; } Log::info(sprintf('Will not cache %s because content is empty.', $key)); diff --git a/app/Helpers/Help/HelpInterface.php b/app/Helpers/Help/HelpInterface.php index d642f68294..027552f203 100644 --- a/app/Helpers/Help/HelpInterface.php +++ b/app/Helpers/Help/HelpInterface.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Helpers\Help; /** @@ -29,12 +30,12 @@ interface HelpInterface public function getFromCache(string $route, string $language): string; /** - * @param string $language * @param string $route + * @param string $language * * @return string */ - public function getFromGithub(string $language, string $route): string; + public function getFromGithub(string $route, string $language): string; /** * @param string $route diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index ca0667cf66..9c62ea1ebc 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Report; @@ -158,7 +158,9 @@ class BalanceReportHelper implements BalanceReportHelperInterface foreach ($accounts as $account) { $balanceEntry = new BalanceEntry; $balanceEntry->setAccount($account); - $spent = $this->budgetRepository->spentInPeriod(new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date); + $spent = $this->budgetRepository->spentInPeriod( + new Collection([$budgetLimit->budget]), new Collection([$account]), $budgetLimit->start_date, $budgetLimit->end_date + ); $balanceEntry->setSpent($spent); $line->addBalanceEntry($balanceEntry); } diff --git a/app/Helpers/Report/BalanceReportHelperInterface.php b/app/Helpers/Report/BalanceReportHelperInterface.php index 6687eb6cf6..a0cd05246e 100644 --- a/app/Helpers/Report/BalanceReportHelperInterface.php +++ b/app/Helpers/Report/BalanceReportHelperInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Report; diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 8025b0a159..55c6ea64a7 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Report; diff --git a/app/Helpers/Report/BudgetReportHelperInterface.php b/app/Helpers/Report/BudgetReportHelperInterface.php index a64d47d39f..7977453193 100644 --- a/app/Helpers/Report/BudgetReportHelperInterface.php +++ b/app/Helpers/Report/BudgetReportHelperInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Helpers\Report; diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php new file mode 100644 index 0000000000..f4d0a07d17 --- /dev/null +++ b/app/Helpers/Report/PopupReport.php @@ -0,0 +1,199 @@ +setAccounts(new Collection([$account])) + ->setTypes([TransactionType::WITHDRAWAL]) + ->setRange($attributes['startDate'], $attributes['endDate']) + ->withoutBudget(); + $journals = $collector->getJournals(); + + + return $journals->filter( + function (Transaction $transaction) { + $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); + if ($tags === 0) { + return true; + } + + return false; + } + ); + } + + /** + * @param Budget $budget + * @param Account $account + * @param array $attributes + * + * @return Collection + */ + public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setBudget($budget); + $journals = $collector->getJournals(); + + return $journals; + } + + /** + * @param Account $account + * @param array $attributes + * + * @return Collection + */ + public function balanceForNoBudget(Account $account, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector + ->setAccounts(new Collection([$account])) + ->setTypes([TransactionType::WITHDRAWAL]) + ->setRange($attributes['startDate'], $attributes['endDate']) + ->withoutBudget(); + + return $collector->getJournals(); + } + + /** + * @param Budget $budget + * @param array $attributes + * + * @return Collection + */ + public function byBudget(Budget $budget, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + + $collector->setAccounts($attributes['accounts'])->setRange($attributes['startDate'], $attributes['endDate']); + + if (is_null($budget->id)) { + $collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget(); + } + if (!is_null($budget->id)) { + $collector->setBudget($budget); + } + $journals = $collector->getJournals(); + + return $journals; + } + + /** + * @param Category $category + * @param array $attributes + * + * @return Collection + */ + public function byCategory(Category $category, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setRange($attributes['startDate'], $attributes['endDate']) + ->setCategory($category); + $journals = $collector->getJournals(); + + return $journals; + } + + /** + * @param Account $account + * @param array $attributes + * + * @return Collection + */ + public function byExpenses(Account $account, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + + $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate']) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]); + $journals = $collector->getJournals(); + + $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report + + // filter for transfers and withdrawals TO the given $account + $journals = $journals->filter( + function (Transaction $transaction) use ($report) { + // get the destinations: + $sources = $transaction->transactionJournal->sourceAccountList()->pluck('id')->toArray(); + + // do these intersect with the current list? + return !empty(array_intersect($report, $sources)); + } + ); + + return $journals; + } + + /** + * @param Account $account + * @param array $attributes + * + * @return Collection + */ + public function byIncome(Account $account, array $attributes): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate']) + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]); + $journals = $collector->getJournals(); + $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report + + // filter the set so the destinations outside of $attributes['accounts'] are not included. + $journals = $journals->filter( + function (Transaction $transaction) use ($report) { + // get the destinations: + $destinations = $transaction->destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray(); + + // do these intersect with the current list? + return !empty(array_intersect($report, $destinations)); + } + ); + + return $journals; + } +} \ No newline at end of file diff --git a/app/Helpers/Report/PopupReportInterface.php b/app/Helpers/Report/PopupReportInterface.php new file mode 100644 index 0000000000..79ca4be007 --- /dev/null +++ b/app/Helpers/Report/PopupReportInterface.php @@ -0,0 +1,83 @@ +get()); - $defaultCurrency = Amount::getDefaultCurrency(); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); - $subTitle = trans('firefly.make_new_' . $what . '_account'); - $roles = []; + $repository = app(CurrencyRepositoryInterface::class); + $allCurrencies = $repository->get(); + $currencySelectList = ExpandedForm::makeSelectList($allCurrencies); + $defaultCurrency = Amount::getDefaultCurrency(); + $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); + $subTitle = trans('firefly.make_new_' . $what . '_account'); + $roles = []; foreach (config('firefly.accountRoles') as $role) { $roles[$role] = strval(trans('firefly.account_role_' . $role)); } // pre fill some data - Session::flash('preFilled', ['currency_id' => $defaultCurrency->id,]); + $request->session()->flash('preFilled', ['currency_id' => $defaultCurrency->id,]); // put previous url in session if not redirect from store (not "create another"). if (session('accounts.create.fromStore') !== true) { $this->rememberPreviousUri('accounts.create.uri'); } - Session::forget('accounts.create.fromStore'); - Session::flash('gaEventCategory', 'accounts'); - Session::flash('gaEventAction', 'create-' . $what); + $request->session()->forget('accounts.create.fromStore'); + $request->session()->flash('gaEventCategory', 'accounts'); + $request->session()->flash('gaEventAction', 'create-' . $what); - return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies', 'roles')); + return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencySelectList', 'allCurrencies', 'roles')); } /** + * @param Request $request * @param AccountRepositoryInterface $repository * @param Account $account * * @return View */ - public function delete(AccountRepositoryInterface $repository, Account $account) + public function delete(Request $request, AccountRepositoryInterface $repository, Account $account) { $typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type); $subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]); @@ -109,16 +112,16 @@ class AccountController extends Controller // put previous url in session $this->rememberPreviousUri('accounts.delete.uri'); - Session::flash('gaEventCategory', 'accounts'); - Session::flash('gaEventAction', 'delete-' . $typeName); + $request->session()->flash('gaEventCategory', 'accounts'); + $request->session()->flash('gaEventAction', 'delete-' . $typeName); return view('accounts.delete', compact('account', 'subTitle', 'accountList')); } /** - * @param Request $request - * @param AccountRepositoryInterface $repository - * @param Account $account + * @param Request $request + * @param AccountRepositoryInterface $repository + * @param Account $account * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ @@ -131,27 +134,28 @@ class AccountController extends Controller $repository->destroy($account, $moveTo); - Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name]))); + $request->session()->flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name]))); Preferences::mark(); return redirect($this->getPreviousUri('accounts.delete.uri')); } /** + * @param Request $request * @param Account $account * * @return View */ - public function edit(Account $account) + public function edit(Request $request, Account $account) { - - $what = config('firefly.shortNamesByFullName')[$account->accountType->type]; - $subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $currencies = ExpandedForm::makeSelectList($repository->get()); - $roles = []; + $repository = app(CurrencyRepositoryInterface::class); + $what = config('firefly.shortNamesByFullName')[$account->accountType->type]; + $subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]); + $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); + $allCurrencies = $repository->get(); + $currencySelectList = ExpandedForm::makeSelectList($allCurrencies); + $roles = []; foreach (config('firefly.accountRoles') as $role) { $roles[$role] = strval(trans('firefly.account_role_' . $role)); } @@ -161,7 +165,7 @@ class AccountController extends Controller if (session('accounts.edit.fromUpdate') !== true) { $this->rememberPreviousUri('accounts.edit.uri'); } - Session::forget('accounts.edit.fromUpdate'); + $request->session()->forget('accounts.edit.fromUpdate'); // pre fill some useful values. @@ -170,6 +174,7 @@ class AccountController extends Controller $openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount; $openingBalanceDate = $account->getOpeningBalanceDate(); $openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d'); + $currency = $repository->find(intval($account->getMeta('currency_id'))); $preFilled = [ 'accountNumber' => $account->getMeta('accountNumber'), @@ -180,18 +185,23 @@ class AccountController extends Controller 'openingBalanceDate' => $openingBalanceDate, 'openingBalance' => $openingBalanceAmount, 'virtualBalance' => $account->virtual_balance, - 'currency_id' => $account->getMeta('currency_id'), - ]; - Session::flash('preFilled', $preFilled); - Session::flash('gaEventCategory', 'accounts'); - Session::flash('gaEventAction', 'edit-' . $what); + 'currency_id' => $currency->id, - return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles')); + ]; + $request->session()->flash('preFilled', $preFilled); + $request->session()->flash('gaEventCategory', 'accounts'); + $request->session()->flash('gaEventAction', 'edit-' . $what); + + return view( + 'accounts.edit', compact( + 'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles' + ) + ); } /** - * @param AccountRepositoryInterface $repository - * @param string $what + * @param AccountRepositoryInterface $repository + * @param string $what * * @return View */ @@ -225,106 +235,104 @@ class AccountController extends Controller return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts')); } + /** - * @param Request $request - * @param JournalCollectorInterface $collector - * @param Account $account + * @param Request $request + * @param JournalRepositoryInterface $repository + * @param Account $account + * @param string $moment * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function show(Request $request, JournalCollectorInterface $collector, Account $account) + public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '') { if ($account->accountType->type === AccountType::INITIAL_BALANCE) { return $this->redirectToOriginalAccount($account); } - // show journals from current period only: - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); - $subTitle = $account->name; - $range = Preferences::get('viewRange', '1M')->data; - $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); - $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $chartUri = route('chart.account.single', [$account->id]); - $accountType = $account->accountType->type; + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + $range = Preferences::get('viewRange', '1M')->data; + $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); + $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + $chartUri = route('chart.account.single', [$account->id]); + $start = null; + $end = null; + $periods = new Collection; + $currency = $currencyRepos->find(intval($account->getMeta('currency_id'))); - // grab those journals: - $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('accounts/show/' . $account->id); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]); + $chartUri = route('chart.account.all', [$account->id]); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; + } - // generate entries for each period (and cache those) - $entries = $this->periodEntries($account); + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]); + $periods = $this->getPeriodOverview($account); + } - return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')); + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $subTitle = trans( + 'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodOverview($account); + } + + $count = 0; + $loop = 0; + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + $collector = app(JournalCollectorInterface::class); + Log::info('Count is zero, search for journals.'); + $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page); + if (!is_null($start)) { + $collector->setRange($start, $end); + } + $journals = $collector->getPaginatedJournals(); + $journals->setPath('accounts/show/' . $account->id . '/' . $moment); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } + + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + + return view( + 'accounts.show', + compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri') + ); } /** - * @param Request $request - * @param AccountRepositoryInterface $repository - * @param Account $account - * - * @return View - */ - public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account) - { - $subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything'))); - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $chartUri = route('chart.account.all', [$account->id]); - - // replace with journal collector: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('accounts/show/' . $account->id . '/all'); - - // get oldest and newest journal for account: - $start = $repository->oldestJournalDate($account); - $end = $repository->newestJournalDate($account); - - // same call, except "entries". - return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')); - } - - /** - * @param Request $request - * @param Account $account - * @param string $date - * - * @return View - */ - public function showByDate(Request $request, Account $account, string $date) - { - $carbon = new Carbon($date); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')'; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]); - $accountType = $account->accountType->type; - - // replace with journal collector: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('accounts/show/' . $account->id . '/' . $date); - - // generate entries for each period (and cache those) - $entries = $this->periodEntries($account); - - // same call, except "entries". - return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')); - } - - /** - * @param AccountFormRequest $request - * @param AccountRepositoryInterface $repository + * @param AccountFormRequest $request + * @param AccountRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @@ -333,8 +341,7 @@ class AccountController extends Controller { $data = $request->getAccountData(); $account = $repository->store($data); - - Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name]))); + $request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name]))); Preferences::mark(); // update preferences if necessary: @@ -346,7 +353,7 @@ class AccountController extends Controller if (intval($request->get('create_another')) === 1) { // set value so create routine will not overwrite URL: - Session::put('accounts.create.fromStore', true); + $request->session()->put('accounts.create.fromStore', true); return redirect(route('accounts.create', [$request->input('what')]))->withInput(); } @@ -356,9 +363,9 @@ class AccountController extends Controller } /** - * @param AccountFormRequest $request - * @param AccountRepositoryInterface $repository - * @param Account $account + * @param AccountFormRequest $request + * @param AccountRepositoryInterface $repository + * @param Account $account * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ @@ -367,12 +374,12 @@ class AccountController extends Controller $data = $request->getAccountData(); $repository->update($account, $data); - Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name]))); + $request->session()->flash('success', strval(trans('firefly.updated_account', ['name' => $account->name]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: - Session::put('accounts.edit.fromUpdate', true); + $request->session()->put('accounts.edit.fromUpdate', true); return redirect(route('accounts.edit', [$account->id]))->withInput(['return_to_edit' => 1]); } @@ -407,18 +414,15 @@ class AccountController extends Controller * * @return Collection */ - private function periodEntries(Account $account): Collection + private function getPeriodOverview(Account $account): Collection { /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - /** @var AccountTaskerInterface $tasker */ - $tasker = app(AccountTaskerInterface::class); - - $start = $repository->oldestJournalDate($account); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($start, $range); - $end = Navigation::endOfX(new Carbon, $range); - $entries = new Collection; + $start = $repository->oldestJournalDate($account); + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; // properties for cache $cache = new CacheProperties; @@ -428,25 +432,39 @@ class AccountController extends Controller $cache->addProperty($account->id); if ($cache->has()) { - Log::debug('Entries are cached, return cache.'); - - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - // only include asset accounts when this account is an asset: - $assets = new Collection; - if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) { - $assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); - } Log::debug('Going to get period expenses and incomes.'); while ($end >= $start) { $end = Navigation::startOfPeriod($end, $range); $currentEnd = Navigation::endOfPeriod($end, $range); - $spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd); - $earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd); - $dateStr = $end->format('Y-m-d'); - $dateName = Navigation::periodShow($end, $range); - $entries->push([$dateStr, $dateName, $spent, $earned, clone $end]); + + // try a collector for income: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd) + ->setTypes([TransactionType::DEPOSIT]) + ->withOpposingAccount(); + $earned = strval($collector->getJournals()->sum('transaction_amount')); + + // try a collector for expenses: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd) + ->setTypes([TransactionType::WITHDRAWAL]) + ->withOpposingAccount(); + $spent = strval($collector->getJournals()->sum('transaction_amount')); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'spent' => $spent, + 'earned' => $earned, + 'date' => clone $end] + ); $end = Navigation::subtractPeriod($end, $range, 1); } @@ -474,7 +492,7 @@ class AccountController extends Controller $opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first(); if (is_null($opposingTransaction)) { - throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); + throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore } return redirect(route('accounts.show', [$opposingTransaction->account_id])); diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php index 909d33ae62..746c9cbdfe 100644 --- a/app/Http/Controllers/Admin/ConfigurationController.php +++ b/app/Http/Controllers/Admin/ConfigurationController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Admin; diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index fa798ef248..6cc7090a62 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Admin; diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 818c12c2f6..1a82b02e86 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Admin; @@ -18,6 +18,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\UserFormRequest; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; +use Log; use Preferences; use Session; use View; @@ -124,34 +125,34 @@ class UserController extends Controller } /** - * @param UserFormRequest $request - * @param User $user + * @param UserFormRequest $request + * @param User $user + * + * @param UserRepositoryInterface $repository * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(UserFormRequest $request, User $user) + public function update(UserFormRequest $request, User $user, UserRepositoryInterface $repository) { + Log::debug('Actually here'); $data = $request->getUserData(); // update password if (strlen($data['password']) > 0) { - $user->password = bcrypt($data['password']); - $user->save(); + $repository->changePassword($user, $data['password']); } - // change blocked status and code: - $user->blocked = $data['blocked']; - $user->blocked_code = $data['blocked_code']; - $user->save(); + $repository->changeStatus($user, $data['blocked'], $data['blocked_code']); Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('users.edit.fromUpdate', true); return redirect(route('admin.users.edit', [$user->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } // redirect to previous URL. diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index a6532309f1..cd8de38752 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -18,10 +18,10 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\AttachmentFormRequest; use FireflyIII\Models\Attachment; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; +use Illuminate\Http\Request; use Illuminate\Http\Response as LaravelResponse; use Preferences; use Response; -use Session; use View; /** @@ -53,35 +53,37 @@ class AttachmentController extends Controller } /** + * @param Request $request * @param Attachment $attachment * * @return View */ - public function delete(Attachment $attachment) + public function delete(Request $request, Attachment $attachment) { $subTitle = trans('firefly.delete_attachment', ['name' => $attachment->filename]); // put previous url in session $this->rememberPreviousUri('attachments.delete.uri'); - Session::flash('gaEventCategory', 'attachments'); - Session::flash('gaEventAction', 'delete-attachment'); + $request->session()->flash('gaEventCategory', 'attachments'); + $request->session()->flash('gaEventAction', 'delete-attachment'); return view('attachments.delete', compact('attachment', 'subTitle')); } /** + * @param Request $request * @param AttachmentRepositoryInterface $repository * @param Attachment $attachment * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(AttachmentRepositoryInterface $repository, Attachment $attachment) + public function destroy(Request $request, AttachmentRepositoryInterface $repository, Attachment $attachment) { $name = $attachment->filename; $repository->destroy($attachment); - Session::flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name]))); + $request->session()->flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name]))); Preferences::mark(); return redirect($this->getPreviousUri('attachments.delete.uri')); @@ -119,11 +121,12 @@ class AttachmentController extends Controller } /** + * @param Request $request * @param Attachment $attachment * * @return View */ - public function edit(Attachment $attachment) + public function edit(Request $request, Attachment $attachment) { $subTitleIcon = 'fa-pencil'; $subTitle = trans('firefly.edit_attachment', ['name' => $attachment->filename]); @@ -132,7 +135,7 @@ class AttachmentController extends Controller if (session('attachments.edit.fromUpdate') !== true) { $this->rememberPreviousUri('attachments.edit.uri'); } - Session::forget('attachments.edit.fromUpdate'); + $request->session()->forget('attachments.edit.fromUpdate'); return view('attachments.edit', compact('attachment', 'subTitleIcon', 'subTitle')); } @@ -169,14 +172,15 @@ class AttachmentController extends Controller $data = $request->getAttachmentData(); $repository->update($attachment, $data); - Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename]))); + $request->session()->flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: - Session::put('attachments.edit.fromUpdate', true); + // @codeCoverageIgnoreStart + $request->session()->put('attachments.edit.fromUpdate', true); return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } // redirect to previous URL. diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 3942c0868b..d1cfdd7832 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -8,11 +8,12 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Http\Request; @@ -40,25 +41,24 @@ class ForgotPasswordController extends Controller /** * Send a reset link to the given user. * - * @param Request $request + * @param Request $request + * + * @param UserRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse */ - public function sendResetLinkEmail(Request $request) + public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository) { $this->validate($request, ['email' => 'required|email']); // verify if the user is not a demo user. If so, we give him back an error. $user = User::where('email', $request->get('email'))->first(); - if (!is_null($user) && $user->hasRole('demo')) { - return back()->withErrors( - ['email' => trans('firefly.cannot_reset_demo_user')] - ); + + if (!is_null($user) && $repository->hasRole($user, 'demo')) { + return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]); } - $response = $this->broker()->sendResetLink( - $request->only('email') - ); + $response = $this->broker()->sendResetLink($request->only('email')); if ($response === Password::RESET_LINK_SENT) { return back()->with('status', trans($response)); @@ -67,8 +67,6 @@ class ForgotPasswordController extends Controller // If an error was returned by the password broker, we will get this message // translated so we can notify a user of the problem. We'll redirect back // to where the users came from so they can attempt this process again. - return back()->withErrors( - ['email' => trans($response)] - ); + return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 04e8abcc06..f74d1a1e7f 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; @@ -16,11 +16,14 @@ use Config; use FireflyConfig; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; +use Illuminate\Cookie\CookieJar; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Lang; /** + * @codeCoverageIgnore + * * Class LoginController * * @package FireflyIII\Http\Controllers\Auth @@ -74,23 +77,26 @@ class LoginController extends Controller } /** - * @param Request $request + * @param Request $request + * @param CookieJar $cookieJar * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return $this */ - public function logout(Request $request) + public function logout(Request $request, CookieJar $cookieJar) { if (intval(getenv('SANDSTORM')) === 1) { return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); } + $cookie = $cookieJar->forever('twoFactorAuthenticated', 'false'); + $this->guard()->logout(); $request->session()->flush(); $request->session()->regenerate(); - return redirect('/'); + return redirect('/')->withCookie($cookie); } /** @@ -104,12 +110,16 @@ class LoginController extends Controller /** * Show the application login form. * - * @param Request $request + * @param Request $request + * + * @param CookieJar $cookieJar * * @return \Illuminate\Http\Response */ - public function showLoginForm(Request $request) + public function showLoginForm(Request $request, CookieJar $cookieJar) { + // forget 2fa cookie: + $cookie = $cookieJar->forever('twoFactorAuthenticated', 'false'); // is allowed to? $singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data; $userCount = User::count(); @@ -121,7 +131,7 @@ class LoginController extends Controller $email = $request->old('email'); $remember = $request->old('remember'); - return view('auth.login', compact('allowRegistration', 'email', 'remember')); + return view('auth.login', compact('allowRegistration', 'email', 'remember'))->withCookie($cookie); } /** diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index 06a6fa99b2..035b11bb13 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; @@ -22,6 +22,8 @@ use Illuminate\Support\Facades\Password; /** + * @codeCoverageIgnore + * * Class PasswordController * * @package FireflyIII\Http\Controllers\Auth diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 2cf28042ba..7f7816b64d 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; @@ -24,6 +24,8 @@ use Session; use Validator; /** + * @codeCoverageIgnore + * * Class RegisterController * * @package FireflyIII\Http\Controllers\Auth @@ -122,12 +124,15 @@ class RegisterController extends Controller */ protected function create(array $data) { - return User::create( + /** @var User $user */ + $user = User::create( [ 'email' => $data['email'], 'password' => bcrypt($data['password']), ] ); + + return $user; } /** diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 1d99a0c179..1cdb78b988 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; @@ -16,6 +16,8 @@ use FireflyIII\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; /** + * @codeCoverageIgnore + * * Class ResetPasswordController * * @package FireflyIII\Http\Controllers\Auth diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index bf3b425161..f0b2b74993 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -9,18 +9,17 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; -use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\TokenFormRequest; +use Illuminate\Cookie\CookieJar; use Illuminate\Http\Request; use Log; use Preferences; -use Session; /** * Class TwoFactorController @@ -42,11 +41,12 @@ class TwoFactorController extends Controller $user = auth()->user(); // to make sure the validator in the next step gets the secret, we push it in session - $secret = Preferences::get('twoFactorAuthSecret', null)->data; - $title = strval(trans('firefly.two_factor_title')); + $secretPreference = Preferences::get('twoFactorAuthSecret', null); + $secret = is_null($secretPreference) ? null : $secretPreference->data; + $title = strval(trans('firefly.two_factor_title')); // make sure the user has two factor configured: - $has2FA = Preferences::get('twoFactorAuthEnabled', null)->data; + $has2FA = Preferences::get('twoFactorAuthEnabled', false)->data; if (is_null($has2FA) || $has2FA === false) { return redirect(route('index')); } @@ -80,16 +80,18 @@ class TwoFactorController extends Controller /** * @param TokenFormRequest $request - * @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation. + * @param CookieJar $cookieJar * * @return mixed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation. + * */ - public function postIndex(TokenFormRequest $request) + public function postIndex(TokenFormRequest $request, CookieJar $cookieJar) { - Session::put('twofactor-authenticated', true); - Session::put('twofactor-authenticated-date', new Carbon); + // set cookie! + $cookie = $cookieJar->forever('twoFactorAuthenticated', 'true'); - return redirect(route('home')); + return redirect(route('home'))->withCookie($cookie); } } diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index ae95ba23ef..2418bde1b0 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -22,7 +22,6 @@ use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Preferences; -use Session; use URL; use View; @@ -53,9 +52,11 @@ class BillController extends Controller } /** + * @param Request $request + * * @return View */ - public function create() + public function create(Request $request) { $periods = []; foreach (config('firefly.bill_periods') as $current) { @@ -68,52 +69,55 @@ class BillController extends Controller if (session('bills.create.fromStore') !== true) { $this->rememberPreviousUri('bills.create.uri'); } - Session::forget('bills.create.fromStore'); - Session::flash('gaEventCategory', 'bills'); - Session::flash('gaEventAction', 'create'); + $request->session()->forget('bills.create.fromStore'); + $request->session()->flash('gaEventCategory', 'bills'); + $request->session()->flash('gaEventAction', 'create'); return view('bills.create', compact('periods', 'subTitle')); } /** - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return View */ - public function delete(Bill $bill) + public function delete(Request $request, Bill $bill) { // put previous url in session $this->rememberPreviousUri('bills.delete.uri'); - Session::flash('gaEventCategory', 'bills'); - Session::flash('gaEventAction', 'delete'); + $request->session()->flash('gaEventCategory', 'bills'); + $request->session()->flash('gaEventAction', 'delete'); $subTitle = trans('firefly.delete_bill', ['name' => $bill->name]); return view('bills.delete', compact('bill', 'subTitle')); } /** + * @param Request $request * @param BillRepositoryInterface $repository * @param Bill $bill * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(BillRepositoryInterface $repository, Bill $bill) + public function destroy(Request $request, BillRepositoryInterface $repository, Bill $bill) { $name = $bill->name; $repository->destroy($bill); - Session::flash('success', strval(trans('firefly.deleted_bill', ['name' => $name]))); + $request->session()->flash('success', strval(trans('firefly.deleted_bill', ['name' => $name]))); Preferences::mark(); return redirect($this->getPreviousUri('bills.delete.uri')); } /** - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return View */ - public function edit(Bill $bill) + public function edit(Request $request, Bill $bill) { $periods = []; foreach (config('firefly.bill_periods') as $current) { @@ -125,9 +129,9 @@ class BillController extends Controller if (session('bills.edit.fromUpdate') !== true) { $this->rememberPreviousUri('bills.edit.uri'); } - Session::forget('bills.edit.fromUpdate'); - Session::flash('gaEventCategory', 'bills'); - Session::flash('gaEventAction', 'edit'); + $request->session()->forget('bills.edit.fromUpdate'); + $request->session()->flash('gaEventCategory', 'bills'); + $request->session()->flash('gaEventAction', 'edit'); return view('bills.edit', compact('subTitle', 'periods', 'bill')); } @@ -163,15 +167,16 @@ class BillController extends Controller } /** + * @param Request $request * @param BillRepositoryInterface $repository * @param Bill $bill * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function rescan(BillRepositoryInterface $repository, Bill $bill) + public function rescan(Request $request, BillRepositoryInterface $repository, Bill $bill) { if (intval($bill->active) == 0) { - Session::flash('warning', strval(trans('firefly.cannot_scan_inactive_bill'))); + $request->session()->flash('warning', strval(trans('firefly.cannot_scan_inactive_bill'))); return redirect(URL::previous()); } @@ -183,7 +188,7 @@ class BillController extends Controller } - Session::flash('success', strval(trans('firefly.rescanned_bill'))); + $request->session()->flash('success', strval(trans('firefly.rescanned_bill'))); Preferences::mark(); return redirect(URL::previous()); @@ -231,14 +236,15 @@ class BillController extends Controller { $billData = $request->getBillData(); $bill = $repository->store($billData); - Session::flash('success', strval(trans('firefly.stored_new_bill', ['name' => e($bill->name)]))); + $request->session()->flash('success', strval(trans('firefly.stored_new_bill', ['name' => e($bill->name)]))); Preferences::mark(); if (intval($request->get('create_another')) === 1) { - // set value so create routine will not overwrite URL: - Session::put('bills.create.fromStore', true); + // @codeCoverageIgnoreStart + $request->session()->put('bills.create.fromStore', true); return redirect(route('bills.create'))->withInput(); + // @codeCoverageIgnoreEnd } // redirect to previous URL. @@ -258,14 +264,15 @@ class BillController extends Controller $billData = $request->getBillData(); $bill = $repository->update($bill, $billData); - Session::flash('success', strval(trans('firefly.updated_bill', ['name' => e($bill->name)]))); + $request->session()->flash('success', strval(trans('firefly.updated_bill', ['name' => e($bill->name)]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: - Session::put('bills.edit.fromUpdate', true); + // @codeCoverageIgnoreStart + $request->session()->put('bills.edit.fromUpdate', true); return redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('bills.edit.uri')); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index f21bc904fa..05188a146e 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -22,14 +22,17 @@ use FireflyIII\Http\Requests\BudgetIncomeRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Log; +use Navigation; use Preferences; use Response; -use Session; use View; /** @@ -87,61 +90,66 @@ class BudgetController extends Controller } /** + * @param Request $request + * * @return View */ - public function create() + public function create(Request $request) { // put previous url in session if not redirect from store (not "create another"). if (session('budgets.create.fromStore') !== true) { $this->rememberPreviousUri('budgets.create.uri'); } - Session::forget('budgets.create.fromStore'); - Session::flash('gaEventCategory', 'budgets'); - Session::flash('gaEventAction', 'create'); + $request->session()->forget('budgets.create.fromStore'); + $request->session()->flash('gaEventCategory', 'budgets'); + $request->session()->flash('gaEventAction', 'create'); $subTitle = (string)trans('firefly.create_new_budget'); return view('budgets.create', compact('subTitle')); } /** - * @param Budget $budget + * @param Request $request + * @param Budget $budget * * @return View */ - public function delete(Budget $budget) + public function delete(Request $request, Budget $budget) { $subTitle = trans('firefly.delete_budget', ['name' => $budget->name]); // put previous url in session $this->rememberPreviousUri('budgets.delete.uri'); - Session::flash('gaEventCategory', 'budgets'); - Session::flash('gaEventAction', 'delete'); + $request->session()->flash('gaEventCategory', 'budgets'); + $request->session()->flash('gaEventAction', 'delete'); return view('budgets.delete', compact('budget', 'subTitle')); } /** - * @param Budget $budget + * @param Request $request + * @param Budget $budget * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(Budget $budget) + public function destroy(Request $request, Budget $budget) { $name = $budget->name; $this->repository->destroy($budget); - Session::flash('success', strval(trans('firefly.deleted_budget', ['name' => e($name)]))); + $request->session()->flash('success', strval(trans('firefly.deleted_budget', ['name' => e($name)]))); Preferences::mark(); return redirect($this->getPreviousUri('budgets.delete.uri')); } /** - * @param Budget $budget + * @param Request $request + * @param Budget $budget * * @return View */ - public function edit(Budget $budget) + public function edit(Request $request, Budget $budget) { $subTitle = trans('firefly.edit_budget', ['name' => $budget->name]); @@ -149,9 +157,9 @@ class BudgetController extends Controller if (session('budgets.edit.fromUpdate') !== true) { $this->rememberPreviousUri('budgets.edit.uri'); } - Session::forget('budgets.edit.fromUpdate'); - Session::flash('gaEventCategory', 'budgets'); - Session::flash('gaEventAction', 'edit'); + $request->session()->forget('budgets.edit.fromUpdate'); + $request->session()->flash('gaEventCategory', 'budgets'); + $request->session()->flash('gaEventAction', 'edit'); return view('budgets.edit', compact('budget', 'subTitle')); @@ -183,34 +191,88 @@ class BudgetController extends Controller } /** - * @param Request $request + * @param Request $request + * @param JournalRepositoryInterface $repository + * @param string $moment * * @return View */ - public function noBudget(Request $request) + public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '') { - /** @var Carbon $start */ - $start = session('start', Carbon::now()->startOfMonth()); - /** @var Carbon $end */ - $end = session('end', Carbon::now()->endOfMonth()); + // default values: + $range = Preferences::get('viewRange', '1M')->data; + $start = null; + $end = null; + $periods = new Collection; + + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_without_budget'); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; + } + + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.without_budget_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodOverview(); + } + + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->getPeriodOverview(); + $subTitle = trans( + 'firefly.without_budget_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitle = trans( - 'firefly.without_budget_between', - ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] - ); - // collector - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('/budgets/list/noBudget'); + $count = 0; + $loop = 0; + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at no-budget loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page) + ->withoutBudget()->withOpposingAccount(); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('/budgets/list/no-budget'); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } - return view('budgets.no-budget', compact('journals', 'subTitle')); + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.without_budget_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end')); } /** + * @param BudgetIncomeRequest $request + * * @return \Illuminate\Http\RedirectResponse */ public function postUpdateIncome(BudgetIncomeRequest $request) @@ -249,7 +311,7 @@ class BudgetController extends Controller $journals->setPath('/budgets/show/' . $budget->id); - $subTitle = e($budget->name); + $subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]); return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle')); } @@ -305,14 +367,15 @@ class BudgetController extends Controller $data = $request->getBudgetData(); $budget = $this->repository->store($data); - Session::flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)]))); + $request->session()->flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)]))); Preferences::mark(); if (intval($request->get('create_another')) === 1) { - // set value so create routine will not overwrite URL: - Session::put('budgets.create.fromStore', true); + // @codeCoverageIgnoreStart + $request->session()->put('budgets.create.fromStore', true); return redirect(route('budgets.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('budgets.create.uri')); @@ -329,14 +392,15 @@ class BudgetController extends Controller $data = $request->getBudgetData(); $this->repository->update($budget, $data); - Session::flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)]))); + $request->session()->flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: - Session::put('budgets.edit.fromUpdate', true); + // @codeCoverageIgnoreStart + $request->session()->put('budgets.edit.fromUpdate', true); return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('budgets.edit.uri')); @@ -399,7 +463,6 @@ class BudgetController extends Controller return $return; } - /** * @param Budget $budget * @param Carbon $start @@ -417,7 +480,7 @@ class BudgetController extends Controller $cache->addProperty('get-limits'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } /** @var AccountRepositoryInterface $accountRepository */ @@ -436,4 +499,57 @@ class BudgetController extends Controller return $set; } + /** + * @return Collection + */ + private function getPeriodOverview(): Collection + { + $repository = app(JournalRepositoryInterface::class); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('no-budget-period-entries'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + Log::debug('Going to get period expenses and incomes.'); + while ($end >= $start) { + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + + // count journals without budget in this period: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]); + $set = $collector->getJournals(); + $sum = $set->sum('transaction_amount'); + $journals = $set->count(); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $journals, + 'sum' => $sum, + 'date' => clone $end, + ] + ); + $end = Navigation::subtractPeriod($end, $range, 1); + } + $cache->store($entries); + + return $entries; + } + } diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 2224cb3512..a760a0733d 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -9,23 +9,27 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Http\Requests\CategoryFormRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Log; use Navigation; use Preferences; -use Session; +use Steam; use View; /** @@ -55,63 +59,68 @@ class CategoryController extends Controller } /** + * @param Request $request + * * @return View */ - public function create() + public function create(Request $request) { if (session('categories.create.fromStore') !== true) { $this->rememberPreviousUri('categories.create.uri'); } - Session::forget('categories.create.fromStore'); - Session::flash('gaEventCategory', 'categories'); - Session::flash('gaEventAction', 'create'); + $request->session()->forget('categories.create.fromStore'); + $request->session()->flash('gaEventCategory', 'categories'); + $request->session()->flash('gaEventAction', 'create'); $subTitle = trans('firefly.create_new_category'); return view('categories.create', compact('subTitle')); } /** + * @param Request $request * @param Category $category * * @return View */ - public function delete(Category $category) + public function delete(Request $request, Category $category) { $subTitle = trans('firefly.delete_category', ['name' => $category->name]); // put previous url in session $this->rememberPreviousUri('categories.delete.uri'); - Session::flash('gaEventCategory', 'categories'); - Session::flash('gaEventAction', 'delete'); + $request->session()->flash('gaEventCategory', 'categories'); + $request->session()->flash('gaEventAction', 'delete'); return view('categories.delete', compact('category', 'subTitle')); } /** + * @param Request $request * @param CategoryRepositoryInterface $repository * @param Category $category * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(CategoryRepositoryInterface $repository, Category $category) + public function destroy(Request $request, CategoryRepositoryInterface $repository, Category $category) { $name = $category->name; $repository->destroy($category); - Session::flash('success', strval(trans('firefly.deleted_category', ['name' => e($name)]))); + $request->session()->flash('success', strval(trans('firefly.deleted_category', ['name' => e($name)]))); Preferences::mark(); return redirect($this->getPreviousUri('categories.delete.uri')); } /** + * @param Request $request * @param Category $category * * @return View */ - public function edit(Category $category) + public function edit(Request $request, Category $category) { $subTitle = trans('firefly.edit_category', ['name' => $category->name]); @@ -119,9 +128,9 @@ class CategoryController extends Controller if (session('categories.edit.fromUpdate') !== true) { $this->rememberPreviousUri('categories.edit.uri'); } - Session::forget('categories.edit.fromUpdate'); - Session::flash('gaEventCategory', 'categories'); - Session::flash('gaEventAction', 'edit'); + $request->session()->forget('categories.edit.fromUpdate'); + $request->session()->flash('gaEventCategory', 'categories'); + $request->session()->flash('gaEventAction', 'edit'); return view('categories.edit', compact('category', 'subTitle')); @@ -146,118 +155,171 @@ class CategoryController extends Controller } /** - * @return View - */ - public function noCategory() - { - /** @var Carbon $start */ - $start = session('start', Carbon::now()->startOfMonth()); - /** @var Carbon $end */ - $end = session('end', Carbon::now()->startOfMonth()); - - // new collector: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals(); - $journals = $collector->getJournals(); - $subTitle = trans( - 'firefly.without_category_between', - ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] - ); - - return view('categories.no-category', compact('journals', 'subTitle')); - } - - /** - * @param Request $request - * @param JournalCollectorInterface $collector - * @param Category $category + * @param Request $request + * @param JournalRepositoryInterface $repository + * @param string $moment * * @return View */ - public function show(Request $request, JournalCollectorInterface $collector, Category $category) + public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '') { - $range = Preferences::get('viewRange', '1M')->data; - $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); - $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); - $hideCategory = true; // used in list. - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitle = $category->name; - $subTitleIcon = 'fa-bar-chart'; - $entries = $this->getGroupedEntries($category); - $method = 'default'; + // default values: + $range = Preferences::get('viewRange', '1M')->data; + $start = null; + $end = null; + $periods = new Collection; - // get journals - $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('categories/show/' . $category->id); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_without_category'); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; + } + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getNoCategoryPeriodOverview(); + } - return view('categories.show', compact('category', 'method', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->getNoCategoryPeriodOverview(); + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + + $count = 0; + $loop = 0; + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at no-cat loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount(); + $collector->removeFilter(InternalTransferFilter::class); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('/categories/list/no-category'); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } + + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end')); } /** * @param Request $request * @param CategoryRepositoryInterface $repository * @param Category $category + * @param string $moment * * @return View */ - public function showAll(Request $request, CategoryRepositoryInterface $repository, Category $category) + public function show(Request $request, CategoryRepositoryInterface $repository, Category $category, string $moment = '') { - $range = Preferences::get('viewRange', '1M')->data; - $start = $repository->firstUseDate($category); - if ($start->year == 1900) { - $start = new Carbon; - } - $end = Navigation::endOfPeriod(new Carbon, $range); + // default values: $subTitle = $category->name; $subTitleIcon = 'fa-bar-chart'; - $hideCategory = true; // used in list. - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $method = 'all'; - - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setCategory($category)->withBudgetInformation(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('categories/show/' . $category->id . '/all'); - - return view('categories.show', compact('category', 'method', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); - } - - /** - * @param Request $request - * @param Category $category - * @param string $date - * - * @return View - */ - public function showByDate(Request $request, Category $category, string $date) - { - $carbon = new Carbon($date); + $count = 0; + $loop = 0; $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $subTitle = $category->name; - $subTitleIcon = 'fa-bar-chart'; - $hideCategory = true; // used in list. - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $entries = $this->getGroupedEntries($category); - $method = 'date'; + $start = null; + $end = null; + $periods = new Collection; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('categories/show/' . $category->id . '/' . $date); - return view('categories.show', compact('category', 'method', 'entries', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]); + $start = $repository->firstUseDate($category); + $end = new Carbon; + } + + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.journals_in_period_for_category', + ['name' => $category->name, + 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodOverview($category); + } + + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->getPeriodOverview($category); + $subTitle = trans( + 'firefly.journals_in_period_for_category', + ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at category loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); + $collector->removeFilter(InternalTransferFilter::class); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('categories/show/' . $category->id); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } + + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.journals_in_period_for_category', + ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + return view('categories.show', compact('category', 'moment', 'journals', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); } + /** * @param CategoryFormRequest $request * @param CategoryRepositoryInterface $repository @@ -269,13 +331,15 @@ class CategoryController extends Controller $data = $request->getCategoryData(); $category = $repository->store($data); - Session::flash('success', strval(trans('firefly.stored_category', ['name' => e($category->name)]))); + $request->session()->flash('success', strval(trans('firefly.stored_category', ['name' => e($category->name)]))); Preferences::mark(); if (intval($request->get('create_another')) === 1) { - Session::put('categories.create.fromStore', true); + // @codeCoverageIgnoreStart + $request->session()->put('categories.create.fromStore', true); return redirect(route('categories.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect(route('categories.index')); @@ -294,24 +358,104 @@ class CategoryController extends Controller $data = $request->getCategoryData(); $repository->update($category, $data); - Session::flash('success', strval(trans('firefly.updated_category', ['name' => e($category->name)]))); + $request->session()->flash('success', strval(trans('firefly.updated_category', ['name' => e($category->name)]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - Session::put('categories.edit.fromUpdate', true); + // @codeCoverageIgnoreStart + $request->session()->put('categories.edit.fromUpdate', true); return redirect(route('categories.edit', [$category->id])); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('categories.edit.uri')); } + /** + * @return Collection + */ + private function getNoCategoryPeriodOverview(): Collection + { + $repository = app(JournalRepositoryInterface::class); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('no-budget-period-entries'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d'))); + while ($end >= $start) { + Log::debug('Loop!'); + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + + // count journals without budget in this period: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory() + ->withOpposingAccount(); + $collector->removeFilter(InternalTransferFilter::class); + $count = $collector->getJournals()->count(); + + // amount transferred + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory() + ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); + + // amount spent + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]); + $spent = $collector->getJournals()->sum('transaction_amount'); + + // amount earned + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]); + $earned = $collector->getJournals()->sum('transaction_amount'); + + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $count, + 'spent' => $spent, + 'earned' => $earned, + 'transferred' => $transferred, + 'date' => clone $end, + ] + ); + $end = Navigation::subtractPeriod($end, $range, 1); + } + Log::debug('End of loops'); + $cache->store($entries); + + return $entries; + } + /** * @param Category $category * * @return Collection */ - private function getGroupedEntries(Category $category): Collection + private function getPeriodOverview(Category $category): Collection { /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); @@ -335,7 +479,7 @@ class CategoryController extends Controller $cache->addProperty($category->id); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } while ($end >= $first) { $end = Navigation::startOfPeriod($end, $range); @@ -344,7 +488,26 @@ class CategoryController extends Controller $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd); $dateStr = $end->format('Y-m-d'); $dateName = Navigation::periodShow($end, $range); - $entries->push([$dateStr, $dateName, $spent, $earned, clone $end]); + + // amount transferred + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category) + ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); + + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'spent' => $spent, + 'earned' => $earned, + 'sum' => bcadd($earned, $spent), + 'transferred' => $transferred, + 'date' => clone $end, + ] + ); $end = Navigation::subtractPeriod($end, $range, 1); } $cache->store($entries); diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 5391d06883..38bd1f3345 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -26,6 +26,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Log; @@ -61,15 +62,12 @@ class AccountController extends Controller */ public function all(Account $account) { - $cache = new CacheProperties(); + $cache = new CacheProperties; $cache->addProperty('chart.account.all'); $cache->addProperty($account->id); if ($cache->has()) { - Log::debug('Return chart.account.all from cache.'); - - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } - Log::debug('Regenerate chart.account.all from scratch.'); /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); @@ -112,7 +110,7 @@ class AccountController extends Controller $cache->addProperty($end); $cache->addProperty('chart.account.expense-accounts'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $start->subDay(); @@ -139,14 +137,13 @@ class AccountController extends Controller } /** - * @param JournalCollectorInterface $collector - * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Account $account + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\JsonResponse */ - public function expenseBudget(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) + public function expenseBudget(Account $account, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($account->id); @@ -154,12 +151,10 @@ class AccountController extends Controller $cache->addProperty($end); $cache->addProperty('chart.account.expense-budget'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } - $collector->setAccounts(new Collection([$account])) - ->setRange($start, $end) - ->withBudgetInformation() - ->setTypes([TransactionType::WITHDRAWAL]); + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]); $transactions = $collector->getJournals(); $chartData = []; $result = []; @@ -185,14 +180,27 @@ class AccountController extends Controller } /** - * @param JournalCollectorInterface $collector - * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param AccountRepositoryInterface $repository + * @param Account $account * * @return \Illuminate\Http\JsonResponse */ - public function expenseCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) + public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account) + { + $start = $repository->oldestJournalDate($account); + $end = Carbon::now(); + + return $this->expenseBudget($account, $start, $end); + } + + /** + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return \Illuminate\Http\JsonResponse + */ + public function expenseCategory(Account $account, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($account->id); @@ -200,9 +208,10 @@ class AccountController extends Controller $cache->addProperty($end); $cache->addProperty('chart.account.expense-category'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } + $collector = app(JournalCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]); $transactions = $collector->getJournals(); $result = []; @@ -228,6 +237,20 @@ class AccountController extends Controller } + /** + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account) + { + $start = $repository->oldestJournalDate($account); + $end = Carbon::now(); + + return $this->expenseCategory($account, $start, $end); + } + /** * Shows the balances for all the user's frontpage accounts. * @@ -254,14 +277,13 @@ class AccountController extends Controller } /** - * @param JournalCollectorInterface $collector - * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Account $account + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\JsonResponse */ - public function incomeCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) + public function incomeCategory(Account $account, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($account->id); @@ -269,10 +291,11 @@ class AccountController extends Controller $cache->addProperty($end); $cache->addProperty('chart.account.income-category'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } // grab all journals: + $collector = app(JournalCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]); $transactions = $collector->getJournals(); $result = []; @@ -297,21 +320,29 @@ class AccountController extends Controller } + /** + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account) + { + $start = $repository->oldestJournalDate($account); + $end = Carbon::now(); + + return $this->incomeCategory($account, $start, $end); + } + /** * @param Account $account - * @param string $date + * @param Carbon $start * * @return \Illuminate\Http\JsonResponse * @throws FireflyException */ - public function period(Account $account, string $date) + public function period(Account $account, Carbon $start) { - try { - $start = new Carbon($date); - } catch (Exception $e) { - Log::error($e->getMessage()); - throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD'); - } $range = Preferences::get('viewRange', '1M')->data; $end = Navigation::endOfPeriod($start, $range); $cache = new CacheProperties(); @@ -320,7 +351,7 @@ class AccountController extends Controller $cache->addProperty('chart.account.period'); $cache->addProperty($account->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $format = (string)trans('config.month_and_day'); @@ -375,7 +406,7 @@ class AccountController extends Controller $cache->addProperty($end); $cache->addProperty('chart.account.revenue-accounts'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $accounts = $repository->getAccountsByType([AccountType::REVENUE]); @@ -421,7 +452,7 @@ class AccountController extends Controller $cache->addProperty('chart.account.single'); $cache->addProperty($account->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $format = (string)trans('config.month_and_day'); @@ -461,17 +492,20 @@ class AccountController extends Controller $cache->addProperty('chart.account.account-balance-chart'); $cache->addProperty($accounts); if ($cache->has()) { - Log::debug('Return chart.account.account-balance-chart from cache.'); - - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } Log::debug('Regenerate chart.account.account-balance-chart from scratch.'); + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $chartData = []; foreach ($accounts as $account) { + $currency = $repository->find(intval($account->getMeta('currency_id'))); $currentSet = [ - 'label' => $account->name, - 'entries' => [], + 'label' => $account->name, + 'currency_symbol' => $currency->symbol, + 'entries' => [], ]; $currentStart = clone $start; $range = Steam::balanceInRange($account, $start, clone $end); diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index 14588e0720..27a0ad5ad8 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -60,7 +60,7 @@ class BillController extends Controller $cache->addProperty($end); $cache->addProperty('chart.bill.frontpage'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. @@ -88,7 +88,7 @@ class BillController extends Controller $cache->addProperty('chart.bill.single'); $cache->addProperty($bill->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals(); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index cf99421aee..189c1e2dea 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -18,11 +18,14 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Navigation; @@ -80,7 +83,7 @@ class BudgetController extends Controller $cache->addProperty('chart.budget.budget'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $final = clone $last; @@ -133,7 +136,7 @@ class BudgetController extends Controller $cache->addProperty($budgetLimit->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $entries = []; @@ -153,6 +156,144 @@ class BudgetController extends Controller return Response::json($data); } + /** + * @param Budget $budget + * @param BudgetLimit|null $budgetLimit + * + * @return \Illuminate\Http\JsonResponse + */ + public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null) + { + $cache = new CacheProperties; + $cache->addProperty($budget->id); + $cache->addProperty($budgetLimit->id ?? 0); + $cache->addProperty('chart.budget.expense-asset'); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget); + if (!is_null($budgetLimit->id)) { + $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); + } + + $transactions = $collector->getJournals(); + $result = []; + $chartData = []; + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $assetId = intval($transaction->account_id); + $result[$assetId] = $result[$assetId] ?? '0'; + $result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]); + } + + $names = $this->getAccountNames(array_keys($result)); + foreach ($result as $assetId => $amount) { + $chartData[$names[$assetId]] = $amount; + } + + $data = $this->generator->pieChart($chartData); + $cache->store($data); + + return Response::json($data); + + } + + /** + * @param Budget $budget + * @param BudgetLimit|null $budgetLimit + * + * @return \Illuminate\Http\JsonResponse + */ + public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null) + { + $cache = new CacheProperties; + $cache->addProperty($budget->id); + $cache->addProperty($budgetLimit->id ?? 0); + $cache->addProperty('chart.budget.expense-category'); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withCategoryInformation(); + if (!is_null($budgetLimit->id)) { + $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); + } + + $transactions = $collector->getJournals(); + $result = []; + $chartData = []; + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $jrnlCatId = intval($transaction->transaction_journal_category_id); + $transCatId = intval($transaction->transaction_category_id); + $categoryId = max($jrnlCatId, $transCatId); + $result[$categoryId] = $result[$categoryId] ?? '0'; + $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); + } + + $names = $this->getCategoryNames(array_keys($result)); + foreach ($result as $categoryId => $amount) { + $chartData[$names[$categoryId]] = $amount; + } + + $data = $this->generator->pieChart($chartData); + $cache->store($data); + + return Response::json($data); + + } + + /** + * @param Budget $budget + * @param BudgetLimit|null $budgetLimit + * + * @return \Illuminate\Http\JsonResponse + */ + public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null) + { + $cache = new CacheProperties; + $cache->addProperty($budget->id); + $cache->addProperty($budgetLimit->id ?? 0); + $cache->addProperty('chart.budget.expense-expense'); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount(); + if (!is_null($budgetLimit->id)) { + $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); + } + + $transactions = $collector->getJournals(); + $result = []; + $chartData = []; + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $opposingId = intval($transaction->opposing_account_id); + $result[$opposingId] = $result[$opposingId] ?? '0'; + $result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]); + } + + $names = $this->getAccountNames(array_keys($result)); + foreach ($result as $opposingId => $amount) { + $name = $names[$opposingId] ?? 'no name'; + $chartData[$name] = $amount; + } + + $data = $this->generator->pieChart($chartData); + $cache->store($data); + + return Response::json($data); + + } + /** * Shows a budget list with spent/left/overspent. * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. @@ -170,7 +311,7 @@ class BudgetController extends Controller $cache->addProperty($end); $cache->addProperty('chart.budget.frontpage'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $budgets = $this->repository->getActiveBudgets(); $chartData = [ @@ -227,7 +368,7 @@ class BudgetController extends Controller $cache->addProperty($budget->id); $cache->addProperty('chart.budget.period'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $periods = Navigation::listOfPeriods($start, $end); $entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses @@ -268,7 +409,7 @@ class BudgetController extends Controller $cache->addProperty($accounts); $cache->addProperty('chart.budget.no-budget'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } // the expenses: @@ -288,6 +429,28 @@ class BudgetController extends Controller return Response::json($data); } + /** + * @param array $accountIds + * + * @return array + */ + private function getAccountNames(array $accountIds): array + { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE]); + $grouped = $accounts->groupBy('id')->toArray(); + $return = []; + foreach ($accountIds as $accountId) { + if (isset($grouped[$accountId])) { + $return[$accountId] = $grouped[$accountId][0]['name']; + } + } + $return[0] = '(no name)'; + + return $return; + } + /** * @param Budget $budget * @param Carbon $start @@ -314,6 +477,30 @@ class BudgetController extends Controller return $budgeted; } + /** + * Small helper function for some of the charts. + * + * @param array $categoryIds + * + * @return array + */ + private function getCategoryNames(array $categoryIds): array + { + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); + $categories = $repository->getCategories(); + $grouped = $categories->groupBy('id')->toArray(); + $return = []; + foreach ($categoryIds as $categoryId) { + if (isset($grouped[$categoryId])) { + $return[$categoryId] = $grouped[$categoryId][0]['name']; + } + } + $return[0] = trans('firefly.noCategory'); + + return $return; + } + /** * * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok. @@ -386,9 +573,14 @@ class BudgetController extends Controller ] ); } + /* + * amount: amount of budget limit + * left: amount of budget limit min spent, or 0 when < 0. + * spent: spent, or amount of budget limit when > amount + */ $amount = $budgetLimit->amount; $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); - $spent = $expenses; + $spent = bccomp($expenses, $amount) === 1 ? $expenses : bcmul($amount, '-1'); $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; $return[$name] = [ 'left' => $left, diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index d570eae6b8..091448e599 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -9,22 +9,23 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; -use FireflyIII\Generator\Report\Category\MonthReportGenerator; use FireflyIII\Helpers\Chart\MetaPieChartInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; @@ -41,8 +42,6 @@ use Response; class BudgetReportController extends Controller { - /** @var AccountRepositoryInterface */ - private $accountRepository; /** @var BudgetRepositoryInterface */ private $budgetRepository; /** @var GeneratorInterface */ @@ -56,9 +55,8 @@ class BudgetReportController extends Controller parent::__construct(); $this->middleware( function ($request, $next) { - $this->generator = app(GeneratorInterface::class); - $this->budgetRepository = app(BudgetRepositoryInterface::class); - $this->accountRepository = app(AccountRepositoryInterface::class); + $this->generator = app(GeneratorInterface::class); + $this->budgetRepository = app(BudgetRepositoryInterface::class); return $next($request); } @@ -131,10 +129,8 @@ class BudgetReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); $format = Navigation::preferredCarbonLocalizedFormat($start, $end); $function = Navigation::preferredEndOfPeriod($start, $end); $chartData = []; @@ -163,7 +159,7 @@ class BudgetReportController extends Controller 'entries' => [], ]; } - $allBudgetLimits = $repository->getAllBudgetLimits($start, $end); + $allBudgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end); $sumOfExpenses = []; $leftOfLimits = []; while ($currentStart < $end) { @@ -203,6 +199,7 @@ class BudgetReportController extends Controller * Returns the budget limits belonging to the given budget and valid on the given day. * * @param Collection $budgetLimits + * @param Budget $budget * @param Carbon $start * @param Carbon $end * @@ -239,12 +236,15 @@ class BudgetReportController extends Controller /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setBudgets($budgets)->withOpposingAccount()->disableFilter(); - $accountIds = $accounts->pluck('id')->toArray(); - $transactions = $collector->getJournals(); - $set = MonthReportGenerator::filterExpenses($transactions, $accountIds); + ->setBudgets($budgets)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); - return $set; + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); + + $transactions = $collector->getJournals(); + + return $transactions; } /** @@ -268,21 +268,4 @@ class BudgetReportController extends Controller return $grouped; } - /** - * @param Collection $set - * - * @return array - */ - private function groupByOpposingAccount(Collection $set): array - { - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $accountId = $transaction->opposing_account_id; - $grouped[$accountId] = $grouped[$accountId] ?? '0'; - $grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]); - } - - return $grouped; - } } diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 217d122bc8..d330a9b363 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -20,7 +20,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Navigation; @@ -50,19 +50,19 @@ class CategoryController extends Controller /** * Show an overview for a category for all time, per month/week/year. * - * @param CRI $repository - * @param AccountRepositoryInterface $accountRepository - * @param Category $category + * @param CategoryRepositoryInterface $repository + * @param AccountRepositoryInterface $accountRepository + * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ - public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category) + public function all(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Category $category) { $cache = new CacheProperties; $cache->addProperty('chart.category.all'); $cache->addProperty($category->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $start = $repository->firstUseDate($category); @@ -86,15 +86,23 @@ class CategoryController extends Controller 'entries' => [], 'type' => 'bar', ], + [ + 'label' => strval(trans('firefly.sum')), + 'entries' => [], + 'type' => 'line', + 'fill' => false, + ], ]; while ($start <= $end) { $currentEnd = Navigation::endOfPeriod($start, $range); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd); + $sum = bcadd($spent, $earned); $label = Navigation::periodShow($start, $range); - $chartData[0]['entries'][$label] = bcmul($spent, '-1'); - $chartData[1]['entries'][$label] = $earned; + $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12); + $chartData[1]['entries'][$label] = round($earned, 12); + $chartData[2]['entries'][$label] = round($sum, 12); $start = Navigation::addPeriod($start, $range, 0); } @@ -106,27 +114,12 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param Category $category - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function currentPeriod(CRI $repository, Category $category) - { - $start = clone session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); - $data = $this->makePeriodChart($repository, $category, $start, $end); - - return Response::json($data); - } - - /** - * @param CRI $repository - * @param AccountRepositoryInterface $accountRepository + * @param CategoryRepositoryInterface $repository + * @param AccountRepositoryInterface $accountRepository * * @return \Illuminate\Http\JsonResponse */ - public function frontpage(CRI $repository, AccountRepositoryInterface $accountRepository) + public function frontpage(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository) { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -136,7 +129,7 @@ class CategoryController extends Controller $cache->addProperty($end); $cache->addProperty('chart.category.frontpage'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $chartData = []; $categories = $repository->getCategories(); @@ -161,15 +154,15 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param Category $category - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param CategoryRepositoryInterface $repository + * @param Category $category + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\JsonResponse|mixed */ - public function reportPeriod(CRI $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end) + public function reportPeriod(CategoryRepositoryInterface $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($start); @@ -178,7 +171,7 @@ class CategoryController extends Controller $cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($category); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end); $income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end); @@ -194,13 +187,22 @@ class CategoryController extends Controller 'entries' => [], 'type' => 'bar', ], + [ + 'label' => strval(trans('firefly.sum')), + 'entries' => [], + 'type' => 'line', + 'fill' => false, + ], ]; foreach (array_keys($periods) as $period) { $label = $periods[$period]; $spent = $expenses[$category->id]['entries'][$period] ?? '0'; - $chartData[0]['entries'][$label] = bcmul($spent, '-1'); - $chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0'; + $earned = $income[$category->id]['entries'][$period] ?? '0'; + $sum = bcadd($spent, $earned); + $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12); + $chartData[1]['entries'][$label] = round($earned, 12); + $chartData[2]['entries'][$label] = round($sum, 12); } $data = $this->generator->multiSet($chartData); @@ -210,14 +212,14 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param CategoryRepositoryInterface $repository + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\JsonResponse|mixed */ - public function reportPeriodNoCategory(CRI $repository, Collection $accounts, Carbon $start, Carbon $end) + public function reportPeriodNoCategory(CategoryRepositoryInterface $repository, Collection $accounts, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($start); @@ -225,7 +227,7 @@ class CategoryController extends Controller $cache->addProperty('chart.category.period.no-cat'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $expenses = $repository->periodExpensesNoCategory($accounts, $start, $end); $income = $repository->periodIncomeNoCategory($accounts, $start, $end); @@ -241,13 +243,22 @@ class CategoryController extends Controller 'entries' => [], 'type' => 'bar', ], + [ + 'label' => strval(trans('firefly.sum')), + 'entries' => [], + 'type' => 'line', + 'fill' => false, + ], ]; foreach (array_keys($periods) as $period) { $label = $periods[$period]; $spent = $expenses['entries'][$period] ?? '0'; + $earned = $income['entries'][$period] ?? '0'; + $sum = bcadd($spent, $earned); $chartData[0]['entries'][$label] = bcmul($spent, '-1'); - $chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0'; + $chartData[1]['entries'][$label] = $earned; + $chartData[2]['entries'][$label] = $sum; } $data = $this->generator->multiSet($chartData); @@ -257,19 +268,18 @@ class CategoryController extends Controller } /** - * @param CRI $repository + * @param CategoryRepositoryInterface $repository * @param Category $category * * @param $date * * @return \Symfony\Component\HttpFoundation\Response */ - public function specificPeriod(CRI $repository, Category $category, $date) + public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date) { - $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); + $start = Navigation::startOfPeriod($date, $range); + $end = Navigation::endOfPeriod($date, $range); $data = $this->makePeriodChart($repository, $category, $start, $end); return Response::json($data); @@ -277,14 +287,14 @@ class CategoryController extends Controller /** - * @param CRI $repository - * @param Category $category - * @param Carbon $start - * @param Carbon $end + * @param CategoryRepositoryInterface $repository + * @param Category $category + * @param Carbon $start + * @param Carbon $end * * @return array */ - private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end) + private function makePeriodChart(CategoryRepositoryInterface $repository, Category $category, Carbon $start, Carbon $end) { $cache = new CacheProperties; $cache->addProperty($start); @@ -297,7 +307,7 @@ class CategoryController extends Controller $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // chart data @@ -312,15 +322,23 @@ class CategoryController extends Controller 'entries' => [], 'type' => 'bar', ], + [ + 'label' => strval(trans('firefly.sum')), + 'entries' => [], + 'type' => 'line', + 'fill' => false, + ], ]; while ($start <= $end) { $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start); - $label = Navigation::periodShow($start, '1D'); + $sum = bcadd($spent, $earned); + $label = trim(Navigation::periodShow($start, '1D')); - $chartData[0]['entries'][$label] = bcmul($spent, '-1'); - $chartData[1]['entries'][$label] = $earned; + $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'),12); + $chartData[1]['entries'][$label] = round($earned,12); + $chartData[2]['entries'][$label] = round($sum,12); $start->addDay(); diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index 94f6cfb370..572a8a3432 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -19,12 +19,14 @@ use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Report\Category\MonthReportGenerator; use FireflyIII\Helpers\Chart\MetaPieChartInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\OpposingAccountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Navigation; @@ -41,10 +43,6 @@ use Response; class CategoryReportController extends Controller { - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** @var CategoryRepositoryInterface */ - private $categoryRepository; /** @var GeneratorInterface */ private $generator; @@ -56,9 +54,7 @@ class CategoryReportController extends Controller parent::__construct(); $this->middleware( function ($request, $next) { - $this->generator = app(GeneratorInterface::class); - $this->categoryRepository = app(CategoryRepositoryInterface::class); - $this->accountRepository = app(AccountRepositoryInterface::class); + $this->generator = app(GeneratorInterface::class); return $next($request); } @@ -78,11 +74,8 @@ class CategoryReportController extends Controller { /** @var MetaPieChartInterface $helper */ $helper = app(MetaPieChartInterface::class); - $helper->setAccounts($accounts); - $helper->setCategories($categories); - $helper->setStart($start); - $helper->setEnd($end); - $helper->setCollectOtherObjects(intval($others) === 1); + $helper->setAccounts($accounts)->setCategories($categories)->setStart($start)->setEnd($end)->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'account'); $data = $this->generator->pieChart($chartData); @@ -179,7 +172,7 @@ class CategoryReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $format = Navigation::preferredCarbonLocalizedFormat($start, $end); @@ -282,12 +275,15 @@ class CategoryReportController extends Controller /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setCategories($categories)->withOpposingAccount()->disableFilter(); - $accountIds = $accounts->pluck('id')->toArray(); - $transactions = $collector->getJournals(); - $set = MonthReportGenerator::filterExpenses($transactions, $accountIds); + ->setCategories($categories)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); - return $set; + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); + + $transactions = $collector->getJournals(); + + return $transactions; } /** @@ -304,11 +300,13 @@ class CategoryReportController extends Controller $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->setCategories($categories)->withOpposingAccount(); - $accountIds = $accounts->pluck('id')->toArray(); - $transactions = $collector->getJournals(); - $set = MonthReportGenerator::filterIncome($transactions, $accountIds); - return $set; + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(NegativeAmountFilter::class); + + $transactions = $collector->getJournals(); + + return $transactions; } /** @@ -331,22 +329,4 @@ class CategoryReportController extends Controller return $grouped; } - - /** - * @param Collection $set - * - * @return array - */ - private function groupByOpposingAccount(Collection $set): array - { - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $accountId = $transaction->opposing_account_id; - $grouped[$accountId] = $grouped[$accountId] ?? '0'; - $grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]); - } - - return $grouped; - } } diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php index aa11d8bbf0..7cec7acf2d 100644 --- a/app/Http/Controllers/Chart/PiggyBankController.php +++ b/app/Http/Controllers/Chart/PiggyBankController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -58,7 +58,7 @@ class PiggyBankController extends Controller $cache->addProperty('chart.piggy-bank.history'); $cache->addProperty($piggyBank->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $set = $repository->getEvents($piggyBank); diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index 03833b4504..1452192fe3 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Chart; @@ -20,6 +20,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; +use Log; use Navigation; use Response; use Steam; @@ -64,7 +65,7 @@ class ReportController extends Controller $cache->addProperty($accounts); $cache->addProperty($end); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } $ids = $accounts->pluck('id')->toArray(); $current = clone $start; @@ -103,8 +104,9 @@ class ReportController extends Controller $cache->addProperty($accounts); $cache->addProperty($end); if ($cache->has()) { - return Response::json($cache->get()); + //return Response::json($cache->get()); // @codeCoverageIgnore } + Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray()); $format = Navigation::preferredCarbonLocalizedFormat($start, $end); $source = $this->getChartData($accounts, $start, $end); $chartData = [ @@ -161,8 +163,10 @@ class ReportController extends Controller $cache->addProperty($end); $cache->addProperty($accounts); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } + + $source = $this->getChartData($accounts, $start, $end); $numbers = [ 'sum_earned' => '0', @@ -246,19 +250,41 @@ class ReportController extends Controller $cache->addProperty($accounts); $cache->addProperty($end); if ($cache->has()) { - return $cache->get(); + // return $cache->get(); // @codeCoverageIgnore } - - $tasker = app(AccountTaskerInterface::class); $currentStart = clone $start; $spentArray = []; $earnedArray = []; + + /** @var AccountTaskerInterface $tasker */ + $tasker = app(AccountTaskerInterface::class); + while ($currentStart <= $end) { - $currentEnd = Navigation::endOfPeriod($currentStart, '1M'); + + $currentEnd = Navigation::endOfPeriod($currentStart, '1M'); + $earned = strval( + array_sum( + array_map( + function ($item) { + return $item['sum']; + }, $tasker->getIncomeReport($currentStart, $currentEnd, $accounts) + ) + ) + ); + + $spent = strval( + array_sum( + array_map( + function ($item) { + return $item['sum']; + }, $tasker->getExpenseReport($currentStart, $currentEnd, $accounts) + ) + ) + ); + + $label = $currentStart->format('Y-m') . '-01'; - $spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd); - $earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd); $spentArray[$label] = bcmul($spent, '-1'); $earnedArray[$label] = $earned; $currentStart = Navigation::addPeriod($currentStart, '1M', 0); diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php new file mode 100644 index 0000000000..2aca50bc77 --- /dev/null +++ b/app/Http/Controllers/Chart/TagReportController.php @@ -0,0 +1,367 @@ +generator = app(GeneratorInterface::class); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * @param string $others + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others) + { + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'account'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * @param string $others + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others) + { + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('income', 'account'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return \Illuminate\Http\JsonResponse + */ + public function budgetExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) + { + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(false); + $chartData = $helper->generate('expense', 'budget'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return \Illuminate\Http\JsonResponse + */ + public function categoryExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) + { + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(false); + $chartData = $helper->generate('expense', 'category'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return \Illuminate\Http\JsonResponse + */ + public function mainChart(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) + { + $cache = new CacheProperties; + $cache->addProperty('chart.category.report.main'); + $cache->addProperty($accounts); + $cache->addProperty($tags); + $cache->addProperty($start); + $cache->addProperty($end); + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + $format = Navigation::preferredCarbonLocalizedFormat($start, $end); + $function = Navigation::preferredEndOfPeriod($start, $end); + $chartData = []; + $currentStart = clone $start; + + // prep chart data: + foreach ($tags as $tag) { + $chartData[$tag->id . '-in'] = [ + 'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.income'))) . ')', + 'type' => 'bar', + 'yAxisID' => 'y-axis-0', + 'entries' => [], + ]; + $chartData[$tag->id . '-out'] = [ + 'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')', + 'type' => 'bar', + 'yAxisID' => 'y-axis-0', + 'entries' => [], + ]; + // total in, total out: + $chartData[$tag->id . '-total-in'] = [ + 'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_income'))) . ')', + 'type' => 'line', + 'fill' => false, + 'yAxisID' => 'y-axis-1', + 'entries' => [], + ]; + $chartData[$tag->id . '-total-out'] = [ + 'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_expenses'))) . ')', + 'type' => 'line', + 'fill' => false, + 'yAxisID' => 'y-axis-1', + 'entries' => [], + ]; + } + $sumOfIncome = []; + $sumOfExpense = []; + + while ($currentStart < $end) { + $currentEnd = clone $currentStart; + $currentEnd = $currentEnd->$function(); + $expenses = $this->groupByTag($this->getExpenses($accounts, $tags, $currentStart, $currentEnd)); + $income = $this->groupByTag($this->getIncome($accounts, $tags, $currentStart, $currentEnd)); + $label = $currentStart->formatLocalized($format); + + /** @var Tag $tag */ + foreach ($tags as $tag) { + $labelIn = $tag->id . '-in'; + $labelOut = $tag->id . '-out'; + $labelSumIn = $tag->id . '-total-in'; + $labelSumOut = $tag->id . '-total-out'; + $currentIncome = $income[$tag->id] ?? '0'; + $currentExpense = $expenses[$tag->id] ?? '0'; + + + // add to sum: + $sumOfIncome[$tag->id] = $sumOfIncome[$tag->id] ?? '0'; + $sumOfExpense[$tag->id] = $sumOfExpense[$tag->id] ?? '0'; + $sumOfIncome[$tag->id] = bcadd($sumOfIncome[$tag->id], $currentIncome); + $sumOfExpense[$tag->id] = bcadd($sumOfExpense[$tag->id], $currentExpense); + + // add to chart: + $chartData[$labelIn]['entries'][$label] = $currentIncome; + $chartData[$labelOut]['entries'][$label] = $currentExpense; + $chartData[$labelSumIn]['entries'][$label] = $sumOfIncome[$tag->id]; + $chartData[$labelSumOut]['entries'][$label] = $sumOfExpense[$tag->id]; + } + $currentStart = clone $currentEnd; + $currentStart->addDay(); + } + // remove all empty entries to prevent cluttering: + $newSet = []; + foreach ($chartData as $key => $entry) { + if (!array_sum($entry['entries']) == 0) { + $newSet[$key] = $chartData[$key]; + } + } + if (count($newSet) === 0) { + $newSet = $chartData; // @codeCoverageIgnore + } + $data = $this->generator->multiSet($newSet); + $cache->store($data); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * @param string $others + * + * @return \Illuminate\Http\JsonResponse + */ + public function tagExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others) + { + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('expense', 'tag'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * @param string $others + * + * @return \Illuminate\Http\JsonResponse + */ + public function tagIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others) + { + + /** @var MetaPieChartInterface $helper */ + $helper = app(MetaPieChartInterface::class); + $helper->setAccounts($accounts); + $helper->setTags($tags); + $helper->setStart($start); + $helper->setEnd($end); + $helper->setCollectOtherObjects(intval($others) === 1); + $chartData = $helper->generate('income', 'tag'); + $data = $this->generator->pieChart($chartData); + + return Response::json($data); + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + private function getExpenses(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setTags($tags)->withOpposingAccount(); + $collector->removeFilter(TransferFilter::class); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(PositiveAmountFilter::class); + + $transactions = $collector->getJournals(); + + return $transactions; + } + + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + private function getIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->setTags($tags)->withOpposingAccount(); + + $collector->addFilter(OpposingAccountFilter::class); + $collector->addFilter(NegativeAmountFilter::class); + + $transactions = $collector->getJournals(); + + return $transactions; + } + + /** + * @param Collection $set + * + * @return array + */ + private function groupByTag(Collection $set): array + { + // group by category ID: + $grouped = []; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + $journal = $transaction->transactionJournal; + $journalTags = $journal->tags; + /** @var Tag $journalTag */ + foreach ($journalTags as $journalTag) { + $journalTagId = $journalTag->id; + $grouped[$journalTagId] = $grouped[$journalTagId] ?? '0'; + $grouped[$journalTagId] = bcadd($transaction->transaction_amount, $grouped[$journalTagId]); + } + } + + return $grouped; + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index bbf6cb51d5..63cbe9c975 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -100,7 +100,7 @@ class Controller extends BaseController */ protected function isOpeningBalance(TransactionJournal $journal): bool { - return TransactionJournal::transactionTypeStr($journal) === TransactionType::OPENING_BALANCE; + return $journal->transactionTypeStr() === TransactionType::OPENING_BALANCE; } /** @@ -120,9 +120,11 @@ class Controller extends BaseController } } + // @codeCoverageIgnoreStart Session::flash('error', strval(trans('firefly.cannot_redirect_to_account'))); return redirect(route('index')); + // @codeCoverageIgnoreEnd } /** diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index 01cb6abd3e..7590ff821a 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -17,9 +17,10 @@ use Cache; use FireflyIII\Http\Requests\CurrencyFormRequest; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Http\Request; use Log; use Preferences; -use Session; use View; /** @@ -30,6 +31,11 @@ use View; class CurrencyController extends Controller { + /** @var CurrencyRepositoryInterface */ + protected $repository; + + /** @var UserRepositoryInterface */ + protected $userRepository; /** * @@ -43,6 +49,8 @@ class CurrencyController extends Controller function ($request, $next) { View::share('title', trans('firefly.currencies')); View::share('mainTitleIcon', 'fa-usd'); + $this->repository = app(CurrencyRepositoryInterface::class); + $this->userRepository = app(UserRepositoryInterface::class); return $next($request); } @@ -50,10 +58,18 @@ class CurrencyController extends Controller } /** - * @return View + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function create() + public function create(Request $request) { + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + + return redirect(route('currencies.index')); + } + $subTitleIcon = 'fa-plus'; $subTitle = trans('firefly.create_currency'); @@ -61,25 +77,26 @@ class CurrencyController extends Controller if (session('currencies.create.fromStore') !== true) { $this->rememberPreviousUri('currencies.create.uri'); } - Session::forget('currencies.create.fromStore'); - Session::flash('gaEventCategory', 'currency'); - Session::flash('gaEventAction', 'create'); + $request->session()->forget('currencies.create.fromStore'); + $request->session()->flash('gaEventCategory', 'currency'); + $request->session()->flash('gaEventAction', 'create'); return view('currencies.create', compact('subTitleIcon', 'subTitle')); } /** + * @param Request $request * @param TransactionCurrency $currency * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function defaultCurrency(TransactionCurrency $currency) + public function defaultCurrency(Request $request, TransactionCurrency $currency) { Preferences::set('currencyPreference', $currency->code); Preferences::mark(); - Session::flash('success', trans('firefly.new_default_currency', ['name' => $currency->name])); + $request->session()->flash('success', trans('firefly.new_default_currency', ['name' => $currency->name])); Cache::forget('FFCURRENCYSYMBOL'); Cache::forget('FFCURRENCYCODE'); @@ -89,15 +106,23 @@ class CurrencyController extends Controller /** - * @param CurrencyRepositoryInterface $repository - * @param TransactionCurrency $currency + * @param Request $request + * @param TransactionCurrency $currency * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function delete(CurrencyRepositoryInterface $repository, TransactionCurrency $currency) + public function delete(Request $request, TransactionCurrency $currency) { - if (!$repository->canDeleteCurrency($currency)) { - Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + // @codeCoverageIgnoreStart + $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + + return redirect(route('currencies.index')); + // @codeCoverageIgnoreEnd + } + + if (!$this->repository->canDeleteCurrency($currency)) { + $request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); return redirect(route('currencies.index')); } @@ -105,8 +130,8 @@ class CurrencyController extends Controller // put previous url in session $this->rememberPreviousUri('currencies.delete.uri'); - Session::flash('gaEventCategory', 'currency'); - Session::flash('gaEventAction', 'delete'); + $request->session()->flash('gaEventCategory', 'currency'); + $request->session()->flash('gaEventAction', 'delete'); $subTitle = trans('form.delete_currency', ['name' => $currency->name]); @@ -114,32 +139,49 @@ class CurrencyController extends Controller } /** - * @param CurrencyRepositoryInterface $repository - * @param TransactionCurrency $currency + * @param Request $request + * @param TransactionCurrency $currency * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(CurrencyRepositoryInterface $repository, TransactionCurrency $currency) + public function destroy(Request $request, TransactionCurrency $currency) { - if (!$repository->canDeleteCurrency($currency)) { - Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + // @codeCoverageIgnoreStart + $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + + return redirect(route('currencies.index')); + // @codeCoverageIgnoreEnd + } + + if (!$this->repository->canDeleteCurrency($currency)) { + $request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); return redirect(route('currencies.index')); } - $repository->destroy($currency); - Session::flash('success', trans('firefly.deleted_currency', ['name' => $currency->name])); + $this->repository->destroy($currency); + $request->session()->flash('success', trans('firefly.deleted_currency', ['name' => $currency->name])); return redirect($this->getPreviousUri('currencies.delete.uri')); } /** + * @param Request $request * @param TransactionCurrency $currency * - * @return View + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function edit(TransactionCurrency $currency) + public function edit(Request $request, TransactionCurrency $currency) { + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + // @codeCoverageIgnoreStart + $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + + return redirect(route('currencies.index')); + // @codeCoverageIgnoreEnd + } + $subTitleIcon = 'fa-pencil'; $subTitle = trans('breadcrumbs.edit_currency', ['name' => $currency->name]); $currency->symbol = htmlentities($currency->symbol); @@ -148,82 +190,88 @@ class CurrencyController extends Controller if (session('currencies.edit.fromUpdate') !== true) { $this->rememberPreviousUri('currencies.edit.uri'); } - Session::forget('currencies.edit.fromUpdate'); - Session::flash('gaEventCategory', 'currency'); - Session::flash('gaEventAction', 'edit'); + $request->session()->forget('currencies.edit.fromUpdate'); + $request->session()->flash('gaEventCategory', 'currency'); + $request->session()->flash('gaEventAction', 'edit'); return view('currencies.edit', compact('currency', 'subTitle', 'subTitleIcon')); } /** - * @param CurrencyRepositoryInterface $repository + * @param Request $request * * @return View */ - public function index(CurrencyRepositoryInterface $repository) + public function index(Request $request) { - $currencies = $repository->get(); - $defaultCurrency = $repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'))); - - - if (!auth()->user()->hasRole('owner')) { - Session::flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + $currencies = $this->repository->get(); + $defaultCurrency = $this->repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'))); + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + $request->session()->flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); } - return view('currencies.index', compact('currencies', 'defaultCurrency')); } /** + * @param CurrencyFormRequest $request * - * @param CurrencyFormRequest $request - * @param CurrencyRepositoryInterface $repository - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function store(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository) + public function store(CurrencyFormRequest $request) { - if (!auth()->user()->hasRole('owner')) { + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + // @codeCoverageIgnoreStart Log::error('User ' . auth()->user()->id . ' is not admin, but tried to store a currency.'); return redirect($this->getPreviousUri('currencies.create.uri')); + // @codeCoverageIgnoreEnd } $data = $request->getCurrencyData(); - $currency = $repository->store($data); - Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name])); + $currency = $this->repository->store($data); + $request->session()->flash('success', trans('firefly.created_currency', ['name' => $currency->name])); if (intval($request->get('create_another')) === 1) { - Session::put('currencies.create.fromStore', true); + // @codeCoverageIgnoreStart + $request->session()->put('currencies.create.fromStore', true); return redirect(route('currencies.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('currencies.create.uri')); } /** - * @param CurrencyFormRequest $request - * @param CurrencyRepositoryInterface $repository - * @param TransactionCurrency $currency + * @param CurrencyFormRequest $request + * @param TransactionCurrency $currency * - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency) + public function update(CurrencyFormRequest $request, TransactionCurrency $currency) { - $data = $request->getCurrencyData(); - if (auth()->user()->hasRole('owner')) { - $currency = $repository->update($currency, $data); + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + // @codeCoverageIgnoreStart + $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); + + return redirect(route('currencies.index')); + // @codeCoverageIgnoreEnd } - Session::flash('success', trans('firefly.updated_currency', ['name' => $currency->name])); + + $data = $request->getCurrencyData(); + $currency = $this->repository->update($currency, $data); + $request->session()->flash('success', trans('firefly.updated_currency', ['name' => $currency->name])); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - Session::put('currencies.edit.fromUpdate', true); + // @codeCoverageIgnoreStart + $request->session()->put('currencies.edit.fromUpdate', true); return redirect(route('currencies.edit', [$currency->id])); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('currencies.edit.uri')); diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 823c82e928..6bb4cd3f44 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -23,7 +23,6 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\ExportJob; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; -use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface as EJRI; use Illuminate\Http\Response as LaravelResponse; use Preferences; use Response; @@ -55,9 +54,10 @@ class ExportController extends Controller } /** - * @param ExportJob $job + * @param ExportJobRepositoryInterface $repository + * @param ExportJob $job * - * @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory + * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response * @throws FireflyException */ public function download(ExportJobRepositoryInterface $repository, ExportJob $job) @@ -102,12 +102,12 @@ class ExportController extends Controller } /** - * @param AccountRepositoryInterface $repository - * @param EJRI $jobs + * @param AccountRepositoryInterface $repository + * @param ExportJobRepositoryInterface $jobs * * @return View */ - public function index(AccountRepositoryInterface $repository, EJRI $jobs) + public function index(AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs) { // create new export job. $job = $jobs->create(); @@ -128,13 +128,13 @@ class ExportController extends Controller } /** - * @param ExportFormRequest $request - * @param AccountRepositoryInterface $repository - * @param EJRI $jobs + * @param ExportFormRequest $request + * @param AccountRepositoryInterface $repository + * @param ExportJobRepositoryInterface $jobs * * @return \Illuminate\Http\JsonResponse */ - public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs) + public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs) { $job = $jobs->findByKey($request->get('job')); $settings = [ diff --git a/app/Http/Controllers/HelpController.php b/app/Http/Controllers/HelpController.php index 24d2d75254..e776749bc6 100644 --- a/app/Http/Controllers/HelpController.php +++ b/app/Http/Controllers/HelpController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -58,22 +58,22 @@ class HelpController extends Controller return Response::json($content); } - $content = $help->getFromGithub($language, $route); + $content = $help->getFromGithub($route, $language); + $notYourLanguage = '

' . strval(trans('firefly.help_may_not_be_your_language')) . '

'; // get backup language content (try English): if (strlen($content) === 0) { $language = 'en_US'; if ($help->inCache($route, $language)) { Log::debug(sprintf('Help text %s was in cache.', $language)); - $content = $help->getFromCache($route, $language); + $content = $notYourLanguage . $help->getFromCache($route, $language); } if (!$help->inCache($route, $language)) { - $content = $help->getFromGithub($language, $route); - $content = '

' . strval(trans('firefly.help_may_not_be_your_language')) . '

' . $content; + $content = trim($notYourLanguage . $help->getFromGithub($route, $language)); } } - if (strlen($content) === 0) { + if ($content === $notYourLanguage) { $content = '

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

'; } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 9dbce2de32..7e4cca9547 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Controllers; use Artisan; @@ -17,13 +18,12 @@ use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AccountType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; use Preferences; -use Route; use Session; use View; @@ -91,18 +91,18 @@ class HomeController extends Controller public function flush(Request $request) { Preferences::mark(); - $request->session()->forget(['start', 'end','_previous', 'viewRange', 'range', 'is_custom_range']); + $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); Artisan::call('cache:clear'); return redirect(route('index')); } /** - * @param ARI $repository + * @param AccountRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function index(ARI $repository) + public function index(AccountRepositoryInterface $repository) { $types = config('firefly.accountTypesByIdentifier.asset'); $count = $repository->count($types); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index ef812a8a1b..325698f720 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -20,6 +20,7 @@ use FireflyIII\Import\Setup\SetupInterface; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Http\Response as LaravelResponse; use Log; @@ -87,8 +88,6 @@ class ImportController extends Controller { Log::debug('Now at start of configure()'); if (!$this->jobInCorrectStep($job, 'configure')) { - Log::debug('Job is not in correct state for configure()', ['status' => $job->status]); - return $this->redirectToCorrectStep($job); } @@ -100,8 +99,6 @@ class ImportController extends Controller $subTitleIcon = 'fa-wrench'; return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); - - } /** @@ -145,8 +142,6 @@ class ImportController extends Controller public function finished(ImportJob $job) { if (!$this->jobInCorrectStep($job, 'finished')) { - Log::debug('Job is not in correct state for finished()', ['status' => $job->status]); - return $this->redirectToCorrectStep($job); } @@ -224,13 +219,13 @@ class ImportController extends Controller /** * Step 4. Save the configuration. * - * @param Request $request - * @param ImportJob $job + * @param Request $request + * @param ImportJobRepositoryInterface $repository + * @param ImportJob $job * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws FireflyException */ - public function postConfigure(Request $request, ImportJob $job) + public function postConfigure(Request $request, ImportJobRepositoryInterface $repository, ImportJob $job) { Log::debug('Now in postConfigure()', ['job' => $job->key]); if (!$this->jobInCorrectStep($job, 'process')) { @@ -245,8 +240,7 @@ class ImportController extends Controller $importer->saveImportConfiguration($data, $files); // update job: - $job->status = 'import_configuration_saved'; - $job->save(); + $repository->updateStatus($job, 'import_configuration_saved'); // return redirect to settings. // this could loop until the user is done. @@ -280,17 +274,15 @@ class ImportController extends Controller * Step 5. Depending on the importer, this will show the user settings to * fill in. * - * @param ImportJob $job + * @param ImportJobRepositoryInterface $repository + * @param ImportJob $job * * @return View - * @throws FireflyException */ - public function settings(ImportJob $job) + public function settings(ImportJobRepositoryInterface $repository, ImportJob $job) { Log::debug('Now in settings()', ['job' => $job->key]); if (!$this->jobInCorrectStep($job, 'settings')) { - Log::debug('Job should not be in settings()'); - return $this->redirectToCorrectStep($job); } Log::debug('Continue in settings()'); @@ -308,8 +300,7 @@ class ImportController extends Controller } Log::debug('Job does NOT require user config.'); - $job->status = 'settings_complete'; - $job->save(); + $repository->updateStatus($job, 'settings_complete'); // if no more settings, save job and continue to process thing. return redirect(route('import.complete', [$job->key])); @@ -350,15 +341,17 @@ class ImportController extends Controller return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); } + /** * This is step 2. It creates an Import Job. Stores the import. * * @param ImportUploadRequest $request * @param ImportJobRepositoryInterface $repository + * @param UserRepositoryInterface $userRepository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository) + public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository, UserRepositoryInterface $userRepository) { Log::debug('Now in upload()'); // create import job: @@ -375,7 +368,7 @@ class ImportController extends Controller $disk = Storage::disk('upload'); // user is demo user, replace upload with prepared file. - if (auth()->user()->hasRole('demo')) { + if ($userRepository->hasRole(auth()->user(), 'demo')) { $stubsDisk = Storage::disk('stubs'); $content = $stubsDisk->get('demo-import.csv'); $contentEncrypted = Crypt::encrypt($content); @@ -383,15 +376,14 @@ class ImportController extends Controller Log::debug('Replaced upload with demo file.'); // also set up prepared configuration. - $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); - $job->configuration = $configuration; - $job->save(); + $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); + $repository->setConfiguration($job, $configuration); Log::debug('Set configuration for demo user', $configuration); // also flash info Session::flash('info', trans('demo.import-configure-security')); } - if (!auth()->user()->hasRole('demo')) { + if (!$userRepository->hasRole(auth()->user(), 'demo')) { // user is not demo, process original upload: $disk->put($newName, $contentEncrypted); Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); @@ -411,18 +403,15 @@ class ImportController extends Controller $configRaw = $configFileObject->fread($configFileObject->getSize()); $configuration = json_decode($configRaw, true); + // @codeCoverageIgnoreStart if (!is_null($configuration) && is_array($configuration)) { Log::debug('Found configuration', $configuration); - $job->configuration = $configuration; - $job->save(); + $repository->setConfiguration($job, $configuration); } + // @codeCoverageIgnoreEnd } - // if user is demo user, replace config with prepared config: - - return redirect(route('import.configure', [$job->key])); - } /** @@ -440,6 +429,8 @@ class ImportController extends Controller return $job->status === 'import_status_never_started'; case 'settings': case 'store-settings': + Log::debug(sprintf('Job %d with key %s has status %s', $job->id, $job->key, $job->status)); + return $job->status === 'import_configuration_saved'; case 'finished': return $job->status === 'import_complete'; @@ -449,7 +440,7 @@ class ImportController extends Controller return ($job->status === 'settings_complete') || ($job->status === 'import_running'); } - return false; + return false; // @codeCoverageIgnore } @@ -475,7 +466,7 @@ class ImportController extends Controller return $importer; } - throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); + throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); // @codeCoverageIgnore } @@ -507,7 +498,7 @@ class ImportController extends Controller return redirect(route('import.finished', [$job->key])); } - throw new FireflyException('Cannot redirect for job state ' . $job->status); + throw new FireflyException('Cannot redirect for job state ' . $job->status); // @codeCoverageIgnore } } diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index 6e63e57a43..e5e7fb0651 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -7,12 +7,17 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Amount; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Http\Request; use Navigation; use Preferences; @@ -25,9 +30,61 @@ use Session; */ class JavascriptController extends Controller { + /** + * @param AccountRepositoryInterface $repository + * @param CurrencyRepositoryInterface $currencyRepository + * + * @return $this + */ + public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository) + { + $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $preference = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR')); + $default = $currencyRepository->findByCode($preference->data); + + $data = ['accounts' => [],]; + + + /** @var Account $account */ + foreach ($accounts as $account) { + $accountId = $account->id; + $currency = intval($account->getMeta('currency_id')); + $currency = $currency === 0 ? $default->id : $currency; + $entry = ['preferredCurrency' => $currency, 'name' => $account->name]; + $data['accounts'][$accountId] = $entry; + } + + + return response() + ->view('javascript.accounts', $data, 200) + ->header('Content-Type', 'text/javascript'); + } /** + * @param CurrencyRepositoryInterface $repository * + * @return $this + */ + public function currencies(CurrencyRepositoryInterface $repository) + { + $currencies = $repository->get(); + $data = ['currencies' => [],]; + /** @var TransactionCurrency $currency */ + foreach ($currencies as $currency) { + $currencyId = $currency->id; + $entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol]; + $data['currencies'][$currencyId] = $entry; + } + + return response() + ->view('javascript.currencies', $data, 200) + ->header('Content-Type', 'text/javascript'); + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\Response */ public function variables(Request $request) { @@ -95,7 +152,7 @@ class JavascriptController extends Controller switch ($viewRange) { default: - throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); + throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); // @codeCoverageIgnore case '1D': case 'custom': $format = (string)trans('config.month_and_day'); diff --git a/app/Http/Controllers/Json/ExchangeController.php b/app/Http/Controllers/Json/ExchangeController.php new file mode 100644 index 0000000000..e96cae05e8 --- /dev/null +++ b/app/Http/Controllers/Json/ExchangeController.php @@ -0,0 +1,66 @@ +getExchangeRate($fromCurrency, $toCurrency, $date); + $amount = null; + if (is_null($rate->id)) { + Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); + $preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service')); + $class = config('firefly.currency_exchange_services.' . $preferred); + /** @var ExchangeRateInterface $object */ + $object = app($class); + $object->setUser(auth()->user()); + $rate = $object->getRate($fromCurrency, $toCurrency, $date); + } + $return = $rate->toArray(); + $return['amount'] = null; + if (!is_null($request->get('amount'))) { + // assume amount is in "from" currency: + $return['amount'] = bcmul($request->get('amount'), strval($rate->rate), 12); + // round to toCurrency decimal places: + $return['amount'] = round($return['amount'], $toCurrency->decimal_places); + } + + return Response::json($return); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 4d95c51fc9..c8e916e231 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Controllers; use Amount; @@ -17,6 +18,7 @@ use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AccountType; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -144,7 +146,7 @@ class JsonController extends Controller * @return \Illuminate\Http\JsonResponse * */ - public function boxIn(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository) + public function boxIn() { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -155,12 +157,18 @@ class JsonController extends Controller $cache->addProperty($end); $cache->addProperty('box-in'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } - $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]); - $assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $amount = $accountTasker->amountInInPeriod($accounts, $assets, $start, $end); - $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + + // try a collector for income: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end) + ->setTypes([TransactionType::DEPOSIT]) + ->withOpposingAccount(); + + $amount = strval($collector->getJournals()->sum('transaction_amount')); + $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $cache->store($data); return Response::json($data); @@ -172,7 +180,7 @@ class JsonController extends Controller * * @return \Symfony\Component\HttpFoundation\Response */ - public function boxOut(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository) + public function boxOut() { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -183,12 +191,16 @@ class JsonController extends Controller $cache->addProperty($end); $cache->addProperty('box-out'); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); // @codeCoverageIgnore } - $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]); - $assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $amount = $accountTasker->amountOutInPeriod($accounts, $assets, $start, $end); + // try a collector for expenses: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end) + ->setTypes([TransactionType::WITHDRAWAL]) + ->withOpposingAccount(); + $amount = strval($collector->getJournals()->sum('transaction_amount')); $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $cache->store($data); @@ -287,7 +299,7 @@ class JsonController extends Controller { $pref = Preferences::get('tour', true); if (!$pref) { - throw new FireflyException('Cannot find preference for tour. Exit.'); + throw new FireflyException('Cannot find preference for tour. Exit.'); // @codeCoverageIgnore } $headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end']; $steps = []; @@ -329,7 +341,9 @@ class JsonController extends Controller } /** + * @param JournalRepositoryInterface $repository * + * @return \Illuminate\Http\JsonResponse */ public function transactionTypes(JournalRepositoryInterface $repository) { diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php index 12a9c396a9..478e5830d6 100644 --- a/app/Http/Controllers/NewUserController.php +++ b/app/Http/Controllers/NewUserController.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Controllers; use Carbon\Carbon; @@ -111,15 +112,15 @@ class NewUserController extends Controller private function createAssetAccount(NewUserFormRequest $request, AccountRepositoryInterface $repository): bool { $assetAccount = [ - 'name' => $request->get('bank_name'), - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'active' => true, - 'accountRole' => 'defaultAsset', - 'openingBalance' => round($request->input('bank_balance'), 12), - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => intval($request->input('amount_currency_id_bank_balance')), + 'name' => $request->get('bank_name'), + 'iban' => null, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'active' => true, + 'accountRole' => 'defaultAsset', + 'openingBalance' => round($request->input('bank_balance'), 12), + 'openingBalanceDate' => new Carbon, + 'currency_id' => intval($request->input('amount_currency_id_bank_balance')), ]; $repository->store($assetAccount); @@ -136,15 +137,15 @@ class NewUserController extends Controller private function createSavingsAccount(NewUserFormRequest $request, AccountRepositoryInterface $repository): bool { $savingsAccount = [ - 'name' => $request->get('bank_name') . ' savings account', - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'active' => true, - 'accountRole' => 'savingAsset', - 'openingBalance' => round($request->input('savings_balance'), 12), - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => intval($request->input('amount_currency_id_savings_balance')), + 'name' => $request->get('bank_name') . ' savings account', + 'iban' => null, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'active' => true, + 'accountRole' => 'savingAsset', + 'openingBalance' => round($request->input('savings_balance'), 12), + 'openingBalanceDate' => new Carbon, + 'currency_id' => intval($request->input('amount_currency_id_savings_balance')), ]; $repository->store($savingsAccount); diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index b74905c682..052900cba5 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Controllers; use Amount; @@ -209,10 +210,11 @@ class PiggyBankController extends Controller $end = session('end', Carbon::now()->endOfMonth()); $accounts = []; + Log::debug('Looping piggues'); /** @var PiggyBank $piggyBank */ foreach ($piggyBanks as $piggyBank) { - $piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount; - $piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0; + $piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0'; + $piggyBank->percentage = bccomp('0', $piggyBank->savedSoFar) !== 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0; $piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar)); $piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage; @@ -220,7 +222,9 @@ class PiggyBankController extends Controller * Fill account information: */ $account = $piggyBank->account; + $new = false; if (!isset($accounts[$account->id])) { + $new = true; $accounts[$account->id] = [ 'name' => $account->name, 'balance' => Steam::balanceIgnoreVirtual($account, $end), @@ -229,7 +233,8 @@ class PiggyBankController extends Controller 'sumOfTargets' => $piggyBank->targetamount, 'leftToSave' => $piggyBank->leftToSave, ]; - } else { + } + if (isset($accounts[$account->id]) && $new === false) { $accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar)); $accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount); $accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave); @@ -272,32 +277,16 @@ class PiggyBankController extends Controller public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { $amount = $request->get('amount'); - Log::debug(sprintf('Found amount is %s', $amount)); - /** @var Carbon $date */ - $date = session('end', Carbon::now()->endOfMonth()); - $leftOnAccount = $piggyBank->leftOnAccount($date); - $savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount); - $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); - $maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12))); - if (bccomp($amount, $maxAmount) <= 0) { - $repetition = $piggyBank->currentRelevantRep(); - $currentAmount = $repetition->currentamount ?? '0'; - $repetition->currentamount = bcadd($currentAmount, $amount); - $repetition->save(); - - // create event - $repository->createEvent($piggyBank, $amount); - - Session::flash( - 'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])) - ); + if ($repository->canAddAmount($piggyBank, $amount)) { + $repository->addAmount($piggyBank, $amount); + Session::flash('success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name]))); Preferences::mark(); return redirect(route('piggy-banks.index')); } - Log::error('Cannot add ' . $amount . ' because max amount is ' . $maxAmount . ' (left on account is ' . $leftOnAccount . ')'); + Log::error('Cannot add ' . $amount . ' because canAddAmount returned false.'); Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); return redirect(route('piggy-banks.index')); @@ -312,26 +301,19 @@ class PiggyBankController extends Controller */ public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { - $amount = strval(round($request->get('amount'), 12)); - - $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; - - if (bccomp($amount, $savedSoFar) <= 0) { - $repetition = $piggyBank->currentRelevantRep(); - $repetition->currentamount = bcsub($repetition->currentamount, $amount); - $repetition->save(); - - // create event - $repository->createEvent($piggyBank, bcmul($amount, '-1')); - + $amount = $request->get('amount'); + if ($repository->canRemoveAmount($piggyBank, $amount)) { + $repository->removeAmount($piggyBank, $amount); Session::flash( - 'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])) + 'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name])) ); Preferences::mark(); return redirect(route('piggy-banks.index')); } + $amount = strval(round($request->get('amount'), 12)); + Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); return redirect(route('piggy-banks.index')); @@ -391,9 +373,11 @@ class PiggyBankController extends Controller Preferences::mark(); if (intval($request->get('create_another')) === 1) { + // @codeCoverageIgnoreStart Session::put('piggy-banks.create.fromStore', true); return redirect(route('piggy-banks.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('piggy-banks.edit.uri')); @@ -415,9 +399,11 @@ class PiggyBankController extends Controller Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { + // @codeCoverageIgnoreStart Session::put('piggy-banks.edit.fromUpdate', true); return redirect(route('piggy-banks.edit', [$piggyBank->id])); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('piggy-banks.edit.uri')); diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index a2f3505923..db70c528aa 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Popup; @@ -17,17 +17,13 @@ namespace FireflyIII\Http\Controllers\Popup; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collection\BalanceLine; -use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Report\PopupReportInterface; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\Binder\AccountList; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use InvalidArgumentException; use Response; use View; @@ -40,6 +36,44 @@ use View; class ReportController extends Controller { + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var BudgetRepositoryInterface */ + private $budgetRepository; + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + /** @var PopupReportInterface */ + private $popupHelper; + + + /** + * + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + + /** @var AccountRepositoryInterface $repository */ + $this->accountRepository = app(AccountRepositoryInterface::class); + + /** @var BudgetRepositoryInterface $repository */ + $this->budgetRepository = app(BudgetRepositoryInterface::class); + + /** @var CategoryRepositoryInterface categoryRepository */ + $this->categoryRepository = app(CategoryRepositoryInterface::class); + + /** @var PopupReportInterface popupHelper */ + $this->popupHelper = app(PopupReportInterface::class); + + return $next($request); + } + ); + + } + + /** * @param Request $request * @@ -59,7 +93,6 @@ class ReportController extends Controller throw new FireflyException('Firefly cannot handle "' . e($attributes['location']) . '" '); case 'budget-spent-amount': $html = $this->budgetSpentAmount($attributes); - break; case 'expense-entry': $html = $this->expenseEntry($attributes); @@ -88,63 +121,26 @@ class ReportController extends Controller */ private function balanceAmount(array $attributes): string { - $role = intval($attributes['role']); - - /** @var BudgetRepositoryInterface $budgetRepository */ - $budgetRepository = app(BudgetRepositoryInterface::class); - $budget = $budgetRepository->find(intval($attributes['budgetId'])); - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - $account = $repository->find(intval($attributes['accountId'])); - $types = [TransactionType::WITHDRAWAL]; + $role = intval($attributes['role']); + $budget = $this->budgetRepository->find(intval($attributes['budgetId'])); + $account = $this->accountRepository->find(intval($attributes['accountId'])); switch (true) { case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)): - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector - ->setAccounts(new Collection([$account])) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->setBudget($budget); - $journals = $collector->getJournals(); - + // normal row with a budget: + $journals = $this->popupHelper->balanceForBudget($budget, $account, $attributes); break; case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)): + // normal row without a budget: + $journals = $this->popupHelper->balanceForNoBudget($account, $attributes); $budget->name = strval(trans('firefly.no_budget')); - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector - ->setAccounts(new Collection([$account])) - ->setTypes($types) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->withoutBudget(); - $journals = $collector->getJournals(); break; case ($role === BalanceLine::ROLE_DIFFROLE): - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector - ->setAccounts(new Collection([$account])) - ->setTypes($types) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->withoutBudget(); - $journals = $collector->getJournals(); - + $journals = $this->popupHelper->balanceDifference($account, $attributes); $budget->name = strval(trans('firefly.leftUnbalanced')); - $journals = $journals->filter( - function (Transaction $transaction) { - $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); - if ($tags === 0) { - return true; - } - - return false; - } - ); break; case ($role === BalanceLine::ROLE_TAGROLE): + // row with tag info. throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)'); } $view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render(); @@ -162,27 +158,8 @@ class ReportController extends Controller */ private function budgetSpentAmount(array $attributes): string { - // need to find the budget - // then search for expenses in the given period - // list them in some table format. - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $budget = $repository->find(intval($attributes['budgetId'])); - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - - $collector - ->setAccounts($attributes['accounts']) - ->setRange($attributes['startDate'], $attributes['endDate']); - - if (is_null($budget->id)) { - $collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget(); - } - if (!is_null($budget->id)) { - // get all expenses in budget in period: - $collector->setBudget($budget); - } - $journals = $collector->getJournals(); + $budget = $this->budgetRepository->find(intval($attributes['budgetId'])); + $journals = $this->popupHelper->byBudget($budget, $attributes); $view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render(); return $view; @@ -198,18 +175,9 @@ class ReportController extends Controller */ private function categoryEntry(array $attributes): string { - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $category = $repository->find(intval($attributes['categoryId'])); - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts($attributes['accounts'])->setTypes($types) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->setCategory($category); - $journals = $collector->getJournals(); // 7193 - - $view = view('popup.report.category-entry', compact('journals', 'category'))->render(); + $category = $this->categoryRepository->find(intval($attributes['categoryId'])); + $journals = $this->popupHelper->byCategory($category, $attributes); + $view = view('popup.report.category-entry', compact('journals', 'category'))->render(); return $view; } @@ -224,29 +192,9 @@ class ReportController extends Controller */ private function expenseEntry(array $attributes): string { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - $account = $repository->find(intval($attributes['accountId'])); - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types); - $journals = $collector->getJournals(); - $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report - - // filter for transfers and withdrawals TO the given $account - $journals = $journals->filter( - function (Transaction $transaction) use ($report) { - // get the destinations: - $sources = TransactionJournal::sourceAccountList($transaction->transactionJournal)->pluck('id')->toArray(); - - // do these intersect with the current list? - return !empty(array_intersect($report, $sources)); - } - ); - - $view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); + $account = $this->accountRepository->find(intval($attributes['accountId'])); + $journals = $this->popupHelper->byExpenses($account, $attributes); + $view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); return $view; } @@ -261,28 +209,9 @@ class ReportController extends Controller */ private function incomeEntry(array $attributes): string { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $account = $repository->find(intval($attributes['accountId'])); - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types); - $journals = $collector->getJournals(); - $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report - - // filter the set so the destinations outside of $attributes['accounts'] are not included. - $journals = $journals->filter( - function (Transaction $transaction) use ($report) { - // get the destinations: - $destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray(); - - // do these intersect with the current list? - return !empty(array_intersect($report, $destinations)); - } - ); - - $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); + $account = $this->accountRepository->find(intval($attributes['accountId'])); + $journals = $this->popupHelper->byIncome($account, $attributes); + $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); return $view; } diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 4373d61204..7ef95c1307 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -9,12 +9,14 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Controllers; use FireflyIII\Http\Requests\TokenFormRequest; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Http\Request; use PragmaRX\Google2FA\Contracts\Google2FA; use Preferences; @@ -55,8 +57,7 @@ class PreferencesController extends Controller public function code(Google2FA $google2fa) { $domain = $this->getDomain(); - /** @noinspection PhpMethodParametersCountMismatchInspection */ - $secret = $google2fa->generateSecretKey(32, auth()->user()->id); + $secret = $google2fa->generateSecretKey(16); Session::flash('two-factor-secret', $secret); $image = $google2fa->getQRCodeInline('Firefly III at ' . $domain, auth()->user()->email, $secret, 150); @@ -127,11 +128,13 @@ class PreferencesController extends Controller } /** - * @param Request $request + * @param Request $request + * + * @param UserRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function postIndex(Request $request) + public function postIndex(Request $request, UserRepositoryInterface $repository) { // front page accounts $frontPageAccounts = []; @@ -160,16 +163,15 @@ class PreferencesController extends Controller Preferences::set('showDepositsFrontpage', $showDepositsFrontpage); // save page size: + Preferences::set('transactionPageSize', 50); $transactionPageSize = intval($request->get('transactionPageSize')); if ($transactionPageSize > 0 && $transactionPageSize < 1337) { Preferences::set('transactionPageSize', $transactionPageSize); - } else { - Preferences::set('transactionPageSize', 50); } $twoFactorAuthEnabled = false; $hasTwoFactorAuthSecret = false; - if (!auth()->user()->hasRole('demo')) { + if (!$repository->hasRole(auth()->user(), 'demo')) { // two factor auth $twoFactorAuthEnabled = intval($request->get('twoFactorAuthEnabled')); $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index e2f7516dd8..780c3038f4 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -9,14 +9,16 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use FireflyIII\Exceptions\ValidationException; +use FireflyIII\Http\Middleware\IsLimitedUser; use FireflyIII\Http\Requests\DeleteAccountFormRequest; use FireflyIII\Http\Requests\ProfileFormRequest; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; use Hash; use Log; use Session; @@ -45,6 +47,8 @@ class ProfileController extends Controller return $next($request); } ); + $this->middleware(IsLimitedUser::class); + } /** @@ -52,16 +56,6 @@ class ProfileController extends Controller */ public function changePassword() { - if (intval(getenv('SANDSTORM')) === 1) { - return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); - } - - if (auth()->user()->hasRole('demo')) { - Session::flash('info', strval(trans('firefly.cannot_change_demo'))); - - return redirect(route('profile.index')); - } - $title = auth()->user()->email; $subTitle = strval(trans('firefly.change_your_password')); $subTitleIcon = 'fa-key'; @@ -74,16 +68,6 @@ class ProfileController extends Controller */ public function deleteAccount() { - if (intval(getenv('SANDSTORM')) === 1) { - return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); - } - - if (auth()->user()->hasRole('demo')) { - Session::flash('info', strval(trans('firefly.cannot_delete_demo'))); - - return redirect(route('profile.index')); - } - $title = auth()->user()->email; $subTitle = strval(trans('firefly.delete_account')); $subTitleIcon = 'fa-trash'; @@ -111,32 +95,18 @@ class ProfileController extends Controller */ public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository) { - if (intval(getenv('SANDSTORM')) === 1) { - return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); - } - - if (auth()->user()->hasRole('demo')) { - Session::flash('info', strval(trans('firefly.cannot_change_demo'))); - - return redirect(route('profile.index')); - } - - // old, new1, new2 - if (!Hash::check($request->get('current_password'), auth()->user()->password)) { - Session::flash('error', strval(trans('firefly.invalid_current_password'))); - - return redirect(route('profile.change-password')); - } + // the request has already validated both new passwords must be equal. + $current = $request->get('current_password'); + $new = $request->get('new_password'); try { - $this->validatePassword($request->get('current_password'), $request->get('new_password')); + $this->validatePassword(auth()->user(), $current, $new); } catch (ValidationException $e) { Session::flash('error', $e->getMessage()); return redirect(route('profile.change-password')); } - // update the user with the new password. $repository->changePassword(auth()->user(), $request->get('new_password')); Session::flash('success', strval(trans('firefly.password_changed'))); @@ -151,17 +121,6 @@ class ProfileController extends Controller */ public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request) { - if (intval(getenv('SANDSTORM')) === 1) { - return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); - } - - if (auth()->user()->hasRole('demo')) { - Session::flash('info', strval(trans('firefly.cannot_delete_demo'))); - - return redirect(route('profile.index')); - } - - // old, new1, new2 if (!Hash::check($request->get('password'), auth()->user()->password)) { Session::flash('error', strval(trans('firefly.invalid_password'))); @@ -181,16 +140,22 @@ class ProfileController extends Controller return redirect(route('index')); } + /** - * @param string $old + * @param User $user + * @param string $current * @param string $new * * @return bool * @throws ValidationException */ - protected function validatePassword(string $old, string $new): bool + protected function validatePassword(User $user, string $current, string $new): bool { - if ($new === $old) { + if (!Hash::check($current, $user->password)) { + throw new ValidationException(strval(trans('firefly.invalid_current_password'))); + } + + if ($current === $new) { throw new ValidationException(strval(trans('firefly.should_change'))); } diff --git a/app/Http/Controllers/Report/AccountController.php b/app/Http/Controllers/Report/AccountController.php index c36c6c7aea..3453b525c5 100644 --- a/app/Http/Controllers/Report/AccountController.php +++ b/app/Http/Controllers/Report/AccountController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; @@ -44,7 +44,7 @@ class AccountController extends Controller $cache->addProperty('account-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } /** @var AccountTaskerInterface $accountTasker */ diff --git a/app/Http/Controllers/Report/BalanceController.php b/app/Http/Controllers/Report/BalanceController.php index 9dadfa608d..9793f45275 100644 --- a/app/Http/Controllers/Report/BalanceController.php +++ b/app/Http/Controllers/Report/BalanceController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; @@ -47,7 +47,7 @@ class BalanceController extends Controller $cache->addProperty('balance-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $balance = $helper->getBalanceReport($accounts, $start, $end); diff --git a/app/Http/Controllers/Report/BudgetController.php b/app/Http/Controllers/Report/BudgetController.php index bc4d026631..b6b4c8bebd 100644 --- a/app/Http/Controllers/Report/BudgetController.php +++ b/app/Http/Controllers/Report/BudgetController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; @@ -49,7 +49,7 @@ class BudgetController extends Controller $cache->addProperty('budget-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $budgets = $helper->getBudgetReport($start, $end, $accounts); @@ -76,7 +76,7 @@ class BudgetController extends Controller $cache->addProperty('budget-period-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // generate budget report right here. diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index 730deeaece..1528f44460 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; @@ -20,7 +20,6 @@ use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; -use Log; use Navigation; /** @@ -45,9 +44,7 @@ class CategoryController extends Controller $cache->addProperty('category-period-expenses-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - Log::debug('Return report from cache'); - - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); @@ -79,9 +76,7 @@ class CategoryController extends Controller $cache->addProperty('category-period-income-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - Log::debug('Return report from cache'); - - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); @@ -114,7 +109,7 @@ class CategoryController extends Controller $cache->addProperty('category-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } /** @var CategoryRepositoryInterface $repository */ diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php index f1abe0b96e..5081ba6554 100644 --- a/app/Http/Controllers/Report/OperationsController.php +++ b/app/Http/Controllers/Report/OperationsController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; @@ -19,6 +19,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; @@ -31,13 +32,14 @@ class OperationsController extends Controller { /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param AccountTaskerInterface $tasker + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return mixed|string */ - public function expenses(Collection $accounts, Carbon $start, Carbon $end) + public function expenses(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end) { // chart properties for cache: $cache = new CacheProperties; @@ -46,9 +48,9 @@ class OperationsController extends Controller $cache->addProperty('expense-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - $entries = $this->getExpenseReport($start, $end, $accounts); + $entries = $tasker->getExpenseReport($start, $end, $accounts); $type = 'expense-entry'; $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); $cache->store($result); @@ -58,13 +60,14 @@ class OperationsController extends Controller } /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param AccountTaskerInterface $tasker + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return string */ - public function income(Collection $accounts, Carbon $start, Carbon $end) + public function income(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end) { // chart properties for cache: $cache = new CacheProperties; @@ -73,9 +76,9 @@ class OperationsController extends Controller $cache->addProperty('income-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - $entries = $this->getIncomeReport($start, $end, $accounts); + $entries = $tasker->getIncomeReport($start, $end, $accounts); $type = 'income-entry'; $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); @@ -86,13 +89,14 @@ class OperationsController extends Controller } /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param AccountTaskerInterface $tasker + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * * @return mixed|string */ - public function operations(Collection $accounts, Carbon $start, Carbon $end) + public function operations(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end) { // chart properties for cache: $cache = new CacheProperties; @@ -101,11 +105,11 @@ class OperationsController extends Controller $cache->addProperty('inc-exp-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - $incomes = $this->getIncomeReport($start, $end, $accounts); - $expenses = $this->getExpenseReport($start, $end, $accounts); + $incomes = $tasker->getIncomeReport($start, $end, $accounts); + $expenses = $tasker->getExpenseReport($start, $end, $accounts); $incomeSum = array_sum( array_map( function ($item) { @@ -129,125 +133,4 @@ class OperationsController extends Controller } - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - private function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array - { - // get all expenses for the given accounts in the given period! - // also transfers! - // get all transactions: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end); - $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->withOpposingAccount() - ->enableInternalFilter(); - $transactions = $collector->getJournals(); - $transactions = $transactions->filter( - function (Transaction $transaction) { - // return negative amounts only. - if (bccomp($transaction->transaction_amount, '0') === -1) { - return $transaction; - } - - return false; - } - ); - $expenses = $this->groupByOpposing($transactions); - - // sort the result - // Obtain a list of columns - $sum = []; - foreach ($expenses as $accountId => $row) { - $sum[$accountId] = floatval($row['sum']); - } - - array_multisort($sum, SORT_ASC, $expenses); - - return $expenses; - } - - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - private function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array - { - // get all expenses for the given accounts in the given period! - // also transfers! - // get all transactions: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end); - $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->withOpposingAccount() - ->enableInternalFilter(); - $transactions = $collector->getJournals(); - $transactions = $transactions->filter( - function (Transaction $transaction) { - // return positive amounts only. - if (bccomp($transaction->transaction_amount, '0') === 1) { - return $transaction; - } - - return false; - } - ); - $income = $this->groupByOpposing($transactions); - - // sort the result - // Obtain a list of columns - $sum = []; - foreach ($income as $accountId => $row) { - $sum[$accountId] = floatval($row['sum']); - } - - array_multisort($sum, SORT_DESC, $income); - - return $income; - } - - /** - * @param Collection $transactions - * - * @return array - */ - private function groupByOpposing(Collection $transactions): array - { - $expenses = []; - // join the result together: - foreach ($transactions as $transaction) { - $opposingId = $transaction->opposing_account_id; - $name = $transaction->opposing_account_name; - if (!isset($expenses[$opposingId])) { - $expenses[$opposingId] = [ - 'id' => $opposingId, - 'name' => $name, - 'sum' => '0', - 'average' => '0', - 'count' => 0, - ]; - } - $expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount); - $expenses[$opposingId]['count']++; - } - // do averages: - foreach ($expenses as $key => $entry) { - if ($expenses[$key]['count'] > 1) { - $expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count'])); - } - } - - - return $expenses; - } - } diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 5c32e086f9..9b13cc1bb3 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -9,12 +9,11 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Report\ReportGeneratorFactory; use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Http\Requests\ReportFormRequest; @@ -26,6 +25,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Collection; +use Log; use Preferences; use Response; use Session; @@ -48,6 +48,7 @@ class ReportController extends Controller { parent::__construct(); + $this->helper = app(ReportHelperInterface::class); $this->middleware( function ($request, $next) { @@ -55,8 +56,6 @@ class ReportController extends Controller View::share('mainTitleIcon', 'fa-line-chart'); View::share('subTitleIcon', 'fa-calendar'); - $this->helper = app(ReportHelperInterface::class); - return $next($request); } ); @@ -73,7 +72,7 @@ class ReportController extends Controller public function auditReport(Collection $accounts, Carbon $start, Carbon $end) { if ($end < $start) { - return view('error')->with('message', trans('firefly.end_after_start_date')); + return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore } if ($start < session('first')) { $start = session('first'); @@ -98,42 +97,6 @@ class ReportController extends Controller } - /** - * @param Collection $accounts - * @param Collection $tags - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) - { - if ($end < $start) { - return view('error')->with('message', trans('firefly.end_after_start_date')); - } - if ($start < session('first')) { - $start = session('first'); - } - - View::share( - 'subTitle', trans( - 'firefly.report_tag', - [ - 'start' => $start->formatLocalized($this->monthFormat), - 'end' => $end->formatLocalized($this->monthFormat), - ] - ) - ); - - $generator = ReportGeneratorFactory::reportGenerator('Tag', $start, $end); - $generator->setAccounts($accounts); - $generator->setTags($tags); - $result = $generator->generate(); - - return $result; - - } - /** * @param Collection $accounts * @param Collection $budgets @@ -145,7 +108,7 @@ class ReportController extends Controller public function budgetReport(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end) { if ($end < $start) { - return view('error')->with('message', trans('firefly.end_after_start_date')); + return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore } if ($start < session('first')) { $start = session('first'); @@ -181,7 +144,7 @@ class ReportController extends Controller public function categoryReport(Collection $accounts, Collection $categories, Carbon $start, Carbon $end) { if ($end < $start) { - return view('error')->with('message', trans('firefly.end_after_start_date')); + return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore } if ($start < session('first')) { $start = session('first'); @@ -287,10 +250,9 @@ class ReportController extends Controller /** * @param ReportFormRequest $request * - * @return RedirectResponse - * @throws FireflyException + * @return RedirectResponse|\Illuminate\Routing\Redirector */ - public function postIndex(ReportFormRequest $request): RedirectResponse + public function postIndex(ReportFormRequest $request) { // report type: $reportType = $request->get('report_type'); @@ -300,8 +262,10 @@ class ReportController extends Controller $categories = join(',', $request->getCategoryList()->pluck('id')->toArray()); $budgets = join(',', $request->getBudgetList()->pluck('id')->toArray()); $tags = join(',', $request->getTagList()->pluck('tag')->toArray()); + $uri = route('reports.index'); if ($request->getAccountList()->count() === 0) { + Log::debug('Account count is zero'); Session::flash('error', trans('firefly.select_more_than_one_account')); return redirect(route('reports.index')); @@ -329,14 +293,7 @@ class ReportController extends Controller return view('error')->with('message', trans('firefly.end_after_start_date')); } - // lower threshold - if ($start < session('first')) { - $start = session('first'); - } - switch ($reportType) { - default: - throw new FireflyException(sprintf('Firefly does not support the "%s"-report yet.', $reportType)); case 'category': $uri = route('reports.report.category', [$accounts, $categories, $start, $end]); break; @@ -357,6 +314,42 @@ class ReportController extends Controller return redirect($uri); } + /** + * @param Collection $accounts + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) + { + if ($end < $start) { + return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore + } + if ($start < session('first')) { + $start = session('first'); + } + + View::share( + 'subTitle', trans( + 'firefly.report_tag', + [ + 'start' => $start->formatLocalized($this->monthFormat), + 'end' => $end->formatLocalized($this->monthFormat), + ] + ) + ); + + $generator = ReportGeneratorFactory::reportGenerator('Tag', $start, $end); + $generator->setAccounts($accounts); + $generator->setTags($tags); + $result = $generator->generate(); + + return $result; + + } + /** * @return string */ diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index e20830a819..a7247b7705 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -255,10 +255,11 @@ class RuleController extends Controller Preferences::mark(); if (intval($request->get('create_another')) === 1) { - // set value so create routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('rules.create.fromStore', true); return redirect(route('rules.create', [$ruleGroup]))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('rules.create.uri')); @@ -340,10 +341,11 @@ class RuleController extends Controller Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('rules.edit.fromUpdate', true); return redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('rules.edit.uri')); @@ -473,7 +475,7 @@ class RuleController extends Controller $actions[] = view( 'rules.partials.action', [ - 'oldTrigger' => $entry, + 'oldAction' => $entry, 'oldValue' => $request->old('rule-action-value')[$index], 'oldChecked' => $checked, 'count' => $count, @@ -530,7 +532,8 @@ class RuleController extends Controller ]; if (is_array($data['rule-triggers'])) { foreach ($data['rule-triggers'] as $index => $triggerType) { - $triggers[] = [ + $data['rule-trigger-stop'][$index] = $data['rule-trigger-stop'][$index] ?? 0; + $triggers[] = [ 'type' => $triggerType, 'value' => $data['rule-trigger-values'][$index], 'stopProcessing' => intval($data['rule-trigger-stop'][$index]) === 1 ? true : false, diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index e0bc8b7476..12f9a0bb58 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; @@ -217,10 +217,11 @@ class RuleGroupController extends Controller Preferences::mark(); if (intval($request->get('create_another')) === 1) { - // set value so create routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('rule-groups.create.fromStore', true); return redirect(route('rule-groups.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('rule-groups.create.uri')); @@ -261,10 +262,11 @@ class RuleGroupController extends Controller Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('rule-groups.edit.fromUpdate', true); return redirect(route('rule-groups.edit', [$ruleGroup->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } // redirect to previous URL. diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 4b47777c62..f014f8325a 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -9,12 +9,14 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use FireflyIII\Support\Search\SearchInterface; use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use View; /** * Class SearchController @@ -30,44 +32,66 @@ class SearchController extends Controller { parent::__construct(); + $this->middleware( + function ($request, $next) { + View::share('mainTitleIcon', 'fa-search'); + View::share('title', trans('firefly.search')); + + return $next($request); + } + ); + } /** - * Results always come in the form of an array [results, count, fullCount] - * * @param Request $request * @param SearchInterface $searcher * - * @return $this + * @return View */ public function index(Request $request, SearchInterface $searcher) { - $minSearchLen = 1; - $subTitle = null; - $query = null; - $result = []; - $title = trans('firefly.search'); - $limit = 20; - $mainTitleIcon = 'fa-search'; + // yes, hard coded values: + $minSearchLen = 1; + $limit = 20; - // set limit for search: - $searcher->setLimit($limit); + // ui stuff: + $subTitle = ''; + + // query stuff + $query = null; + $result = []; + $rawQuery = $request->get('q'); + $hasModifiers = true; + $modifiers = []; if (!is_null($request->get('q')) && strlen($request->get('q')) >= $minSearchLen) { - $query = trim(strtolower($request->get('q'))); - $words = explode(' ', $query); - $subTitle = trans('firefly.search_results_for', ['query' => $query]); + // parse query, find modifiers: + // set limit for search + $searcher->setLimit($limit); + $searcher->parseQuery($request->get('q')); - $transactions = $searcher->searchTransactions($words); - $accounts = $searcher->searchAccounts($words); - $categories = $searcher->searchCategories($words); - $budgets = $searcher->searchBudgets($words); - $tags = $searcher->searchTags($words); - $result = ['transactions' => $transactions, 'accounts' => $accounts, 'categories' => $categories, 'budgets' => $budgets, 'tags' => $tags]; + $transactions = $searcher->searchTransactions(); + $accounts = new Collection; + $categories = new Collection; + $tags = new Collection; + $budgets = new Collection; + + // no special search thing? + if (!$searcher->hasModifiers()) { + $hasModifiers = false; + $accounts = $searcher->searchAccounts(); + $categories = $searcher->searchCategories(); + $budgets = $searcher->searchBudgets(); + $tags = $searcher->searchTags(); + } + $query = $searcher->getWordsAsString(); + $subTitle = trans('firefly.search_results_for', ['query' => $query]); + $result = ['transactions' => $transactions, 'accounts' => $accounts, 'categories' => $categories, 'budgets' => $budgets, 'tags' => $tags]; } - return view('search.index', compact('title', 'subTitle', 'limit', 'mainTitleIcon', 'query', 'result')); + return view('search.index', compact('rawQuery', 'hasModifiers', 'modifiers', 'subTitle', 'limit', 'query', 'result')); } } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index bdb3160ea7..fa94a1b612 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -9,20 +9,19 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use Exception; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\TagFormRequest; use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Log; use Navigation; use Preferences; use Session; @@ -141,11 +140,13 @@ class TagController extends Controller } /** - * @param Tag $tag + * @param Tag $tag + * + * @param TagRepositoryInterface $repository * * @return View */ - public function edit(Tag $tag) + public function edit(Tag $tag, TagRepositoryInterface $repository) { $subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]); $subTitleIcon = 'fa-tag'; @@ -159,8 +160,8 @@ class TagController extends Controller /* * Can this tag become another type? */ - $allowAdvance = Tag::tagAllowAdvance($tag); - $allowToBalancingAct = Tag::tagAllowBalancing($tag); + $allowAdvance = $repository->tagAllowAdvance($tag); + $allowToBalancingAct = $repository->tagAllowBalancing($tag); // edit tag options: if ($allowAdvance === false) { @@ -183,25 +184,27 @@ class TagController extends Controller } /** + * @param TagRepositoryInterface $repository * + * @return View */ - public function index() + public function index(TagRepositoryInterface $repository) { $title = 'Tags'; $mainTitleIcon = 'fa-tags'; $types = ['nothing', 'balancingAct', 'advancePayment']; + $count = $repository->count(); // loop each types and get the tags, group them by year. $collection = []; foreach ($types as $type) { /** @var Collection $tags */ - $tags = auth()->user()->tags()->where('tagMode', $type)->orderBy('date', 'ASC')->get(); + $tags = $repository->getByType($type); $tags = $tags->sortBy( function (Tag $tag) { $date = !is_null($tag->date) ? $tag->date->format('Ymd') : '000000'; - return strtolower($date . $tag->tag); } ); @@ -216,58 +219,96 @@ class TagController extends Controller } } - return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection')); + return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection', 'count')); } /** - * @param Request $request - * @param JournalCollectorInterface $collector - * @param Tag $tag + * @param Request $request + * @param TagRepositoryInterface $repository + * @param Tag $tag + * @param string $moment * * @return View */ - public function show(Request $request, JournalCollectorInterface $collector, Tag $tag, string $moment = '') + public function show(Request $request, TagRepositoryInterface $repository, Tag $tag, string $moment = '') { - $range = Preferences::get('viewRange', '1M')->data; - $start = new Carbon; - $end = new Carbon; - - if (strlen($moment) > 0) { - try { - $start = new Carbon($moment); - $end = Navigation::endOfPeriod($start, $range); - } catch (Exception $e) { - $start = Navigation::startOfPeriod($this->repository->firstUseDate($tag), $range); - $end = Navigation::startOfPeriod($this->repository->lastUseDate($tag), $range); - } - } - if (strlen($moment) === 0) { - $start = clone session('start', Carbon::now()->startOfMonth()); - $end = clone session('end', Carbon::now()->endOfMonth()); - } - + // default values: $subTitle = $tag->tag; $subTitleIcon = 'fa-tag'; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $periods = $this->getPeriodOverview($tag); + $count = 0; + $loop = 0; + $range = Preferences::get('viewRange', '1M')->data; + $start = null; + $end = null; + $periods = new Collection; + $apiKey = env('GOOGLE_MAPS_API_KEY', ''); + $sum = '0'; - // use collector: - $collector->setAllAssetAccounts() - ->setLimit($pageSize)->setPage($page)->setTag($tag) - ->withBudgetInformation()->withCategoryInformation()->setRange($start, $end); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('tags/show/' . $tag->id); - $sum = $journals->sum( - function (Transaction $transaction) { - return $transaction->transaction_amount; + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]); + $start = $repository->firstUseDate($tag); + $end = new Carbon; + $sum = $repository->sumOfTag($tag); + } + + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.journals_in_period_for_tag', + ['tag' => $tag->tag, + 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodOverview($tag); + $sum = $repository->sumOfTag($tag, $start, $end); + } + + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->getPeriodOverview($tag); + $subTitle = trans( + 'firefly.journals_in_period_for_tag', + ['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at tag loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + ->setTag($tag)->withBudgetInformation()->withCategoryInformation(); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('tags/show/' . $tag->id); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); } - ); + } - return view('tags.show', compact('tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end')); + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.journals_in_period_for_tag', + ['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment')); } + /** * @param TagFormRequest $request * @@ -282,10 +323,11 @@ class TagController extends Controller Preferences::mark(); if (intval($request->get('create_another')) === 1) { - // set value so create routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('tags.create.fromStore', true); return redirect(route('tags.create'))->withInput(); + // @codeCoverageIgnoreEnd } return redirect($this->getPreviousUri('tags.create.uri')); @@ -307,10 +349,11 @@ class TagController extends Controller Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: + // @codeCoverageIgnoreStart Session::put('tags.edit.fromUpdate', true); return redirect(route('tags.edit', [$tag->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd } // redirect to previous URL. @@ -336,7 +379,7 @@ class TagController extends Controller $cache->addProperty($tag->id); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $collection = new Collection; @@ -347,11 +390,11 @@ class TagController extends Controller // get expenses and what-not in this period and this tag. $arr = [ - 'date_string' => $end->format('Y-m-d'), - 'date_name' => Navigation::periodShow($end, $range), - 'date' => $end, - 'spent' => $this->repository->spentInperiod($tag, $end, $currentEnd), - 'earned' => $this->repository->earnedInperiod($tag, $end, $currentEnd), + 'string' => $end->format('Y-m-d'), + 'name' => Navigation::periodShow($end, $range), + 'date' => clone $end, + 'spent' => $this->repository->spentInperiod($tag, $end, $currentEnd), + 'earned' => $this->repository->earnedInperiod($tag, $end, $currentEnd), ]; $collection->push($arr); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 3fb1e41de7..8853a3dbaa 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; @@ -64,11 +64,13 @@ class ConvertController extends Controller */ public function index(TransactionType $destinationType, TransactionJournal $journal) { + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } + // @codeCoverageIgnoreEnd - $positiveAmount = TransactionJournal::amountPositive($journal); + $positiveAmount = $journal->amountPositive(); $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $sourceType = $journal->transactionType; $subTitle = trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]); @@ -89,8 +91,8 @@ class ConvertController extends Controller } // get source and destination account: - $sourceAccount = TransactionJournal::sourceAccountList($journal)->first(); - $destinationAccount = TransactionJournal::destinationAccountList($journal)->first(); + $sourceAccount = $journal->sourceAccountList()->first(); + $destinationAccount = $journal->destinationAccountList()->first(); return view( 'transactions.convert', @@ -117,20 +119,20 @@ class ConvertController extends Controller */ public function postIndex(Request $request, JournalRepositoryInterface $repository, TransactionType $destinationType, TransactionJournal $journal) { + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } + // @codeCoverageIgnoreEnd $data = $request->all(); - // cannot convert to its own type. if ($journal->transactionType->type === $destinationType->type) { Session::flash('error', trans('firefly.convert_is_already_type_' . $destinationType->type)); return redirect(route('transactions.show', [$journal->id])); } - // cannot convert split. if ($journal->transactions()->count() > 2) { Session::flash('error', trans('firefly.cannot_convert_split_journl')); @@ -165,13 +167,13 @@ class ConvertController extends Controller { /** @var AccountRepositoryInterface $accountRepository */ $accountRepository = app(AccountRepositoryInterface::class); - $sourceAccount = TransactionJournal::sourceAccountList($journal)->first(); - $destinationAccount = TransactionJournal::destinationAccountList($journal)->first(); + $sourceAccount = $journal->sourceAccountList()->first(); + $destinationAccount = $journal->destinationAccountList()->first(); $sourceType = $journal->transactionType; $joined = $sourceType->type . '-' . $destinationType->type; switch ($joined) { default: - throw new FireflyException('Cannot handle ' . $joined); + throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one $destination = $sourceAccount; break; @@ -180,6 +182,12 @@ class ConvertController extends Controller break; case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five + if ($data['destination_account_expense'] === '') { + // destination is a cash account. + $destination = $accountRepository->getCashAccount(); + + return $destination; + } $data = [ 'name' => $data['destination_account_expense'], 'accountType' => 'expense', @@ -210,15 +218,23 @@ class ConvertController extends Controller { /** @var AccountRepositoryInterface $accountRepository */ $accountRepository = app(AccountRepositoryInterface::class); - $sourceAccount = TransactionJournal::sourceAccountList($journal)->first(); - $destinationAccount = TransactionJournal::destinationAccountList($journal)->first(); + $sourceAccount = $journal->sourceAccountList()->first(); + $destinationAccount = $journal->destinationAccountList()->first(); $sourceType = $journal->transactionType; $joined = $sourceType->type . '-' . $destinationType->type; switch ($joined) { default: - throw new FireflyException('Cannot handle ' . $joined); + throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six + + if ($data['source_account_revenue'] === '') { + // destination is a cash account. + $destination = $accountRepository->getCashAccount(); + + return $destination; + } + $data = [ 'name' => $data['source_account_revenue'], 'accountType' => 'revenue', diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 5a61b060f4..285a73f3e9 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -9,18 +9,18 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; use Carbon\Carbon; -use ExpandedForm; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Support\Collection; use Preferences; @@ -118,8 +118,13 @@ class MassController extends Controller $subTitle = trans('firefly.mass_edit_journals'); /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $accountList = ExpandedForm::makeSelectList($repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); + $repository = app(AccountRepositoryInterface::class); + $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + + // get budgets + /** @var BudgetRepositoryInterface $budgetRepository */ + $budgetRepository = app(BudgetRepositoryInterface::class); + $budgets = $budgetRepository->getBudgets(); // skip transactions that have multiple destinations // or multiple sources: @@ -130,8 +135,8 @@ class MassController extends Controller * @var TransactionJournal $journal */ foreach ($journals as $index => $journal) { - $sources = TransactionJournal::sourceAccountList($journal); - $destinations = TransactionJournal::destinationAccountList($journal); + $sources = $journal->sourceAccountList($journal); + $destinations = $journal->destinationAccountList($journal); if ($sources->count() > 1) { $messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]); continue; @@ -144,7 +149,7 @@ class MassController extends Controller $filtered->push($journal); } - if (count($messages)) { + if (count($messages) > 0) { Session::flash('info', $messages); } @@ -156,9 +161,9 @@ class MassController extends Controller // set some values to be used in the edit routine: $filtered->each( function (TransactionJournal $journal) { - $journal->amount = TransactionJournal::amountPositive($journal); - $sources = TransactionJournal::sourceAccountList($journal); - $destinations = TransactionJournal::destinationAccountList($journal); + $journal->amount = $journal->amountPositive(); + $sources = $journal->sourceAccountList(); + $destinations = $journal->destinationAccountList(); $journal->transaction_count = $journal->transactions()->count(); if (!is_null($sources->first())) { $journal->source_account_id = $sources->first()->id; @@ -177,7 +182,7 @@ class MassController extends Controller $journals = $filtered; - return view('transactions.mass.edit', compact('journals', 'subTitle', 'accountList')); + return view('transactions.mass.edit', compact('journals', 'subTitle', 'accounts', 'budgets')); } /** @@ -195,12 +200,12 @@ class MassController extends Controller $journal = $repository->find(intval($journalId)); if ($journal) { // get optional fields: - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $what = strtolower($journal->transactionTypeStr()); $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0; $sourceAccountName = $request->get('source_account_name')[$journal->id] ?? ''; $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; $destAccountName = $request->get('destination_account_name')[$journal->id] ?? ''; - $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; + $budgetId = $request->get('budget_id')[$journal->id] ?? 0; $category = $request->get('category')[$journal->id]; $tags = $journal->tags->pluck('tag')->toArray(); @@ -214,12 +219,12 @@ class MassController extends Controller 'destination_account_id' => intval($destAccountId), 'destination_account_name' => $destAccountName, 'amount' => round($request->get('amount')[$journal->id], 12), - 'currency_id' => intval($request->get('amount_currency_id_amount_' . $journal->id)), + 'currency_id' => $journal->transaction_currency_id, 'date' => new Carbon($request->get('date')[$journal->id]), 'interest_date' => $journal->interest_date, 'book_date' => $journal->book_date, 'process_date' => $journal->process_date, - 'budget_id' => $budgetId, + 'budget_id' => intval($budgetId), 'category' => $category, 'tags' => $tags, diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 4a70121724..27c7cdba77 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; @@ -25,6 +25,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Log; @@ -48,7 +49,8 @@ class SingleController extends Controller /** @var BudgetRepositoryInterface */ private $budgets; - + /** @var CurrencyRepositoryInterface */ + private $currency; /** @var PiggyBankRepositoryInterface */ private $piggyBanks; @@ -71,6 +73,7 @@ class SingleController extends Controller $this->budgets = app(BudgetRepositoryInterface::class); $this->piggyBanks = app(PiggyBankRepositoryInterface::class); $this->attachments = app(AttachmentHelperInterface::class); + $this->currency = app(CurrencyRepositoryInterface::class); View::share('title', trans('firefly.transactions')); View::share('mainTitleIcon', 'fa-repeat'); @@ -83,8 +86,8 @@ class SingleController extends Controller public function cloneTransaction(TransactionJournal $journal) { - $source = TransactionJournal::sourceAccountList($journal)->first(); - $destination = TransactionJournal::destinationAccountList($journal)->first(); + $source = $journal->sourceAccountList()->first(); + $destination = $journal->destinationAccountList()->first(); $budget = $journal->budgets()->first(); $budgetId = is_null($budget) ? 0 : $budget->id; $category = $journal->categories()->first(); @@ -98,7 +101,7 @@ class SingleController extends Controller 'source_account_name' => $source->name, 'destination_account_id' => $destination->id, 'destination_account_name' => $destination->name, - 'amount' => TransactionJournal::amountPositive($journal), + 'amount' => $journal->amountPositive(), 'date' => $journal->date->format('Y-m-d'), 'budget_id' => $budgetId, 'category' => $categoryName, @@ -162,9 +165,12 @@ class SingleController extends Controller */ public function delete(TransactionJournal $journal) { + // Covered by another controller's tests + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } + // @codeCoverageIgnoreEnd $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]); @@ -187,10 +193,12 @@ class SingleController extends Controller */ public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal) { + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($transactionJournal)) { return $this->redirectToAccount($transactionJournal); } - $type = TransactionJournal::transactionTypeStr($transactionJournal); + // @codeCoverageIgnoreEnd + $type = $transactionJournal->transactionTypeStr(); Session::flash('success', strval(trans('firefly.deleted_' . strtolower($type), ['description' => e($transactionJournal->description)]))); $repository->delete($transactionJournal); @@ -207,9 +215,11 @@ class SingleController extends Controller */ public function edit(TransactionJournal $journal) { + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } + // @codeCoverageIgnoreEnd $count = $journal->transactions()->count(); @@ -217,40 +227,54 @@ class SingleController extends Controller return redirect(route('transactions.split.edit', [$journal->id])); } - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $what = strtolower($journal->transactionTypeStr()); $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets()); // view related code $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - // journal related code - $sourceAccounts = TransactionJournal::sourceAccountList($journal); - $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $sourceAccounts = $journal->sourceAccountList(); + $destinationAccounts = $journal->destinationAccountList(); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $preFilled = [ - 'date' => TransactionJournal::dateAsString($journal), - 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), - 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), - 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), - 'category' => TransactionJournal::categoryAsString($journal), - 'budget_id' => TransactionJournal::budgetId($journal), + 'date' => $journal->dateAsString(), + 'interest_date' => $journal->dateAsString('interest_date'), + 'book_date' => $journal->dateAsString('book_date'), + 'process_date' => $journal->dateAsString('process_date'), + 'category' => $journal->categoryAsString(), + 'budget_id' => $journal->budgetId(), 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), 'source_account_id' => $sourceAccounts->first()->id, 'source_account_name' => $sourceAccounts->first()->edit_name, 'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_name' => $destinationAccounts->first()->edit_name, - 'amount' => TransactionJournal::amountPositive($journal), + 'amount' => $journal->amountPositive(), + 'currency' => $journal->transactionCurrency, // new custom fields: - 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'), - 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'), - 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'), + 'due_date' => $journal->dateAsString('due_date'), + 'payment_date' => $journal->dateAsString('payment_date'), + 'invoice_date' => $journal->dateAsString('invoice_date'), 'interal_reference' => $journal->getMeta('internal_reference'), 'notes' => $journal->getMeta('notes'), + + // exchange rate fields + 'native_amount' => $journal->amountPositive(), + 'native_currency' => $journal->transactionCurrency, ]; + // if user has entered a foreign currency, update some fields + $foreignCurrencyId = intval($journal->getMeta('foreign_currency_id')); + if ($foreignCurrencyId > 0) { + // update some fields in pre-filled. + // @codeCoverageIgnoreStart + $preFilled['amount'] = $journal->getMeta('foreign_amount'); + $preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id'))); + // @codeCoverageIgnoreEnd + } + if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { $preFilled['destination_account_name'] = ''; } @@ -313,22 +337,20 @@ class SingleController extends Controller Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); Preferences::mark(); + // @codeCoverageIgnoreStart if ($createAnother === true) { - // set value so create routine will not overwrite URL: Session::put('transactions.create.fromStore', true); return redirect(route('transactions.create', [$request->input('what')]))->withInput(); } if ($doSplit === true) { - // redirect to edit screen: return redirect(route('transactions.split.edit', [$journal->id])); } + // @codeCoverageIgnoreEnd - // redirect to previous URL. return redirect($this->getPreviousUri('transactions.create.uri')); - } /** @@ -340,9 +362,11 @@ class SingleController extends Controller */ public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { + // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } + // @codeCoverageIgnoreEnd $data = $request->getJournalData(); $journal = $repository->update($journal, $data); @@ -350,31 +374,29 @@ class SingleController extends Controller $files = $request->hasFile('attachments') ? $request->file('attachments') : null; $this->attachments->saveAttachmentsForModel($journal, $files); - // flash errors + // @codeCoverageIgnoreStart if (count($this->attachments->getErrors()->get('attachments')) > 0) { Session::flash('error', $this->attachments->getErrors()->get('attachments')); } - // flash messages if (count($this->attachments->getMessages()->get('attachments')) > 0) { Session::flash('info', $this->attachments->getMessages()->get('attachments')); } + // @codeCoverageIgnoreEnd event(new UpdatedTransactionJournal($journal)); // update, get events by date and sort DESC - $type = strtolower(TransactionJournal::transactionTypeStr($journal)); + $type = strtolower($journal->transactionTypeStr()); Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])]))); Preferences::mark(); - // if wishes to split: - - + // @codeCoverageIgnoreStart if (intval($request->get('return_to_edit')) === 1) { - // set value so edit routine will not overwrite URL: Session::put('transactions.edit.fromUpdate', true); return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); } + // @codeCoverageIgnoreEnd // redirect to previous URL. return redirect($this->getPreviousUri('transactions.edit.uri')); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index e667cb5f1b..c587129811 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; @@ -141,25 +141,27 @@ class SplitController extends Controller $files = $request->hasFile('attachments') ? $request->file('attachments') : null; // save attachments: $this->attachments->saveAttachmentsForModel($journal, $files); - event(new UpdatedTransactionJournal($journal)); - // update, get events by date and sort DESC // flash messages + // @codeCoverageIgnoreStart if (count($this->attachments->getMessages()->get('attachments')) > 0) { Session::flash('info', $this->attachments->getMessages()->get('attachments')); } + // @codeCoverageIgnoreEnd - $type = strtolower(TransactionJournal::transactionTypeStr($journal)); + $type = strtolower($journal->transactionTypeStr()); Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])]))); Preferences::mark(); + // @codeCoverageIgnoreStart if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('transactions.edit-split.fromUpdate', true); return redirect(route('transactions.split.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); } + // @codeCoverageIgnoreEnd // redirect to previous URL. return redirect($this->getPreviousUri('transactions.edit-split.uri')); @@ -207,18 +209,18 @@ class SplitController extends Controller */ private function arrayFromJournal(Request $request, TransactionJournal $journal): array { - $sourceAccounts = TransactionJournal::sourceAccountList($journal); - $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $sourceAccounts = $journal->sourceAccountList(); + $destinationAccounts = $journal->destinationAccountList(); $array = [ 'journal_description' => $request->old('journal_description', $journal->description), - 'journal_amount' => TransactionJournal::amountPositive($journal), + 'journal_amount' => $journal->amountPositive(), 'sourceAccounts' => $sourceAccounts, 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), 'currency_id' => $request->old('currency_id', $journal->transaction_currency_id), 'destinationAccounts' => $destinationAccounts, - 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), + 'what' => strtolower($journal->transactionTypeStr()), 'date' => $request->old('date', $journal->date), 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), @@ -263,8 +265,8 @@ class SplitController extends Controller // set initial category and/or budget: if (count($transactions) === 1 && $index === 0) { - $set['budget_id'] = TransactionJournal::budgetId($journal); - $set['category'] = TransactionJournal::categoryAsString($journal); + $set['budget_id'] = $journal->budgetId(); + $set['category'] = $journal->categoryAsString(); } $return[] = $set; diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index b0ef6013ab..1e838c5dec 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -9,21 +9,26 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; +use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; use Navigation; use Preferences; use Response; +use Steam; use View; /** @@ -57,112 +62,81 @@ class TransactionController extends Controller * @param JournalRepositoryInterface $repository * @param string $what * + * @param string $moment + * * @return View */ - public function index(Request $request, JournalRepositoryInterface $repository, string $what) + public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '') { - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + // default values: $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + $count = 0; + $loop = 0; $range = Preferences::get('viewRange', '1M')->data; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - // to make sure we only grab a subset, based on the current date (in session): - $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); - $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); + $start = null; + $end = null; + $periods = new Collection; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->withBudgetInformation() - ->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what); - - unset($start, $end); - - // then also show a list of periods where the user can click on, based on the - // user's range and the oldest journal the user has: - $first = $repository->first(); - $blockStart = is_null($first->id) ? new Carbon : $first->date; - $blockStart = Navigation::startOfPeriod($blockStart, $range); - $blockEnd = Navigation::endOfX(new Carbon, $range); - $entries = new Collection; - - while ($blockEnd >= $blockStart) { - Log::debug(sprintf('Now at blockEnd: %s', $blockEnd->format('Y-m-d'))); - $blockEnd = Navigation::startOfPeriod($blockEnd, $range); - $dateStr = $blockEnd->format('Y-m-d'); - $dateName = Navigation::periodShow($blockEnd, $range); - $entries->push([$dateStr, $dateName]); - $blockEnd = Navigation::subtractPeriod($blockEnd, $range, 1); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_' . $what); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; } - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'entries')); + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodOverview($what); + } - } + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->getPeriodOverview($what); + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at transaction loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount(); + $collector->removeFilter(InternalTransferFilter::class); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('/transactions/' . $what); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } - /** - * @param Request $request - * @param string $what - * - * @return View - */ - public function indexAll(Request $request, string $what) - { - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = sprintf('%s (%s)', trans('firefly.title_' . $what), strtolower(trans('firefly.everything'))); - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + if ($moment != 'all' && $loop > 1) { + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->withBudgetInformation()->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what . '/all'); - - return view('transactions.index-all', compact('subTitle', 'what', 'subTitleIcon', 'journals')); - - } - - /** - * @param Request $request - * @param string $what - * - * @param string $date - * - * @return View - */ - public function indexByDate(Request $request, string $what, string $date) - { - $carbon = new Carbon($date); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what) . ' (' . Navigation::periodShow($carbon, $range) . ')'; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - - Log::debug(sprintf('Transaction index by date will show between %s and %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); - - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts(); - $collector->setRange($start, $end)->withBudgetInformation()->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what . '/' . $date); - - return view('transactions.index-date', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'carbon')); + return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'periods', 'start', 'end', 'moment')); } @@ -181,10 +155,9 @@ class TransactionController extends Controller $ids = array_unique($ids); foreach ($ids as $id) { $journal = $repository->find(intval($id)); - if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) { - $journal->order = $order; + if ($journal && $journal->date->isSameDay($date)) { + $repository->setOrder($journal, $order); $order++; - $journal->save(); } } } @@ -206,14 +179,99 @@ class TransactionController extends Controller return $this->redirectToAccount($journal); } - $events = $tasker->getPiggyBankEvents($journal); - $transactions = $tasker->getTransactionsOverview($journal); - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; + $events = $tasker->getPiggyBankEvents($journal); + $transactions = $tasker->getTransactionsOverview($journal); + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; + $foreignCurrency = null; - return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); + if ($journal->hasMeta('foreign_currency_id')) { + // @codeCoverageIgnoreStart + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id'))); + // @codeCoverageIgnoreEnd + } + + return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency')); } + /** + * @param string $what + * + * @return Collection + * @throws FireflyException + */ + private function getPeriodOverview(string $what): Collection + { + $repository = app(JournalRepositoryInterface::class); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; + $types = config('firefly.transactionTypesByWhat.' . $what); + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($what); + $cache->addProperty('transaction-list-entries'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d'))); + while ($end >= $start) { + Log::debug('Loop start!'); + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + + // count journals without budget in this period: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withOpposingAccount()->setTypes($types); + $collector->removeFilter(InternalTransferFilter::class); + $set = $collector->getJournals(); + $sum = $set->sum('transaction_amount'); + $journals = $set->count(); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $array = [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $journals, + 'spent' => 0, + 'earned' => 0, + 'transferred' => 0, + 'date' => clone $end, + ]; + Log::debug(sprintf('What is %s', $what)); + switch ($what) { + case 'withdrawal': + $array['spent'] = $sum; + break; + case 'deposit': + $array['earned'] = $sum; + break; + case 'transfers': + case 'transfer': + $array['transferred'] = Steam::positive($sum); + break; + + } + $entries->push($array); + $end = Navigation::subtractPeriod($end, $range, 1); + } + Log::debug('End of loop'); + $cache->store($entries); + + return $entries; + } + } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 289a6924d1..5224ad8579 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http; diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 529546437d..5733cb1c1a 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; diff --git a/app/Http/Middleware/AuthenticateTwoFactor.php b/app/Http/Middleware/AuthenticateTwoFactor.php index 4f9f1f5542..625f55ba23 100644 --- a/app/Http/Middleware/AuthenticateTwoFactor.php +++ b/app/Http/Middleware/AuthenticateTwoFactor.php @@ -9,13 +9,15 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; use Closure; +use Cookie; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Log; use Preferences; use Session; @@ -55,8 +57,13 @@ class AuthenticateTwoFactor } $is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data; $has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); - $is2faAuthed = Session::get('twofactor-authenticated'); + + // grab 2auth information from cookie, not from session. + $is2faAuthed = Cookie::get('twoFactorAuthenticated') === 'true'; + if ($is2faEnabled && $has2faSecret && !$is2faAuthed) { + Log::debug('Does not seem to be 2 factor authed, redirect.'); + return redirect(route('two-factor.index')); } diff --git a/app/Http/Middleware/Binder.php b/app/Http/Middleware/Binder.php index 79cf3267fd..52ac1c10ab 100644 --- a/app/Http/Middleware/Binder.php +++ b/app/Http/Middleware/Binder.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 6475f4f6da..85188c9c17 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; diff --git a/app/Http/Middleware/IsAdmin.php b/app/Http/Middleware/IsAdmin.php index 823be2f20d..a8e82710dc 100644 --- a/app/Http/Middleware/IsAdmin.php +++ b/app/Http/Middleware/IsAdmin.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; diff --git a/app/Http/Middleware/IsLimitedUser.php b/app/Http/Middleware/IsLimitedUser.php new file mode 100644 index 0000000000..6b22eccda8 --- /dev/null +++ b/app/Http/Middleware/IsLimitedUser.php @@ -0,0 +1,61 @@ +guest()) { + if ($request->ajax()) { + return response('Unauthorized.', 401); + } + + return redirect()->guest('login'); + } + /** @var User $user */ + $user = auth()->user(); + if ($user->hasRole('demo')) { + Session::flash('warning', strval(trans('firefly.not_available_demo_user'))); + + return redirect(route('index')); + } + + if (intval(getenv('SANDSTORM')) === 1) { + Session::flash('warning', strval(trans('firefly.sandstorm_not_available'))); + + return redirect(route('index')); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index fdf5322a4d..025478e7df 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; @@ -17,7 +17,6 @@ use Amount; use App; use Carbon\Carbon; use Closure; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Contracts\Auth\Guard; use Illuminate\Http\Request; diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index e7cf7f3bcf..e2ba0517f2 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; diff --git a/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php b/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php index 43cc865dc7..1320204959 100644 --- a/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php +++ b/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php @@ -9,14 +9,15 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; use Closure; +use Cookie; use Illuminate\Support\Facades\Auth; use Preferences; -use Session; + /** * Class RedirectIfTwoFactorAuthenticated @@ -40,7 +41,10 @@ class RedirectIfTwoFactorAuthenticated $is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data; $has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); - $is2faAuthed = Session::get('twofactor-authenticated'); + + // grab 2auth information from cookie + $is2faAuthed = Cookie::get('twoFactorAuthenticated') === 'true'; + if ($is2faEnabled && $has2faSecret && $is2faAuthed) { return redirect('/'); } diff --git a/app/Http/Middleware/Sandstorm.php b/app/Http/Middleware/Sandstorm.php index 92225ceef5..a9ba75bd09 100644 --- a/app/Http/Middleware/Sandstorm.php +++ b/app/Http/Middleware/Sandstorm.php @@ -7,12 +7,14 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; use Auth; use Closure; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Http\Request; use View; @@ -33,6 +35,7 @@ class Sandstorm * @param string|null $guard * * @return mixed + * @throws FireflyException */ public function handle(Request $request, Closure $next, $guard = null) { @@ -45,27 +48,55 @@ class Sandstorm // we're in sandstorm! is user a guest? if (Auth::guard($guard)->guest()) { - $userId = strval($request->header('X-Sandstorm-User-Id')); - if (strlen($userId) > 0) { - // find user? - $email = $userId . '@firefly'; - $user = User::whereEmail($email)->first(); - if (is_null($user)) { - $user = User::create( - [ - 'email' => $email, - 'password' => str_random(16), - ] - ); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $userId = strval($request->header('X-Sandstorm-User-Id')); + $count = $repository->count(); - } - - - // login user: + // if there already is one user in this instance, we assume this is + // the "main" user. Firefly's nature does not allow other users to + // access the same data so we have no choice but to simply login + // the new user to the same account and just forget about Bob and Alice + // and any other differences there may be between these users. + if ($count === 1 && strlen($userId) > 0) { + // login as first user user. + $user = User::first(); Auth::guard($guard)->login($user); - } else { - echo 'user id no length, guest?'; - exit; + View::share('SANDSTORM_ANON', false); + + return $next($request); + } + + if ($count === 1 && strlen($userId) === 0) { + // login but indicate anonymous + $user = User::first(); + Auth::guard($guard)->login($user); + View::share('SANDSTORM_ANON', true); + + return $next($request); + } + + if ($count === 0 && strlen($userId) > 0) { + // create new user. + $email = $userId . '@firefly'; + /** @var User $user */ + $user = User::create( + [ + 'email' => $email, + 'password' => str_random(16), + ] + ); + Auth::guard($guard)->login($user); + + return $next($request); + } + + if ($count === 0 && strlen($userId) === 0) { + throw new FireflyException('The first visit to a new Firefly III administration cannot be by a guest user.'); + } + + if ($count > 1) { + throw new FireflyException('Your Firefly III installation has more than one user, which is weird.'); } } diff --git a/app/Http/Middleware/StartFireflySession.php b/app/Http/Middleware/StartFireflySession.php index ba6c518a08..c419b0e8c3 100644 --- a/app/Http/Middleware/StartFireflySession.php +++ b/app/Http/Middleware/StartFireflySession.php @@ -7,14 +7,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; use Illuminate\Http\Request; use Illuminate\Session\Middleware\StartSession; use Illuminate\Session\SessionManager; -use Log; /** * Class StartFireflySession diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 11ec0455c9..98ded14137 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -8,13 +8,14 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Middleware; +use Carbon\Carbon; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier; use Symfony\Component\HttpFoundation\Cookie; -use Carbon\Carbon; + /** * Class VerifyCsrfToken * @@ -35,8 +36,9 @@ class VerifyCsrfToken extends BaseVerifier /** * Add the CSRF token to the response cookies. * - * @param \Illuminate\Http\Request $request - * @param \Symfony\Component\HttpFoundation\Response $response + * @param \Illuminate\Http\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * * @return \Symfony\Component\HttpFoundation\Response */ protected function addCookieToResponse($request, $response) diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index cadf22b600..90b8eb760d 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; @@ -43,14 +43,12 @@ class AccountFormRequest extends Request 'accountType' => $this->string('what'), 'currency_id' => $this->integer('currency_id'), 'virtualBalance' => $this->float('virtualBalance'), - 'virtualBalanceCurrency' => $this->integer('amount_currency_id_virtualBalance'), 'iban' => $this->string('iban'), 'BIC' => $this->string('BIC'), 'accountNumber' => $this->string('accountNumber'), 'accountRole' => $this->string('accountRole'), 'openingBalance' => $this->float('openingBalance'), 'openingBalanceDate' => $this->date('openingBalanceDate'), - 'openingBalanceCurrency' => $this->integer('amount_currency_id_openingBalance'), 'ccType' => $this->string('ccType'), 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'), ]; diff --git a/app/Http/Requests/AttachmentFormRequest.php b/app/Http/Requests/AttachmentFormRequest.php index 6f7608bc46..30d2fe6d2b 100644 --- a/app/Http/Requests/AttachmentFormRequest.php +++ b/app/Http/Requests/AttachmentFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 992542eca1..1dbe2d707b 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; @@ -58,7 +58,7 @@ class BillFormRequest extends Request $nameRule = 'required|between:1,255|uniqueObjectForUser:bills,name'; $matchRule = 'required|between:1,255|uniqueObjectForUser:bills,match'; if (intval($this->get('id')) > 0) { - $nameRule .= ',' . intval($this->get('id')); + $nameRule .= ',' . intval($this->get('id')); $matchRule .= ',' . intval($this->get('id')); } diff --git a/app/Http/Requests/BudgetFormRequest.php b/app/Http/Requests/BudgetFormRequest.php index c7d9d0de63..5c6f1ce537 100644 --- a/app/Http/Requests/BudgetFormRequest.php +++ b/app/Http/Requests/BudgetFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/BudgetIncomeRequest.php b/app/Http/Requests/BudgetIncomeRequest.php index e8c2c93e39..8af316dd48 100644 --- a/app/Http/Requests/BudgetIncomeRequest.php +++ b/app/Http/Requests/BudgetIncomeRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/CategoryFormRequest.php b/app/Http/Requests/CategoryFormRequest.php index b7b5e94a5e..9fb1942d20 100644 --- a/app/Http/Requests/CategoryFormRequest.php +++ b/app/Http/Requests/CategoryFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/ConfigurationRequest.php b/app/Http/Requests/ConfigurationRequest.php index 452e613e73..15cfe56a43 100644 --- a/app/Http/Requests/ConfigurationRequest.php +++ b/app/Http/Requests/ConfigurationRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/CurrencyFormRequest.php b/app/Http/Requests/CurrencyFormRequest.php index 0449f9b4ab..6a779665e5 100644 --- a/app/Http/Requests/CurrencyFormRequest.php +++ b/app/Http/Requests/CurrencyFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/DeleteAccountFormRequest.php b/app/Http/Requests/DeleteAccountFormRequest.php index 9586af3767..b3dd484d7e 100644 --- a/app/Http/Requests/DeleteAccountFormRequest.php +++ b/app/Http/Requests/DeleteAccountFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/ExportFormRequest.php b/app/Http/Requests/ExportFormRequest.php index 028a3246cf..16a5638302 100644 --- a/app/Http/Requests/ExportFormRequest.php +++ b/app/Http/Requests/ExportFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/ImportUploadRequest.php b/app/Http/Requests/ImportUploadRequest.php index 62e4f117d8..93065bcada 100644 --- a/app/Http/Requests/ImportUploadRequest.php +++ b/app/Http/Requests/ImportUploadRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 62dc5656d2..12d35d8617 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; @@ -67,6 +67,11 @@ class JournalFormRequest extends Request 'destination_account_name' => $this->string('destination_account_name'), 'piggy_bank_id' => $this->integer('piggy_bank_id'), + // native amount and stuff like that: + 'native_amount' => $this->float('native_amount'), + 'source_amount' => $this->float('source_amount'), + 'destination_amount' => $this->float('destination_amount'), + ]; return $data; @@ -101,6 +106,11 @@ class JournalFormRequest extends Request 'destination_account_id' => 'numeric|belongsToUser:accounts,id', 'destination_account_name' => 'between:1,255', 'piggy_bank_id' => 'between:1,255', + + // foreign currency amounts + 'native_amount' => 'numeric|more:0', + 'source_amount' => 'numeric|more:0', + 'destination_amount' => 'numeric|more:0', ]; // some rules get an upgrade depending on the type of data: diff --git a/app/Http/Requests/MassDeleteJournalRequest.php b/app/Http/Requests/MassDeleteJournalRequest.php index 13283012d1..004f4aa4ed 100644 --- a/app/Http/Requests/MassDeleteJournalRequest.php +++ b/app/Http/Requests/MassDeleteJournalRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index 03ae6ae6f7..7067d34e05 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/NewUserFormRequest.php b/app/Http/Requests/NewUserFormRequest.php index b8627cffa2..b5ef0fd040 100644 --- a/app/Http/Requests/NewUserFormRequest.php +++ b/app/Http/Requests/NewUserFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/PiggyBankFormRequest.php b/app/Http/Requests/PiggyBankFormRequest.php index ba969dcf64..c2d9f7f9e5 100644 --- a/app/Http/Requests/PiggyBankFormRequest.php +++ b/app/Http/Requests/PiggyBankFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/ProfileFormRequest.php b/app/Http/Requests/ProfileFormRequest.php index 02bce0c99c..fe1ac7bf93 100644 --- a/app/Http/Requests/ProfileFormRequest.php +++ b/app/Http/Requests/ProfileFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php index 02a5f5c8e5..db8b5fbe74 100644 --- a/app/Http/Requests/ReportFormRequest.php +++ b/app/Http/Requests/ReportFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index e73b17ac60..16e77bad4a 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; @@ -87,7 +87,7 @@ class Request extends FormRequest */ protected function string(string $field): string { - $string = $this->get($field) ?? ''; + $string = $this->get($field) ?? ''; $search = [ "\u{0001}", // start of heading "\u{0002}", // start of text diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 6295bd9e3f..8206b53264 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/RuleGroupFormRequest.php b/app/Http/Requests/RuleGroupFormRequest.php index 7d2250882f..4e94676e23 100644 --- a/app/Http/Requests/RuleGroupFormRequest.php +++ b/app/Http/Requests/RuleGroupFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php index 097089ff79..5b85dd3b26 100644 --- a/app/Http/Requests/SelectTransactionsRequest.php +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index 3d82027c6b..2c1fab0dac 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php index 544b53c0b0..d61e5cb8d9 100644 --- a/app/Http/Requests/TagFormRequest.php +++ b/app/Http/Requests/TagFormRequest.php @@ -9,10 +9,10 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Http\Requests; -use Carbon\Carbon; use FireflyIII\Repositories\Tag\TagRepositoryInterface; /** @@ -46,11 +46,10 @@ class TagFormRequest extends Request $longitude = null; $zoomLevel = null; } - $date = $this->get('date') ?? ''; $data = [ 'tag' => $this->string('tag'), - 'date' => $this->date($date), + 'date' => $this->date('date'), 'description' => $this->string('description'), 'latitude' => $latitude, 'longitude' => $longitude, diff --git a/app/Http/Requests/TestRuleFormRequest.php b/app/Http/Requests/TestRuleFormRequest.php index df76fd08a3..c2d3bd8c61 100644 --- a/app/Http/Requests/TestRuleFormRequest.php +++ b/app/Http/Requests/TestRuleFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/TokenFormRequest.php b/app/Http/Requests/TokenFormRequest.php index 4c90a2539f..8e30d3eaa7 100644 --- a/app/Http/Requests/TokenFormRequest.php +++ b/app/Http/Requests/TokenFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; diff --git a/app/Http/Requests/UserFormRequest.php b/app/Http/Requests/UserFormRequest.php index 436e3c9d33..08a7076038 100644 --- a/app/Http/Requests/UserFormRequest.php +++ b/app/Http/Requests/UserFormRequest.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Http\Requests; @@ -37,7 +37,7 @@ class UserFormRequest extends Request { return [ 'email' => $this->string('email'), - 'blocked' => $this->integer('blocked'), + 'blocked' => $this->integer('blocked') === 1, 'blocked_code' => $this->string('blocked_code'), 'password' => $this->string('password'), ]; @@ -50,7 +50,7 @@ class UserFormRequest extends Request { return [ 'id' => 'required|exists:users,id', - 'email' => 'required', + 'email' => 'email|required', 'password' => 'confirmed', 'blocked_code' => 'between:0,30', 'blocked' => 'between:0,1|numeric', diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 05bf0acb56..1b98afd9b4 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Carbon\Carbon; use DaveJamesMiller\Breadcrumbs\Generator as BreadCrumbGenerator; use FireflyIII\Exceptions\FireflyException; @@ -67,42 +67,31 @@ Breadcrumbs::register( ); Breadcrumbs::register( - 'accounts.show', function (BreadCrumbGenerator $breadcrumbs, Account $account) { + 'accounts.show', function (BreadCrumbGenerator $breadcrumbs, Account $account, string $moment, Carbon $start, Carbon $end) { $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $breadcrumbs->parent('accounts.index', $what); $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); + + // push when is all: + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('accounts.show', [$account->id, 'all'])); + } + // when is specific period or when empty: + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('accounts.show', [$account->id, $moment, $start, $end])); + } + } ); -Breadcrumbs::register( - 'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start, Carbon $end) { - - $startString = $start->formatLocalized(strval(trans('config.month_and_day'))); - $endString = $end->formatLocalized(strval(trans('config.month_and_day'))); - $title = sprintf('%s (%s)', $account->name, trans('firefly.from_to', ['start' => $startString, 'end' => $endString])); - - $breadcrumbs->parent('accounts.show', $account); - $breadcrumbs->push($title, route('accounts.show.date', [$account->id, $start->format('Y-m-d')])); -} -); - -Breadcrumbs::register( - 'accounts.show.all', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start, Carbon $end) { - - $startString = $start->formatLocalized(strval(trans('config.month_and_day'))); - $endString = $end->formatLocalized(strval(trans('config.month_and_day'))); - $title = sprintf('%s (%s)', $account->name, trans('firefly.from_to', ['start' => $startString, 'end' => $endString])); - - $breadcrumbs->parent('accounts.show', $account); - $breadcrumbs->push($title, route('accounts.show.all', [$account->id, $start->format('Y-m-d')])); -} -); - - Breadcrumbs::register( 'accounts.delete', function (BreadCrumbGenerator $breadcrumbs, Account $account) { - $breadcrumbs->parent('accounts.show', $account); + $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.delete_account', ['name' => e($account->name)]), route('accounts.delete', [$account->id])); } ); @@ -110,7 +99,7 @@ Breadcrumbs::register( Breadcrumbs::register( 'accounts.edit', function (BreadCrumbGenerator $breadcrumbs, Account $account) { - $breadcrumbs->parent('accounts.show', $account); + $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => e($account->name)]), route('accounts.edit', [$account->id])); @@ -260,9 +249,24 @@ Breadcrumbs::register( ); Breadcrumbs::register( - 'budgets.no-budget', function (BreadCrumbGenerator $breadcrumbs, $subTitle) { + 'budgets.no-budget', function (BreadCrumbGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push($subTitle, route('budgets.no-budget')); + $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget')); + + // push when is all: + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all'])); + } + // when is specific period: + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('budgets.no-budget', [$moment])); + } + + } ); @@ -270,6 +274,7 @@ Breadcrumbs::register( 'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget) { $breadcrumbs->parent('budgets.index'); $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); + $breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id])); } ); @@ -279,11 +284,8 @@ Breadcrumbs::register( $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); $title = trans( - 'firefly.budget_in_period_breadcrumb', [ - 'name' => $budget->name, - 'start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))), - 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))), - ] + 'firefly.between_dates_breadcrumb', ['start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))),] ); $breadcrumbs->push( @@ -310,52 +312,61 @@ Breadcrumbs::register( Breadcrumbs::register( 'categories.edit', function (BreadCrumbGenerator $breadcrumbs, Category $category) { - $breadcrumbs->parent('categories.show', $category); + $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.edit_category', ['name' => e($category->name)]), route('categories.edit', [$category->id])); } ); Breadcrumbs::register( 'categories.delete', function (BreadCrumbGenerator $breadcrumbs, Category $category) { - $breadcrumbs->parent('categories.show', $category); + $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.delete_category', ['name' => e($category->name)]), route('categories.delete', [$category->id])); } ); Breadcrumbs::register( - 'categories.show', function (BreadCrumbGenerator $breadcrumbs, Category $category) { - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push(e($category->name), route('categories.show', [$category->id])); + 'categories.show', function (BreadCrumbGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + + // push when is all: + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); + } + // when is specific period: + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('categories.show', [$category->id, $moment])); + } } ); + Breadcrumbs::register( - 'categories.show.all', function (BreadCrumbGenerator $breadcrumbs, Category $category) { + 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); - $breadcrumbs->push(e($category->name) . '(' . strtolower(trans('firefly.all_periods')) . ')', route('categories.show.all', [$category->id])); + $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); + + // push when is all: + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all'])); + } + // when is specific period: + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('categories.no-category', [$moment])); + } + } ); -Breadcrumbs::register( - 'categories.show.date', function (BreadCrumbGenerator $breadcrumbs, Category $category, Carbon $date) { - - // get current period preference. - $range = Preferences::get('viewRange', '1M')->data; - - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push(e($category->name), route('categories.show', [$category->id])); - $breadcrumbs->push(Navigation::periodShow($date, $range), route('categories.show.date', [$category->id, $date->format('Y-m-d')])); - -} -); - -Breadcrumbs::register( - 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, $subTitle) { - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push($subTitle, route('categories.no-category')); -} -); /** * CURRENCIES @@ -556,6 +567,19 @@ Breadcrumbs::register( } ); +Breadcrumbs::register( + 'reports.report.tag', function (BreadCrumbGenerator $breadcrumbs, string $accountIds, string $tagTags, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_tag', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.tag', [$accountIds, $tagTags, $start->format('Ymd'), $end->format('Ymd')])); +} +); + Breadcrumbs::register( 'reports.report.category', function (BreadCrumbGenerator $breadcrumbs, string $accountIds, string $categoryIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); @@ -686,23 +710,33 @@ Breadcrumbs::register( Breadcrumbs::register( 'tags.edit', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { - $breadcrumbs->parent('tags.show', $tag); + $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => e($tag->tag)]), route('tags.edit', [$tag->id])); } ); Breadcrumbs::register( 'tags.delete', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { - $breadcrumbs->parent('tags.show', $tag); + $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]), route('tags.delete', [$tag->id])); } ); Breadcrumbs::register( - 'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { + 'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('tags.index'); - $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id])); + $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id], $moment)); + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id], $moment)); + } + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('tags.show', [$tag->id], $moment)); + } } ); @@ -710,36 +744,30 @@ Breadcrumbs::register( * TRANSACTIONS */ Breadcrumbs::register( - 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what) { + 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what, string $moment = '', Carbon $start, Carbon $end) { + + $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); -} -); + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('transactions.index', [$what, 'all'])); + } -Breadcrumbs::register( - 'transactions.index.all', function (BreadCrumbGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('transactions.index', $what); + // when is specific period: + if ($moment !== 'all') { + $title = trans( + 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('transactions.index', [$what, $moment])); + } - $title = sprintf('%s (%s)', trans('breadcrumbs.' . $what . '_list'), strtolower(trans('firefly.everything'))); - - $breadcrumbs->push($title, route('transactions.index.all', [$what])); -} -); - -Breadcrumbs::register( - 'transactions.index.date', function (BreadCrumbGenerator $breadcrumbs, string $what, Carbon $date) { - $breadcrumbs->parent('transactions.index', $what); - - $range = Preferences::get('viewRange', '1M')->data; - $title = trans('breadcrumbs.' . $what . '_list') . ' (' . Navigation::periodShow($date, $range) . ')'; - - $breadcrumbs->push($title, route('transactions.index.date', [$what, $date->format('Y-m-d')])); } ); Breadcrumbs::register( 'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); } ); @@ -761,7 +789,7 @@ Breadcrumbs::register( 'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { $what = strtolower($journal->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon); $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); } ); @@ -783,10 +811,16 @@ Breadcrumbs::register( Breadcrumbs::register( 'transactions.mass.edit', function (BreadCrumbGenerator $breadcrumbs, Collection $journals) { - $journalIds = $journals->pluck('id')->toArray(); - $what = strtolower($journals->first()->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); + if ($journals->count() > 0) { + $journalIds = $journals->pluck('id')->toArray(); + $what = strtolower($journals->first()->transactionType->type); + $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon); + $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); + + return; + } + + $breadcrumbs->parent('index'); } ); @@ -795,7 +829,7 @@ Breadcrumbs::register( $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds)); } ); diff --git a/app/Import/Converter/AccountId.php b/app/Import/Converter/AccountId.php index 4c07790fa5..88161515ab 100644 --- a/app/Import/Converter/AccountId.php +++ b/app/Import/Converter/AccountId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index ab0fceb6b7..c7840e397c 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/AssetAccountIban.php b/app/Import/Converter/AssetAccountIban.php index d2336c282d..f98bef6110 100644 --- a/app/Import/Converter/AssetAccountIban.php +++ b/app/Import/Converter/AssetAccountIban.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/AssetAccountName.php b/app/Import/Converter/AssetAccountName.php index 8618d52f2d..8355c3b056 100644 --- a/app/Import/Converter/AssetAccountName.php +++ b/app/Import/Converter/AssetAccountName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/AssetAccountNumber.php b/app/Import/Converter/AssetAccountNumber.php index d389492c91..880b4d3061 100644 --- a/app/Import/Converter/AssetAccountNumber.php +++ b/app/Import/Converter/AssetAccountNumber.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/BasicConverter.php b/app/Import/Converter/BasicConverter.php index 91183ba936..49fba30f0d 100644 --- a/app/Import/Converter/BasicConverter.php +++ b/app/Import/Converter/BasicConverter.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/BillId.php b/app/Import/Converter/BillId.php index cb20e4c7c9..11c91d6536 100644 --- a/app/Import/Converter/BillId.php +++ b/app/Import/Converter/BillId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/BillName.php b/app/Import/Converter/BillName.php index 3d2dbe9a71..2ba4ad2761 100644 --- a/app/Import/Converter/BillName.php +++ b/app/Import/Converter/BillName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/BudgetId.php b/app/Import/Converter/BudgetId.php index cf709c0beb..ea74b8302f 100644 --- a/app/Import/Converter/BudgetId.php +++ b/app/Import/Converter/BudgetId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/BudgetName.php b/app/Import/Converter/BudgetName.php index 7ecd85530c..5d36a109ac 100644 --- a/app/Import/Converter/BudgetName.php +++ b/app/Import/Converter/BudgetName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CategoryId.php b/app/Import/Converter/CategoryId.php index 2544a61597..4b5cd4e6af 100644 --- a/app/Import/Converter/CategoryId.php +++ b/app/Import/Converter/CategoryId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CategoryName.php b/app/Import/Converter/CategoryName.php index f8af2414b1..fcd52413cc 100644 --- a/app/Import/Converter/CategoryName.php +++ b/app/Import/Converter/CategoryName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/ConverterInterface.php b/app/Import/Converter/ConverterInterface.php index 0cc0137a05..f5c27a2746 100644 --- a/app/Import/Converter/ConverterInterface.php +++ b/app/Import/Converter/ConverterInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CurrencyCode.php b/app/Import/Converter/CurrencyCode.php index f78433ddf3..1afa778292 100644 --- a/app/Import/Converter/CurrencyCode.php +++ b/app/Import/Converter/CurrencyCode.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CurrencyId.php b/app/Import/Converter/CurrencyId.php index 299cde1bf3..d3b74da000 100644 --- a/app/Import/Converter/CurrencyId.php +++ b/app/Import/Converter/CurrencyId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CurrencyName.php b/app/Import/Converter/CurrencyName.php index 71af377582..f68ec043a1 100644 --- a/app/Import/Converter/CurrencyName.php +++ b/app/Import/Converter/CurrencyName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/CurrencySymbol.php b/app/Import/Converter/CurrencySymbol.php index 27ed50dd48..a40b06af40 100644 --- a/app/Import/Converter/CurrencySymbol.php +++ b/app/Import/Converter/CurrencySymbol.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/Date.php b/app/Import/Converter/Date.php index e4b6cbb0e9..b799aed9e7 100644 --- a/app/Import/Converter/Date.php +++ b/app/Import/Converter/Date.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/Description.php b/app/Import/Converter/Description.php index b9c3bdf807..9eb507acb8 100644 --- a/app/Import/Converter/Description.php +++ b/app/Import/Converter/Description.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/ExternalId.php b/app/Import/Converter/ExternalId.php index 344e793be4..feb2e8c3d1 100644 --- a/app/Import/Converter/ExternalId.php +++ b/app/Import/Converter/ExternalId.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/INGDebetCredit.php b/app/Import/Converter/INGDebetCredit.php index 1640e76d0c..80650257ae 100644 --- a/app/Import/Converter/INGDebetCredit.php +++ b/app/Import/Converter/INGDebetCredit.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/Ignore.php b/app/Import/Converter/Ignore.php index 88f1c2c889..ac619bac3a 100644 --- a/app/Import/Converter/Ignore.php +++ b/app/Import/Converter/Ignore.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/OpposingAccountIban.php b/app/Import/Converter/OpposingAccountIban.php index ee8b40747c..ce01993ea5 100644 --- a/app/Import/Converter/OpposingAccountIban.php +++ b/app/Import/Converter/OpposingAccountIban.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/OpposingAccountName.php b/app/Import/Converter/OpposingAccountName.php index fa51245fd6..90a959408c 100644 --- a/app/Import/Converter/OpposingAccountName.php +++ b/app/Import/Converter/OpposingAccountName.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/OpposingAccountNumber.php b/app/Import/Converter/OpposingAccountNumber.php index d513a88ae0..8ede15ed85 100644 --- a/app/Import/Converter/OpposingAccountNumber.php +++ b/app/Import/Converter/OpposingAccountNumber.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/RabobankDebetCredit.php b/app/Import/Converter/RabobankDebetCredit.php index dfa8363e2a..9b3e89314d 100644 --- a/app/Import/Converter/RabobankDebetCredit.php +++ b/app/Import/Converter/RabobankDebetCredit.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/TagSplit.php b/app/Import/Converter/TagSplit.php index d93074b7d0..f5ebd034af 100644 --- a/app/Import/Converter/TagSplit.php +++ b/app/Import/Converter/TagSplit.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/TagsComma.php b/app/Import/Converter/TagsComma.php index e9fbbaeecd..93e13698f0 100644 --- a/app/Import/Converter/TagsComma.php +++ b/app/Import/Converter/TagsComma.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/Converter/TagsSpace.php b/app/Import/Converter/TagsSpace.php index 3c437bd94b..ae9635a8b7 100644 --- a/app/Import/Converter/TagsSpace.php +++ b/app/Import/Converter/TagsSpace.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Converter; diff --git a/app/Import/ImportEntry.php b/app/Import/ImportEntry.php index fba65e99d4..43ecf7b445 100644 --- a/app/Import/ImportEntry.php +++ b/app/Import/ImportEntry.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import; @@ -215,7 +215,7 @@ class ImportEntry */ private function setAppendableString(string $field, string $value) { - $value = trim($value); + $value = trim($value); $this->fields[$field] .= ' ' . $value; } diff --git a/app/Import/ImportProcedure.php b/app/Import/ImportProcedure.php index 991c5addcd..9150010dae 100644 --- a/app/Import/ImportProcedure.php +++ b/app/Import/ImportProcedure.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import; diff --git a/app/Import/ImportProcedureInterface.php b/app/Import/ImportProcedureInterface.php index 22a519aa95..ca2c6ff5da 100644 --- a/app/Import/ImportProcedureInterface.php +++ b/app/Import/ImportProcedureInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import; diff --git a/app/Import/ImportStorage.php b/app/Import/ImportStorage.php index ec68385e0d..2d5524fcec 100644 --- a/app/Import/ImportStorage.php +++ b/app/Import/ImportStorage.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import; @@ -162,7 +162,7 @@ class ImportStorage /** @var TagRepositoryInterface $repository */ $repository = app(TagRepositoryInterface::class); $repository->setUser($this->user); - $data = [ + $data = [ 'tag' => trans('firefly.import_with_key', ['key' => $this->job->key]), 'date' => new Carbon, 'description' => null, @@ -171,7 +171,7 @@ class ImportStorage 'zoomLevel' => null, 'tagMode' => 'nothing', ]; - $tag = $repository->store($data); + $tag = $repository->store($data); return $tag; } diff --git a/app/Import/ImportValidator.php b/app/Import/ImportValidator.php index 9a5a254151..3a3a373b2b 100644 --- a/app/Import/ImportValidator.php +++ b/app/Import/ImportValidator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import; diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php index 0cc869fcd4..99f8c62a97 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Importer; diff --git a/app/Import/Importer/ImporterInterface.php b/app/Import/Importer/ImporterInterface.php index 9994faa205..06c18bd793 100644 --- a/app/Import/Importer/ImporterInterface.php +++ b/app/Import/Importer/ImporterInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Importer; diff --git a/app/Import/Logging/CommandHandler.php b/app/Import/Logging/CommandHandler.php index 56813cd2ad..b87830469d 100644 --- a/app/Import/Logging/CommandHandler.php +++ b/app/Import/Logging/CommandHandler.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Logging; diff --git a/app/Import/Mapper/AssetAccountIbans.php b/app/Import/Mapper/AssetAccountIbans.php index e5ff5b8016..e94f502cbb 100644 --- a/app/Import/Mapper/AssetAccountIbans.php +++ b/app/Import/Mapper/AssetAccountIbans.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php index dc6032dc7a..8335bea7e4 100644 --- a/app/Import/Mapper/AssetAccounts.php +++ b/app/Import/Mapper/AssetAccounts.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php index 2fddb31e4e..57d60b3013 100644 --- a/app/Import/Mapper/Bills.php +++ b/app/Import/Mapper/Bills.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/Budgets.php b/app/Import/Mapper/Budgets.php index b566568d25..37276ddcc0 100644 --- a/app/Import/Mapper/Budgets.php +++ b/app/Import/Mapper/Budgets.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/Categories.php b/app/Import/Mapper/Categories.php index 6e66964040..5144a06a41 100644 --- a/app/Import/Mapper/Categories.php +++ b/app/Import/Mapper/Categories.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/MapperInterface.php b/app/Import/Mapper/MapperInterface.php index c80b71fa26..237780d502 100644 --- a/app/Import/Mapper/MapperInterface.php +++ b/app/Import/Mapper/MapperInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php index dd2a3261ca..bcf3372e10 100644 --- a/app/Import/Mapper/OpposingAccountIbans.php +++ b/app/Import/Mapper/OpposingAccountIbans.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/OpposingAccounts.php b/app/Import/Mapper/OpposingAccounts.php index 8fbc20768e..fd2d86a5bb 100644 --- a/app/Import/Mapper/OpposingAccounts.php +++ b/app/Import/Mapper/OpposingAccounts.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/Tags.php b/app/Import/Mapper/Tags.php index ac8daa8f77..e549fd90ef 100644 --- a/app/Import/Mapper/Tags.php +++ b/app/Import/Mapper/Tags.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; diff --git a/app/Import/Mapper/TransactionCurrencies.php b/app/Import/Mapper/TransactionCurrencies.php index f5e157c5a3..d24f083b0f 100644 --- a/app/Import/Mapper/TransactionCurrencies.php +++ b/app/Import/Mapper/TransactionCurrencies.php @@ -9,11 +9,11 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Mapper; -use FireflyIII\Models\TransactionCurrency as TC; +use FireflyIII\Models\TransactionCurrency; /** * Class TransactionCurrencies @@ -28,7 +28,7 @@ class TransactionCurrencies implements MapperInterface */ public function getMap(): array { - $currencies = TC::get(); + $currencies = TransactionCurrency::get(); $list = []; foreach ($currencies as $currency) { $list[$currency->id] = $currency->name . ' (' . $currency->code . ')'; diff --git a/app/Import/MapperPreProcess/PreProcessorInterface.php b/app/Import/MapperPreProcess/PreProcessorInterface.php index 9c0c8a213f..7631ea7a79 100644 --- a/app/Import/MapperPreProcess/PreProcessorInterface.php +++ b/app/Import/MapperPreProcess/PreProcessorInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\MapperPreProcess; diff --git a/app/Import/MapperPreProcess/TagsComma.php b/app/Import/MapperPreProcess/TagsComma.php index ec2ed1013d..71ae03a2f4 100644 --- a/app/Import/MapperPreProcess/TagsComma.php +++ b/app/Import/MapperPreProcess/TagsComma.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\MapperPreProcess; diff --git a/app/Import/MapperPreProcess/TagsSpace.php b/app/Import/MapperPreProcess/TagsSpace.php index bd3c18b660..7b588802be 100644 --- a/app/Import/MapperPreProcess/TagsSpace.php +++ b/app/Import/MapperPreProcess/TagsSpace.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\MapperPreProcess; diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php index 3d0fc10ed3..51ccedb338 100644 --- a/app/Import/Setup/CsvSetup.php +++ b/app/Import/Setup/CsvSetup.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Setup; @@ -182,8 +182,8 @@ class CsvSetup implements SetupInterface { /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - $importId = $data['csv_import_account'] ?? 0; - $account = $repository->find(intval($importId)); + $importId = $data['csv_import_account'] ?? 0; + $account = $repository->find(intval($importId)); $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false; $config = $this->job->configuration; diff --git a/app/Import/Setup/SetupInterface.php b/app/Import/Setup/SetupInterface.php index 995292b52f..cffae80eba 100644 --- a/app/Import/Setup/SetupInterface.php +++ b/app/Import/Setup/SetupInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Setup; diff --git a/app/Import/Specifics/AbnAmroDescription.php b/app/Import/Specifics/AbnAmroDescription.php index 4931e6b327..b0eebd0904 100644 --- a/app/Import/Specifics/AbnAmroDescription.php +++ b/app/Import/Specifics/AbnAmroDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Specifics; diff --git a/app/Import/Specifics/IngDescription.php b/app/Import/Specifics/IngDescription.php index aa03ff06d1..c421d45d2d 100644 --- a/app/Import/Specifics/IngDescription.php +++ b/app/Import/Specifics/IngDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Specifics; diff --git a/app/Import/Specifics/PresidentsChoice.php b/app/Import/Specifics/PresidentsChoice.php index c359edacf8..0f462f00d2 100644 --- a/app/Import/Specifics/PresidentsChoice.php +++ b/app/Import/Specifics/PresidentsChoice.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Specifics; diff --git a/app/Import/Specifics/RabobankDescription.php b/app/Import/Specifics/RabobankDescription.php index 59ff2abb53..ec92580f74 100644 --- a/app/Import/Specifics/RabobankDescription.php +++ b/app/Import/Specifics/RabobankDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Specifics; diff --git a/app/Import/Specifics/SpecificInterface.php b/app/Import/Specifics/SpecificInterface.php index 56a11b9f6b..d7f9186d81 100644 --- a/app/Import/Specifics/SpecificInterface.php +++ b/app/Import/Specifics/SpecificInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Import\Specifics; diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php index 0ac899ffcb..417895ab40 100644 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Jobs; diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php index 92fa2f3246..d4688b9ec3 100644 --- a/app/Jobs/Job.php +++ b/app/Jobs/Job.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Jobs; diff --git a/app/Jobs/MailError.php b/app/Jobs/MailError.php index 44e1ec89b5..05de2aca0a 100644 --- a/app/Jobs/MailError.php +++ b/app/Jobs/MailError.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Jobs; diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php new file mode 100644 index 0000000000..f48ddfe6d5 --- /dev/null +++ b/app/Mail/RegisteredUser.php @@ -0,0 +1,37 @@ +address = $address; + $this->ip = $ip; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('emails.registered-html')->text('emails.registered-text')->subject('Welcome to Firefly III!'); + } +} diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php new file mode 100644 index 0000000000..b97a47c50f --- /dev/null +++ b/app/Mail/RequestedNewPassword.php @@ -0,0 +1,38 @@ +url = $url; + $this->ip = $ip; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('emails.password-html')->text('emails.password-text')->subject('Your password reset request'); + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 11b337ba10..cfef7e34af 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; @@ -211,6 +211,26 @@ class Account extends Model return $value; } + /** + * Returns the opening balance + * + * @return TransactionJournal + * @throws FireflyException + */ + public function getOpeningBalance(): TransactionJournal + { + $journal = TransactionJournal::sortCorrectly() + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transactions.account_id', $this->id) + ->transactionTypes([TransactionType::OPENING_BALANCE]) + ->first(['transaction_journals.*']); + if (is_null($journal)) { + return new TransactionJournal; + } + + return $journal; + } + /** * Returns the amount of the opening balance for this account. * diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 48953f09d5..ecad235ce1 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 3efa588ada..6d6a62eddd 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; @@ -48,6 +48,7 @@ class AccountType extends Model protected $dates = ['created_at', 'updated_at']; // + /** * @return HasMany */ diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 36599f3d6b..ddca8ad0d1 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index d51d8b0418..ae3f69ddab 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 54a7429746..4a858720e5 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 504802e05a..d5f71f21ee 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; @@ -44,7 +44,7 @@ class Budget extends Model 'encrypted' => 'boolean', ]; /** @var array */ - protected $dates = ['created_at', 'updated_at', 'deleted_at']; + protected $dates = ['created_at', 'updated_at', 'deleted_at']; protected $fillable = ['user_id', 'name', 'active']; protected $hidden = ['encrypted']; protected $rules = ['name' => 'required|between:1,200',]; diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index a6c2b68f97..edd2c9c776 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Category.php b/app/Models/Category.php index 50349e9b44..fd7241702f 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index b5edf6aced..102e2d28fa 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php new file mode 100644 index 0000000000..2c7d12558c --- /dev/null +++ b/app/Models/CurrencyExchangeRate.php @@ -0,0 +1,53 @@ +belongsTo(TransactionCurrency::class, 'from_currency_id'); + } + + /** + * @return BelongsTo + */ + public function toCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class, 'to_currency_id'); + } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + +} \ No newline at end of file diff --git a/app/Models/ExportJob.php b/app/Models/ExportJob.php index a0eb58cfed..89f2e4554b 100644 --- a/app/Models/ExportJob.php +++ b/app/Models/ExportJob.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index b8a7bd27c3..66dfc167eb 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; @@ -71,8 +71,8 @@ class ImportJob extends Model */ public function addStepsDone(int $count) { - $status = $this->extended_status; - $status['steps_done'] += $count; + $status = $this->extended_status; + $status['steps_done'] += $count; $this->extended_status = $status; $this->save(); @@ -83,7 +83,7 @@ class ImportJob extends Model */ public function addTotalSteps(int $count) { - $status = $this->extended_status; + $status = $this->extended_status; $status['total_steps'] += $count; $this->extended_status = $status; $this->save(); diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php index d4f5b5f3dc..f59d63f48e 100644 --- a/app/Models/LimitRepetition.php +++ b/app/Models/LimitRepetition.php @@ -9,14 +9,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class LimitRepetition @@ -42,26 +41,6 @@ class LimitRepetition extends Model protected $dates = ['created_at', 'updated_at', 'startdate', 'enddate']; protected $hidden = ['amount_encrypted']; - /** - * @param $value - * - * @return mixed - */ - public static function routeBinder($value) - { - if (auth()->check()) { - $object = self::where('limit_repetitions.id', $value) - ->leftJoin('budget_limits', 'budget_limits.id', '=', 'limit_repetitions.budget_limit_id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') - ->where('budgets.user_id', auth()->user()->id) - ->first(['limit_repetitions.*']); - if ($object) { - return $object; - } - } - throw new NotFoundHttpException; - } - /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Models/Note.php b/app/Models/Note.php index 55ee792ce9..96423130a1 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index d5b2b00187..e648623ba4 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -9,13 +9,14 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; use Carbon\Carbon; use Crypt; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Steam; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -67,7 +68,7 @@ class PiggyBank extends Model /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function account() + public function account(): BelongsTo { return $this->belongsTo('FireflyIII\Models\Account'); } @@ -108,6 +109,31 @@ class PiggyBank extends Model return $value; } + /** + * @return string + */ + public function getSuggestedMonthlyAmount(): string + { + $savePerMonth = '0'; + if ($this->targetdate && $this->currentRelevantRep()->currentamount < $this->targetamount) { + $now = Carbon::now(); + $diffInMonths = $now->diffInMonths($this->targetdate, false); + $remainingAmount = bcsub($this->targetamount, $this->currentRelevantRep()->currentamount); + + // more than 1 month to go and still need money to save: + if ($diffInMonths > 0 && bccomp($remainingAmount, '0') === 1) { + $savePerMonth = bcdiv($remainingAmount, strval($diffInMonths)); + } + + // less than 1 month to go but still need money to save: + if ($diffInMonths === 0 && bccomp($remainingAmount, '0') === 1) { + $savePerMonth = $remainingAmount; + } + } + + return $savePerMonth; + } + /** * * @param Carbon $date diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index a84383100d..6e14a9096f 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 00c8221582..56b5b2f12b 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Preference.php b/app/Models/Preference.php index f5a81dd909..7b7922b4e0 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Role.php b/app/Models/Role.php index 5f1a7c33e9..bdb617438f 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -9,7 +9,8 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + namespace FireflyIII\Models; diff --git a/app/Models/Rule.php b/app/Models/Rule.php index ad3db4309c..28637d2893 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index 8817a108dc..5848086595 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 98cbdc0889..8ab7f6a117 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index 048d1c94fc..eea8821f1d 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 769ea2c5af..30018b5e87 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -9,12 +9,12 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; use Crypt; -use FireflyIII\Support\Models\TagSupport; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Watson\Validating\ValidatingTrait; @@ -24,8 +24,10 @@ use Watson\Validating\ValidatingTrait; * * @package FireflyIII\Models */ -class Tag extends TagSupport +class Tag extends Model { + use ValidatingTrait, SoftDeletes; + /** * The attributes that should be casted to native types. * @@ -44,7 +46,6 @@ class Tag extends TagSupport protected $fillable = ['user_id', 'tag', 'date', 'description', 'longitude', 'latitude', 'zoomLevel', 'tagMode']; protected $rules = ['tag' => 'required|between:1,200',]; - use ValidatingTrait, SoftDeletes; /** * @param array $fields @@ -103,7 +104,7 @@ class Tag extends TagSupport $sum = '0'; /** @var TransactionJournal $journal */ foreach ($tag->transactionjournals as $journal) { - bcadd($sum, TransactionJournal::amount($journal)); + bcadd($sum, $journal->amount()); } return $sum; diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 70061c8072..ef5deeadcd 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 0a63b24329..4cbc278fa5 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 1808955271..53506bc61d 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -9,15 +9,16 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; use Carbon\Carbon; use Crypt; use FireflyIII\Support\CacheProperties; -use FireflyIII\Support\Models\TransactionJournalSupport; +use FireflyIII\Support\Models\TransactionJournalTrait; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Log; @@ -30,9 +31,9 @@ use Watson\Validating\ValidatingTrait; * * @package FireflyIII\Models */ -class TransactionJournal extends TransactionJournalSupport +class TransactionJournal extends Model { - use SoftDeletes, ValidatingTrait; + use SoftDeletes, ValidatingTrait, TransactionJournalTrait; /** * The attributes that should be casted to native types. @@ -169,7 +170,7 @@ class TransactionJournal extends TransactionJournalSupport $cache->addProperty($name); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } Log::debug(sprintf('Looking for journal #%d meta field "%s".', $this->id, $name)); diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index a3f6ec92ba..82c4f9214d 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index aceffbd8fb..2a075913ac 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Models; diff --git a/app/Providers/AccountServiceProvider.php b/app/Providers/AccountServiceProvider.php index 3035731adb..a06860a156 100644 --- a/app/Providers/AccountServiceProvider.php +++ b/app/Providers/AccountServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d8dd3843f5..0458f6077b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; @@ -39,7 +39,7 @@ class AppServiceProvider extends ServiceProvider // force https urls if (env('APP_FORCE_SSL', false)) { - URL::forceSchema('https'); + URL::forceScheme('https'); } } diff --git a/app/Providers/AttachmentServiceProvider.php b/app/Providers/AttachmentServiceProvider.php index c9f3a9d48d..5acae3ff5b 100644 --- a/app/Providers/AttachmentServiceProvider.php +++ b/app/Providers/AttachmentServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index c46b897aa5..9e209b3a5c 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/BillServiceProvider.php b/app/Providers/BillServiceProvider.php index a07e8e37ff..2ac479114d 100644 --- a/app/Providers/BillServiceProvider.php +++ b/app/Providers/BillServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index 76858e1e64..daecc1d29f 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/BudgetServiceProvider.php b/app/Providers/BudgetServiceProvider.php index df87739ea6..8a67417d74 100644 --- a/app/Providers/BudgetServiceProvider.php +++ b/app/Providers/BudgetServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/CategoryServiceProvider.php b/app/Providers/CategoryServiceProvider.php index bdac9c91f1..7b997a99c6 100644 --- a/app/Providers/CategoryServiceProvider.php +++ b/app/Providers/CategoryServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/CurrencyServiceProvider.php b/app/Providers/CurrencyServiceProvider.php index bfdf535c70..bdcc925234 100644 --- a/app/Providers/CurrencyServiceProvider.php +++ b/app/Providers/CurrencyServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 215fc5978e..e1e1e4de4e 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; @@ -56,12 +56,6 @@ class EventServiceProvider extends ServiceProvider 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@scanBills', 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules', ], - - // LARAVEL EVENTS: - 'Illuminate\Auth\Events\Logout' => - [ - 'FireflyIII\Handlers\Events\UserEventHandler@logoutUser', - ], ]; /** diff --git a/app/Providers/ExportJobServiceProvider.php b/app/Providers/ExportJobServiceProvider.php index 6850f417b5..b98e433f8e 100644 --- a/app/Providers/ExportJobServiceProvider.php +++ b/app/Providers/ExportJobServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 2ef97e48d2..ef4890bb7f 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; @@ -29,6 +29,8 @@ use FireflyIII\Helpers\Report\BalanceReportHelper; use FireflyIII\Helpers\Report\BalanceReportHelperInterface; use FireflyIII\Helpers\Report\BudgetReportHelper; use FireflyIII\Helpers\Report\BudgetReportHelperInterface; +use FireflyIII\Helpers\Report\PopupReport; +use FireflyIII\Helpers\Report\PopupReportInterface; use FireflyIII\Helpers\Report\ReportHelper; use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Import\ImportProcedure; @@ -41,6 +43,7 @@ use FireflyIII\Support\FireflyConfig; use FireflyIII\Support\Navigation; use FireflyIII\Support\Preferences; use FireflyIII\Support\Steam; +use FireflyIII\Support\Twig\Account; use FireflyIII\Support\Twig\General; use FireflyIII\Support\Twig\Journal; use FireflyIII\Support\Twig\PiggyBank; @@ -52,6 +55,7 @@ use Illuminate\Support\ServiceProvider; use Twig; use TwigBridge\Extension\Loader\Functions; use Validator; +use Illuminate\Foundation\Application; /** * Class FireflyServiceProvider @@ -75,6 +79,7 @@ class FireflyServiceProvider extends ServiceProvider Twig::addExtension(new Translation); Twig::addExtension(new Transaction); Twig::addExtension(new Rule); + Twig::addExtension(new Account); } /** @@ -119,17 +124,30 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind(GeneratorInterface::class, ChartJsGenerator::class); // chart builder - $this->app->bind(MetaPieChartInterface::class, MetaPieChart::class); + $this->app->bind( + MetaPieChartInterface::class, + function (Application $app) { + /** @var MetaPieChart $chart */ + $chart = app(MetaPieChart::class); + if ($app->auth->check()) { + $chart->setUser(auth()->user()); + } + + return $chart; + } + ); // other generators - $this->app->bind(ProcessorInterface::class,Processor::class); - $this->app->bind(ImportProcedureInterface::class,ImportProcedure::class); + $this->app->bind(ProcessorInterface::class, Processor::class); + $this->app->bind(ImportProcedureInterface::class, ImportProcedure::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); $this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class); + // more generators: + $this->app->bind(PopupReportInterface::class, PopupReport::class); $this->app->bind(HelpInterface::class, Help::class); $this->app->bind(ReportHelperInterface::class, ReportHelper::class); - $this->app->bind(FiscalHelperInterface::class,FiscalHelper::class); + $this->app->bind(FiscalHelperInterface::class, FiscalHelper::class); $this->app->bind(BalanceReportHelperInterface::class, BalanceReportHelper::class); $this->app->bind(BudgetReportHelperInterface::class, BudgetReportHelper::class); } diff --git a/app/Providers/FireflySessionProvider.php b/app/Providers/FireflySessionProvider.php index 47dfb1bfe7..f2db3f80cd 100644 --- a/app/Providers/FireflySessionProvider.php +++ b/app/Providers/FireflySessionProvider.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php index b44c11b760..42ff742255 100644 --- a/app/Providers/JournalServiceProvider.php +++ b/app/Providers/JournalServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; @@ -62,7 +62,7 @@ class JournalServiceProvider extends ServiceProvider if ($app->auth->check()) { $collector->setUser(auth()->user()); } - $collector->startQuery(); + return $collector; } diff --git a/app/Providers/LogServiceProvider.php b/app/Providers/LogServiceProvider.php index dc742d6b80..97d4afab5a 100644 --- a/app/Providers/LogServiceProvider.php +++ b/app/Providers/LogServiceProvider.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/PiggyBankServiceProvider.php b/app/Providers/PiggyBankServiceProvider.php index fb1aaf7d5f..a3594b80b7 100644 --- a/app/Providers/PiggyBankServiceProvider.php +++ b/app/Providers/PiggyBankServiceProvider.php @@ -9,12 +9,11 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Repositories\PiggyBank\PiggyBankRepository; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Foundation\Application; @@ -53,6 +52,7 @@ class PiggyBankServiceProvider extends ServiceProvider if ($app->auth->check()) { $repository->setUser(auth()->user()); } + return $repository; } ); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f8f1240557..b704a42cc4 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/RuleGroupServiceProvider.php b/app/Providers/RuleGroupServiceProvider.php index 0a3c8db599..d67e67d458 100644 --- a/app/Providers/RuleGroupServiceProvider.php +++ b/app/Providers/RuleGroupServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/RuleServiceProvider.php b/app/Providers/RuleServiceProvider.php index 5d694411b3..cefdd3b263 100644 --- a/app/Providers/RuleServiceProvider.php +++ b/app/Providers/RuleServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; @@ -51,6 +51,7 @@ class RuleServiceProvider extends ServiceProvider if ($app->auth->check()) { $repository->setUser(auth()->user()); } + return $repository; } ); diff --git a/app/Providers/SearchServiceProvider.php b/app/Providers/SearchServiceProvider.php index 18d5b48c6e..4a3e712cf8 100644 --- a/app/Providers/SearchServiceProvider.php +++ b/app/Providers/SearchServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Providers/TagServiceProvider.php b/app/Providers/TagServiceProvider.php index b5daa36c6c..b0f7d84a82 100644 --- a/app/Providers/TagServiceProvider.php +++ b/app/Providers/TagServiceProvider.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Providers; diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 26ba26b6ea..423efcd23e 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Account; @@ -250,6 +250,19 @@ class AccountRepository implements AccountRepositoryInterface return $result; } + /** + * @return Account + */ + public function getCashAccount(): Account + { + $type = AccountType::where('type', AccountType::CASH)->first(); + $account = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1] + ); + + return $account; + } + /** * Returns the date of the very last transaction in this account. * @@ -455,13 +468,15 @@ class AccountRepository implements AccountRepositoryInterface { $amount = $data['openingBalance']; $name = $data['name']; + $currencyId = $data['currency_id']; $opposing = $this->storeOpposingAccount($name); $transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first(); - $journal = TransactionJournal::create( + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::create( [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $data['openingBalanceCurrency'], + 'transaction_currency_id' => $currencyId, 'description' => 'Initial balance for "' . $account->name . '"', 'completed' => true, 'date' => $data['openingBalanceDate'], @@ -529,12 +544,8 @@ class AccountRepository implements AccountRepositoryInterface } // opening balance data? update it! if (!is_null($openingBalance->id)) { - $date = $data['openingBalanceDate']; - $amount = $data['openingBalance']; - Log::debug('Opening balance journal found, update journal.'); - - $this->updateOpeningBalanceJournal($account, $openingBalance, $date, $amount); + $this->updateOpeningBalanceJournal($account, $openingBalance, $data); return true; } @@ -588,15 +599,19 @@ class AccountRepository implements AccountRepositoryInterface /** * @param Account $account * @param TransactionJournal $journal - * @param Carbon $date - * @param float $amount + * @param array $data * * @return bool */ - protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, Carbon $date, float $amount): bool + protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool { + $date = $data['openingBalanceDate']; + $amount = $data['openingBalance']; + $currencyId = intval($data['currency_id']); + // update date: - $journal->date = $date; + $journal->date = $date; + $journal->transaction_currency_id = $currencyId; $journal->save(); // update transactions: /** @var Transaction $transaction */ @@ -623,9 +638,7 @@ class AccountRepository implements AccountRepositoryInterface */ protected function validOpeningBalanceData(array $data): bool { - if (isset($data['openingBalance']) - && isset($data['openingBalanceDate']) - && isset($data['openingBalanceCurrency']) + if (isset($data['openingBalance']) && isset($data['openingBalanceDate']) && bccomp(strval($data['openingBalance']), '0') !== 0 ) { Log::debug('Array has valid opening balance data.'); diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index b0ab9b738b..e444fcada0 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Account; @@ -36,6 +36,11 @@ interface AccountRepositoryInterface */ public function count(array $types): int; + /** + * @return Account + */ + public function getCashAccount(): Account; + /** * Moved here from account CRUD. * @@ -137,4 +142,12 @@ interface AccountRepositoryInterface */ public function store(array $data): Account; + /** + * @param Account $account + * @param array $data + * + * @return Account + */ + public function update(Account $account, array $data): Account; + } diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php index e5c81eb211..ce2e437880 100644 --- a/app/Repositories/Account/AccountTasker.php +++ b/app/Repositories/Account/AccountTasker.php @@ -9,14 +9,15 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Account; use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionType; use FireflyIII\User; -use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Log; use Steam; @@ -31,64 +32,6 @@ class AccountTasker implements AccountTaskerInterface /** @var User */ private $user; - /** - * @see self::amountInPeriod - * - * @param Collection $accounts - * @param Collection $excluded - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string - { - $idList = [ - 'accounts' => $accounts->pluck('id')->toArray(), - 'exclude' => $excluded->pluck('id')->toArray(), - ]; - - Log::debug( - 'Now calling amountInInPeriod.', - ['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'], - 'start' => $start->format('Y-m-d'), - 'end' => $end->format('Y-m-d'), - ] - ); - - return $this->amountInPeriod($idList, $start, $end, true); - - } - - /** - * @see self::amountInPeriod - * - * @param Collection $accounts - * @param Collection $excluded - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string - { - $idList = [ - 'accounts' => $accounts->pluck('id')->toArray(), - 'exclude' => $excluded->pluck('id')->toArray(), - ]; - - Log::debug( - 'Now calling amountOutInPeriod.', - ['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'], - 'start' => $start->format('Y-m-d'), - 'end' => $end->format('Y-m-d'), - ] - ); - - return $this->amountInPeriod($idList, $start, $end, false); - - } - /** * @param Collection $accounts * @param Carbon $start @@ -126,13 +69,13 @@ class AccountTasker implements AccountTaskerInterface ]; // get first journal date: - $first = $repository->oldestJournal($account); - Log::debug(sprintf('Date of first journal for %s is %s', $account->name, $first->date->format('Y-m-d'))); + $first = $repository->oldestJournal($account); $entry['start_balance'] = $startSet[$account->id] ?? '0'; $entry['end_balance'] = $endSet[$account->id] ?? '0'; // first journal exists, and is on start, then this is the actual opening balance: if (!is_null($first->id) && $first->date->isSameDay($start)) { + Log::debug(sprintf('Date of first journal for %s is %s', $account->name, $first->date->format('Y-m-d'))); $entry['start_balance'] = $first->transactions()->where('account_id', $account->id)->first()->amount; Log::debug(sprintf('Account %s was opened on %s, so opening balance is %f', $account->name, $start->format('Y-m-d'), $entry['start_balance'])); } @@ -147,6 +90,90 @@ class AccountTasker implements AccountTaskerInterface return $return; } + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array + { + // get all expenses for the given accounts in the given period! + // also transfers! + // get all transactions: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); + $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->withOpposingAccount(); + $transactions = $collector->getJournals(); + $transactions = $transactions->filter( + function (Transaction $transaction) { + // return negative amounts only. + if (bccomp($transaction->transaction_amount, '0') === -1) { + return $transaction; + } + + return false; + } + ); + $expenses = $this->groupByOpposing($transactions); + + // sort the result + // Obtain a list of columns + $sum = []; + foreach ($expenses as $accountId => $row) { + $sum[$accountId] = floatval($row['sum']); + } + + array_multisort($sum, SORT_ASC, $expenses); + + return $expenses; + } + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array + { + // get all expenses for the given accounts in the given period! + // also transfers! + // get all transactions: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); + $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->withOpposingAccount(); + $transactions = $collector->getJournals(); + $transactions = $transactions->filter( + function (Transaction $transaction) { + // return positive amounts only. + if (bccomp($transaction->transaction_amount, '0') === 1) { + return $transaction; + } + + return false; + } + ); + $income = $this->groupByOpposing($transactions); + + // sort the result + // Obtain a list of columns + $sum = []; + foreach ($income as $accountId => $row) { + $sum[$accountId] = floatval($row['sum']); + } + + array_multisort($sum, SORT_DESC, $income); + + return $income; + } + /** * @param User $user */ @@ -156,62 +183,37 @@ class AccountTasker implements AccountTaskerInterface } /** - * Will return how much money has been going out (ie. spent) by the given account(s). - * Alternatively, will return how much money has been coming in (ie. earned) by the given accounts. + * @param Collection $transactions * - * Enter $incoming=true for any money coming in (income) - * Enter $incoming=false for any money going out (expenses) - * - * This means any money going out or in. You can also submit accounts to exclude, - * so transfers between accounts are not included. - * - * As a general rule: - * - * - Asset accounts should return both expenses and earnings. But could return 0. - * - Expense accounts (where money is spent) should only return earnings (the account gets money). - * - Revenue accounts (where money comes from) should only return expenses (they spend money). - * - * - * - * @param array $accounts - * @param Carbon $start - * @param Carbon $end - * @param bool $incoming - * - * @return string + * @return array */ - protected function amountInPeriod(array $accounts, Carbon $start, Carbon $end, bool $incoming): string + private function groupByOpposing(Collection $transactions): array { - $joinModifier = $incoming ? '<' : '>'; - $selection = $incoming ? '>' : '<'; - - $query = Transaction::distinct() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin( - 'transactions as other_side', function (JoinClause $join) use ($joinModifier) { - $join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0); - } - ) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('transaction_journals.user_id', $this->user->id) - ->whereNull('transactions.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->whereIn('transactions.account_id', $accounts['accounts']) - ->where('transactions.amount', $selection, 0); - if (count($accounts['exclude']) > 0) { - $query->whereNotIn('other_side.account_id', $accounts['exclude']); + $expenses = []; + // join the result together: + foreach ($transactions as $transaction) { + $opposingId = $transaction->opposing_account_id; + $name = $transaction->opposing_account_name; + if (!isset($expenses[$opposingId])) { + $expenses[$opposingId] = [ + 'id' => $opposingId, + 'name' => $name, + 'sum' => '0', + 'average' => '0', + 'count' => 0, + ]; + } + $expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount); + $expenses[$opposingId]['count']++; + } + // do averages: + foreach ($expenses as $key => $entry) { + if ($expenses[$key]['count'] > 1) { + $expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count'])); + } } - $result = $query->get(['transactions.id', 'transactions.amount']); - $sum = strval($result->sum('amount')); - if (strlen($sum) === 0) { - Log::debug('Sum is empty.'); - $sum = '0'; - } - Log::debug(sprintf('Result is %s', $sum)); - return $sum; + return $expenses; } - } diff --git a/app/Repositories/Account/AccountTaskerInterface.php b/app/Repositories/Account/AccountTaskerInterface.php index 4ea5be74ef..af04994f1c 100644 --- a/app/Repositories/Account/AccountTaskerInterface.php +++ b/app/Repositories/Account/AccountTaskerInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Account; @@ -24,30 +24,6 @@ use Illuminate\Support\Collection; */ interface AccountTaskerInterface { - /** - * @param Collection $accounts - * @param Collection $excluded - * @param Carbon $start - * @param Carbon $end - * - * @see AccountTasker::amountInPeriod() - * - * @return string - */ - public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string; - - /** - * @param Collection $accounts - * @param Collection $excluded - * @param Carbon $start - * @param Carbon $end - * - * @see AccountTasker::amountInPeriod() - * - * @return string - */ - public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string; - /** * @param Collection $accounts * @param Carbon $start @@ -57,6 +33,24 @@ interface AccountTaskerInterface */ public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array; + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array; + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array; + /** * @param User $user */ diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 79a481e4ab..b7c174b103 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Attachment; diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index cc7cb887f8..caeb3af8b8 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Attachment; diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 545a29fb96..43599dc820 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Bill; @@ -251,7 +251,7 @@ class BillRepository implements BillRepositoryInterface $count = strval($journals->count()); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $sum = bcadd($sum, TransactionJournal::amountPositive($journal)); + $sum = bcadd($sum, $journal->amountPositive()); } $avg = '0'; if ($journals->count() > 0) { @@ -370,7 +370,7 @@ class BillRepository implements BillRepositoryInterface $count = strval($journals->count()); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $sum = bcadd($sum, TransactionJournal::amountPositive($journal)); + $sum = bcadd($sum, $journal->amountPositive()); } $avg = '0'; if ($journals->count() > 0) { @@ -396,7 +396,7 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextDateMatch'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; @@ -433,7 +433,7 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextExpectedMatch'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; @@ -478,15 +478,15 @@ class BillRepository implements BillRepositoryInterface if (false === $journal->isWithdrawal()) { return false; } - $destinationAccounts = TransactionJournal::destinationAccountList($journal); - $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $destinationAccounts = $journal->destinationAccountList(); + $sourceAccounts = $journal->sourceAccountList(); $matches = explode(',', $bill->match); $description = strtolower($journal->description) . ' '; - $description .= strtolower(join(' ', $destinationAccounts->pluck('name')->toArray())); - $description .= strtolower(join(' ', $sourceAccounts->pluck('name')->toArray())); + $description .= strtolower(join(' ', $destinationAccounts->pluck('name')->toArray())); + $description .= strtolower(join(' ', $sourceAccounts->pluck('name')->toArray())); $wordMatch = $this->doWordMatch($matches, $description); - $amountMatch = $this->doAmountMatch(TransactionJournal::amountPositive($journal), $bill->amount_min, $bill->amount_max); + $amountMatch = $this->doAmountMatch($journal->amountPositive(), $bill->amount_min, $bill->amount_max); /* @@ -525,8 +525,7 @@ class BillRepository implements BillRepositoryInterface */ public function store(array $data): Bill { - - + /** @var Bill $bill */ $bill = Bill::create( [ 'name' => $data['name'], diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 2e07d12d93..4fc31cf2d6 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Bill; diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 9b4f985390..8754c70ca8 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Budget; diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 906a4827ae..eb522deb77 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Budget; diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index bed2d5847f..bef618f634 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Category; @@ -109,30 +109,28 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function firstUseDate(Category $category): Carbon { - $first = null; + $first = new Carbon; /** @var TransactionJournal $firstJournal */ $firstJournal = $category->transactionJournals()->orderBy('date', 'ASC')->first(['transaction_journals.date']); - if ($firstJournal) { + // if transaction journal exists and date is before $first, then + // new date: + if (!is_null($firstJournal) && $firstJournal->date->lessThanOrEqualTo($first)) { $first = $firstJournal->date; } + // check transactions: $firstTransaction = $category->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->orderBy('transaction_journals.date', 'ASC')->first(['transaction_journals.date']); - // both exist, the one that is earliest "wins". - if (!is_null($firstTransaction) && !is_null($first) && Carbon::parse($firstTransaction->date)->lt($first)) { - $first = $firstTransaction->date; + // transaction exists, and date is before $first, this date becomes first. + if (!is_null($firstTransaction) && Carbon::parse($firstTransaction->date)->lessThanOrEqualTo($first)) { + $first = new Carbon($firstTransaction->date); } - if (is_null($first)) { - return new Carbon('1900-01-01'); - } - - return $first; } @@ -230,8 +228,7 @@ class CategoryRepository implements CategoryRepositoryInterface $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end); $collector->setCategories($categories)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->withOpposingAccount() - ->enableInternalFilter(); + ->withOpposingAccount(); $transactions = $collector->getJournals(); // loop transactions: @@ -262,7 +259,7 @@ class CategoryRepository implements CategoryRepositoryInterface /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount(); - $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])->enableInternalFilter(); + $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]); $collector->withoutCategory(); $transactions = $collector->getJournals(); $result = [ @@ -314,8 +311,7 @@ class CategoryRepository implements CategoryRepositoryInterface $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end); $collector->setCategories($categories)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->withOpposingAccount() - ->enableInternalFilter(); + ->withOpposingAccount(); $transactions = $collector->getJournals(); // loop transactions: @@ -347,7 +343,7 @@ class CategoryRepository implements CategoryRepositoryInterface /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount(); - $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->enableInternalFilter(); + $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]); $collector->withoutCategory(); $transactions = $collector->getJournals(); $result = [ diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 9e89a78f50..c85568f0a6 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Category; diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index bb1444d73c..6e0ae64ae7 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -9,15 +9,18 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Currency; +use Carbon\Carbon; +use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\Preference; use FireflyIII\Models\TransactionCurrency; use FireflyIII\User; use Illuminate\Support\Collection; +use Log; use Preferences; /** @@ -30,14 +33,6 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** @var User */ private $user; - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - /** * @param TransactionCurrency $currency * @@ -121,7 +116,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function findByCode(string $currencyCode): TransactionCurrency { - $currency = TransactionCurrency::whereCode($currencyCode)->first(); + $currency = TransactionCurrency::where('code', $currencyCode)->first(); if (is_null($currency)) { $currency = new TransactionCurrency; } @@ -178,7 +173,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function getCurrencyByPreference(Preference $preference): TransactionCurrency { - $preferred = TransactionCurrency::whereCode($preference->data)->first(); + $preferred = TransactionCurrency::where('code', $preference->data)->first(); if (is_null($preferred)) { $preferred = TransactionCurrency::first(); } @@ -186,6 +181,46 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $preferred; } + /** + * @param TransactionCurrency $fromCurrency + * @param TransactionCurrency $toCurrency + * @param Carbon $date + * + * @return CurrencyExchangeRate + */ + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate + { + if ($fromCurrency->id === $toCurrency->id) { + $rate = new CurrencyExchangeRate; + $rate->rate = 1; + $rate->id = 0; + + return $rate; + } + + $rate = $this->user->currencyExchangeRates() + ->where('from_currency_id', $fromCurrency->id) + ->where('to_currency_id', $toCurrency->id) + ->where('date', $date->format('Y-m-d'))->first(); + if (!is_null($rate)) { + Log::debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); + + return $rate; + } + + return new CurrencyExchangeRate; + + + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + /** * @param array $data * @@ -193,6 +228,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function store(array $data): TransactionCurrency { + /** @var TransactionCurrency $currency */ $currency = TransactionCurrency::create( [ 'name' => $data['name'], diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 63eb9bf0b5..6c2ccdd61b 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -9,11 +9,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Currency; +use Carbon\Carbon; +use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\Preference; use FireflyIII\Models\TransactionCurrency; use FireflyIII\User; @@ -95,6 +97,15 @@ interface CurrencyRepositoryInterface */ public function getCurrencyByPreference(Preference $preference): TransactionCurrency; + /** + * @param TransactionCurrency $fromCurrency + * @param TransactionCurrency $toCurrency + * @param Carbon $date + * + * @return CurrencyExchangeRate + */ + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate; + /** * @param User $user */ diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php index aa956f901e..a0eb52fc86 100644 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ b/app/Repositories/ExportJob/ExportJobRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\ExportJob; diff --git a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php index 7e93663ea5..9f610bc7c2 100644 --- a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php +++ b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\ExportJob; diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 0eec759e5b..6af984a06c 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\ImportJob; @@ -86,6 +86,20 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $result; } + /** + * @param ImportJob $job + * @param array $configuration + * + * @return ImportJob + */ + public function setConfiguration(ImportJob $job, array $configuration): ImportJob + { + $job->configuration = $configuration; + $job->save(); + + return $job; + } + /** * @param User $user */ @@ -93,4 +107,18 @@ class ImportJobRepository implements ImportJobRepositoryInterface { $this->user = $user; } + + /** + * @param ImportJob $job + * @param string $status + * + * @return ImportJob + */ + public function updateStatus(ImportJob $job, string $status): ImportJob + { + $job->status = $status; + $job->save(); + + return $job; + } } diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 5bb2149fbe..5bdf636d42 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\ImportJob; @@ -37,8 +37,24 @@ interface ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob; + /** + * @param ImportJob $job + * @param array $configuration + * + * @return ImportJob + */ + public function setConfiguration(ImportJob $job, array $configuration): ImportJob; + /** * @param User $user */ public function setUser(User $user); + + /** + * @param ImportJob $job + * @param string $status + * + * @return ImportJob + */ + public function updateStatus(ImportJob $job, string $status): ImportJob; } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 12ce898ff1..98ab2dafb5 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Journal; @@ -41,15 +41,10 @@ class JournalRepository implements JournalRepositoryInterface private $user; /** @var array */ - private $validMetaFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes']; - - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } + private $validMetaFields + = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount', + 'foreign_currency_id', + ]; /** * @param TransactionJournal $journal @@ -143,6 +138,38 @@ class JournalRepository implements JournalRepositoryInterface return TransactionType::orderBy('type', 'ASC')->get(); } + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function isTransfer(TransactionJournal $journal): bool + { + return $journal->transactionType->type === TransactionType::TRANSFER; + } + + /** + * @param TransactionJournal $journal + * @param int $order + * + * @return bool + */ + public function setOrder(TransactionJournal $journal, int $order): bool + { + $journal->order = $order; + $journal->save(); + + return true; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + /** * @param array $data * @@ -151,12 +178,17 @@ class JournalRepository implements JournalRepositoryInterface public function store(array $data): TransactionJournal { // find transaction type. + /** @var TransactionType $transactionType */ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); + $accounts = $this->storeAccounts($transactionType, $data); + $data = $this->verifyNativeAmount($data, $accounts); + $currencyId = $data['currency_id']; + $amount = strval($data['amount']); $journal = new TransactionJournal( [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $data['currency_id'], + 'transaction_currency_id' => $currencyId, 'description' => $data['description'], 'completed' => 0, 'date' => $data['date'], @@ -167,13 +199,13 @@ class JournalRepository implements JournalRepositoryInterface // store stuff: $this->storeCategoryWithJournal($journal, $data['category']); $this->storeBudgetWithJournal($journal, $data['budget_id']); - $accounts = $this->storeAccounts($transactionType, $data); + // store two transactions: $one = [ 'journal' => $journal, 'account' => $accounts['source'], - 'amount' => bcmul(strval($data['amount']), '-1'), + 'amount' => bcmul($amount, '-1'), 'description' => null, 'category' => null, 'budget' => null, @@ -184,7 +216,7 @@ class JournalRepository implements JournalRepositoryInterface $two = [ 'journal' => $journal, 'account' => $accounts['destination'], - 'amount' => $data['amount'], + 'amount' => $amount, 'description' => null, 'category' => null, 'budget' => null, @@ -222,10 +254,22 @@ class JournalRepository implements JournalRepositoryInterface */ public function update(TransactionJournal $journal, array $data): TransactionJournal { + // update actual journal: - $journal->transaction_currency_id = $data['currency_id']; - $journal->description = $data['description']; - $journal->date = $data['date']; + $journal->description = $data['description']; + $journal->date = $data['date']; + $accounts = $this->storeAccounts($journal->transactionType, $data); + $amount = strval($data['amount']); + + if ($data['currency_id'] !== $journal->transaction_currency_id) { + // user has entered amount in foreign currency. + // amount in "our" currency is $data['exchanged_amount']: + $amount = strval($data['exchanged_amount']); + // other values must be stored as well: + $data['original_amount'] = $data['amount']; + $data['original_currency_id'] = $data['currency_id']; + + } // unlink all categories, recreate them: $journal->categories()->detach(); @@ -233,12 +277,9 @@ class JournalRepository implements JournalRepositoryInterface $this->storeCategoryWithJournal($journal, $data['category']); $this->storeBudgetWithJournal($journal, $data['budget_id']); - $accounts = $this->storeAccounts($journal->transactionType, $data); - $sourceAmount = bcmul(strval($data['amount']), '-1'); - $this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money. - $amount = strval($data['amount']); + $this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money. $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money. $journal->save(); @@ -731,4 +772,56 @@ class JournalRepository implements JournalRepositoryInterface return true; } + + /** + * This method checks the data array and the given accounts to verify that the native amount, currency + * and possible the foreign currency and amount are properly saved. + * + * @param array $data + * @param array $accounts + * + * @return array + * @throws FireflyException + */ + private function verifyNativeAmount(array $data, array $accounts): array + { + /** @var TransactionType $transactionType */ + $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); + $submittedCurrencyId = $data['currency_id']; + + // which account to check for what the native currency is? + $check = 'source'; + if ($transactionType->type === TransactionType::DEPOSIT) { + $check = 'destination'; + } + switch ($transactionType->type) { + case TransactionType::DEPOSIT: + case TransactionType::WITHDRAWAL: + // continue: + $nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id')); + + // does not match? Then user has submitted amount in a foreign currency: + if ($nativeCurrencyId !== $submittedCurrencyId) { + // store amount and submitted currency in "foreign currency" fields: + $data['foreign_amount'] = $data['amount']; + $data['foreign_currency_id'] = $submittedCurrencyId; + + // overrule the amount and currency ID fields to be the original again: + $data['amount'] = strval($data['native_amount']); + $data['currency_id'] = $nativeCurrencyId; + } + break; + case TransactionType::TRANSFER: + // source gets the original amount. + $data['amount'] = strval($data['source_amount']); + $data['currency_id'] = intval($accounts['source']->getMeta('currency_id')); + $data['foreign_amount'] = strval($data['destination_amount']); + $data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id')); + break; + default: + throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type)); + } + + return $data; + } } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index f1f5438ad4..028f2d52d7 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Journal; @@ -27,6 +27,7 @@ use Illuminate\Support\MessageBag; */ interface JournalRepositoryInterface { + /** * @param TransactionJournal $journal * @param TransactionType $type @@ -67,6 +68,21 @@ interface JournalRepositoryInterface */ public function getTransactionTypes(): Collection; + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function isTransfer(TransactionJournal $journal): bool; + + /** + * @param TransactionJournal $journal + * @param int $order + * + * @return bool + */ + public function setOrder(TransactionJournal $journal, int $order): bool; + /** * @param User $user */ diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index c0424242c8..b56abe0db8 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -9,11 +9,10 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Journal; -use Crypt; use DB; use FireflyIII\Models\AccountType; use FireflyIII\Models\PiggyBankEvent; @@ -23,6 +22,7 @@ use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Steam; /** * Class JournalTasker @@ -113,7 +113,7 @@ class JournalTasker implements JournalTaskerInterface 'source_amount' => $entry->amount, 'description' => $entry->description, 'source_account_id' => $entry->account_id, - 'source_account_name' => intval($entry->account_encrypted) === 1 ? Crypt::decrypt($entry->account_name) : $entry->account_name, + 'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name), 'source_account_type' => $entry->account_type, 'source_account_before' => $sourceBalance, 'source_account_after' => bcadd($sourceBalance, $entry->amount), @@ -121,8 +121,7 @@ class JournalTasker implements JournalTaskerInterface 'destination_amount' => bcmul($entry->amount, '-1'), 'destination_account_id' => $entry->destination_account_id, 'destination_account_type' => $entry->destination_account_type, - 'destination_account_name' => - intval($entry->destination_account_encrypted) === 1 ? Crypt::decrypt($entry->destination_account_name) : $entry->destination_account_name, + 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $entry->destination_account_name), 'destination_account_before' => $destinationBalance, 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')), 'budget_id' => is_null($budget) ? 0 : $budget->id, diff --git a/app/Repositories/Journal/JournalTaskerInterface.php b/app/Repositories/Journal/JournalTaskerInterface.php index 1273f69c3a..bb2aa688c9 100644 --- a/app/Repositories/Journal/JournalTaskerInterface.php +++ b/app/Repositories/Journal/JournalTaskerInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Journal; diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 1f14467a5b..e721c71b63 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\PiggyBank; @@ -18,8 +18,11 @@ use Carbon\Carbon; use FireflyIII\Models\Note; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; +use Log; /** * Class PiggyBankRepository @@ -32,6 +35,69 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** @var User */ private $user; + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function addAmount(PiggyBank $piggyBank, string $amount): bool + { + $repetition = $piggyBank->currentRelevantRep(); + $currentAmount = $repetition->currentamount ?? '0'; + $repetition->currentamount = bcadd($currentAmount, $amount); + $repetition->save(); + + // create event + $this->createEvent($piggyBank, $amount); + + return true; + } + + /** + * @param PiggyBankRepetition $repetition + * @param string $amount + * + * @return string + */ + public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount): string + { + $newAmount = bcadd($repetition->currentamount, $amount); + $repetition->currentamount = $newAmount; + $repetition->save(); + + return $newAmount; + } + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function canAddAmount(PiggyBank $piggyBank, string $amount): bool + { + $leftOnAccount = $piggyBank->leftOnAccount(new Carbon); + $savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount); + $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); + $maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12))); + + return bccomp($amount, $maxAmount) <= 0; + } + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool + { + $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; + + return bccomp($amount, $savedSoFar) <= 0; + } + /** * @param PiggyBank $piggyBank * @param string $amount @@ -40,11 +106,29 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function createEvent(PiggyBank $piggyBank, string $amount): PiggyBankEvent { + /** @var PiggyBankEvent $event */ $event = PiggyBankEvent::create(['date' => Carbon::now(), 'amount' => $amount, 'piggy_bank_id' => $piggyBank->id]); return $event; } + /** + * @param PiggyBank $piggyBank + * @param string $amount + * @param TransactionJournal $journal + * + * @return PiggyBankEvent + */ + public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent + { + /** @var PiggyBankEvent $event */ + $event = PiggyBankEvent::create( + ['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount] + ); + + return $event; + } + /** * @param PiggyBank $piggyBank * @@ -83,6 +167,53 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $piggyBank->piggyBankEvents()->orderBy('date', 'DESC')->orderBy('id', 'DESC')->get(); } + /** + * Used for connecting to a piggy bank. + * + * @param PiggyBank $piggyBank + * @param PiggyBankRepetition $repetition + * @param TransactionJournal $journal + * + * @return string + */ + public function getExactAmount(PiggyBank $piggyBank, PiggyBankRepetition $repetition, TransactionJournal $journal): string + { + $amount = $journal->amountPositive(); + $sources = $journal->sourceAccountList()->pluck('id')->toArray(); + $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); + $compare = bcmul($repetition->currentamount, '-1'); + + Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + + // if piggy account matches source account, the amount is positive + if (in_array($piggyBank->account_id, $sources)) { + $amount = bcmul($amount, '-1'); + Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); + } + + + // if the amount is positive, make sure it fits in piggy bank: + if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) { + // amount is positive and $room is smaller than $amount + Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); + Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $room)); + + return $room; + } + + // amount is negative and $currentamount is smaller than $amount + if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) { + Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); + Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $compare)); + + return $compare; + } + + return $amount; + } + /** * @return int */ @@ -118,6 +249,40 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $set; } + /** + * @param PiggyBank $piggyBank + * @param Carbon $date + * + * @return PiggyBankRepetition + */ + public function getRepetition(PiggyBank $piggyBank, Carbon $date): PiggyBankRepetition + { + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($date)->first(); + if (is_null($repetition)) { + return new PiggyBankRepetition; + } + + return $repetition; + } + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function removeAmount(PiggyBank $piggyBank, string $amount): bool + { + $repetition = $piggyBank->currentRelevantRep(); + $repetition->currentamount = bcsub($repetition->currentamount, $amount); + $repetition->save(); + + // create event + $this->createEvent($piggyBank, bcmul($amount, '-1')); + + return true; + } + /** * Set all piggy banks to order 0. * @@ -173,7 +338,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function store(array $data): PiggyBank { $data['order'] = $this->getMaxOrder() + 1; - $piggyBank = PiggyBank::create($data); + /** @var PiggyBank $piggyBank */ + $piggyBank = PiggyBank::create($data); $this->updateNote($piggyBank, $data['note']); diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index a2da326d86..0592d75e3b 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -9,12 +9,15 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\PiggyBank; +use Carbon\Carbon; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; @@ -25,6 +28,37 @@ use Illuminate\Support\Collection; */ interface PiggyBankRepositoryInterface { + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function addAmount(PiggyBank $piggyBank, string $amount): bool; + + /** + * @param PiggyBankRepetition $repetition + * @param string $amount + * + * @return string + */ + public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount): string; + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function canAddAmount(PiggyBank $piggyBank, string $amount): bool; + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool; /** * Create a new event. @@ -36,6 +70,15 @@ interface PiggyBankRepositoryInterface */ public function createEvent(PiggyBank $piggyBank, string $amount): PiggyBankEvent; + /** + * @param PiggyBank $piggyBank + * @param string $amount + * @param TransactionJournal $journal + * + * @return PiggyBankEvent + */ + public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent; + /** * Destroy piggy bank. * @@ -61,6 +104,17 @@ interface PiggyBankRepositoryInterface */ public function getEvents(PiggyBank $piggyBank): Collection; + /** + * Used for connecting to a piggy bank. + * + * @param PiggyBank $piggyBank + * @param PiggyBankRepetition $repetition + * @param TransactionJournal $journal + * + * @return string + */ + public function getExactAmount(PiggyBank $piggyBank, PiggyBankRepetition $repetition, TransactionJournal $journal): string; + /** * Highest order of all piggy banks. * @@ -82,6 +136,22 @@ interface PiggyBankRepositoryInterface */ public function getPiggyBanksWithAmount(): Collection; + /** + * @param PiggyBank $piggyBank + * @param Carbon $date + * + * @return PiggyBankRepetition + */ + public function getRepetition(PiggyBank $piggyBank, Carbon $date): PiggyBankRepetition; + + /** + * @param PiggyBank $piggyBank + * @param string $amount + * + * @return bool + */ + public function removeAmount(PiggyBank $piggyBank, string $amount): bool; + /** * Set all piggy banks to order 0. * diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index 7e0487be63..47a848427c 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Rule; diff --git a/app/Repositories/Rule/RuleRepositoryInterface.php b/app/Repositories/Rule/RuleRepositoryInterface.php index 6ca1456214..13fc6c3382 100644 --- a/app/Repositories/Rule/RuleRepositoryInterface.php +++ b/app/Repositories/Rule/RuleRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Rule; diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index e413987334..cee3e353ef 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\RuleGroup; @@ -30,14 +30,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface /** @var User */ private $user; - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - /** * @return int */ @@ -100,6 +92,46 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $this->user->ruleGroups()->orderBy('order', 'ASC')->get(); } + /** + * @param User $user + * + * @return Collection + */ + public function getActiveGroups(User $user): Collection + { + return $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']); + } + + /** + * @param RuleGroup $group + * + * @return Collection + */ + public function getActiveStoreRules(RuleGroup $group): Collection + { + return $group->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.*']); + } + + /** + * @param RuleGroup $group + * + * @return Collection + */ + public function getActiveUpdateRules(RuleGroup $group): Collection + { + return $group->rules() + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'update-journal') + ->where('rules.active', 1) + ->get(['rules.*']); + } + /** * @return int */ @@ -228,6 +260,14 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + /** * @param array $data * diff --git a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php index c365fca704..bb8bb8217d 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php +++ b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\RuleGroup; @@ -53,6 +53,27 @@ interface RuleGroupRepositoryInterface */ public function get(): Collection; + /** + * @param User $user + * + * @return Collection + */ + public function getActiveGroups(User $user): Collection; + + /** + * @param RuleGroup $group + * + * @return Collection + */ + public function getActiveStoreRules(RuleGroup $group): Collection; + + /** + * @param RuleGroup $group + * + * @return Collection + */ + public function getActiveUpdateRules(RuleGroup $group): Collection; + /** * @return int */ diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 9f91816252..63ee5b408b 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Tag; @@ -34,14 +34,6 @@ class TagRepository implements TagRepositoryInterface /** @var User */ private $user; - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - /** * * @param TransactionJournal $journal @@ -76,6 +68,14 @@ class TagRepository implements TagRepositoryInterface return false; } + /** + * @return int + */ + public function count(): int + { + return $this->user->tags()->count(); + } + /** * @param Tag $tag * @@ -88,6 +88,25 @@ class TagRepository implements TagRepositoryInterface return true; } + /** + * @param Tag $tag + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriod(Tag $tag, Carbon $start, Carbon $end): string + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag); + $set = $collector->getJournals(); + $sum = strval($set->sum('transaction_amount')); + + return $sum; + } + /** * @param int $tagId * @@ -152,6 +171,16 @@ class TagRepository implements TagRepositoryInterface return $tags; } + /** + * @param string $type + * + * @return Collection + */ + public function getByType(string $type): Collection + { + return $this->user->tags()->where('tagMode', $type)->orderBy('date', 'ASC')->get(); + } + /** * @param Tag $tag * @@ -167,6 +196,33 @@ class TagRepository implements TagRepositoryInterface return new Carbon; } + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + + /** + * @param Tag $tag + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Tag $tag, Carbon $start, Carbon $end): string + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag); + $set = $collector->getJournals(); + $sum = strval($set->sum('transaction_amount')); + + return $sum; + } + /** * @param array $data * @@ -190,6 +246,95 @@ class TagRepository implements TagRepositoryInterface } + /** + * @param Tag $tag + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return string + */ + public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + + if (!is_null($start) && !is_null($end)) { + $collector->setRange($start, $end); + } + + $collector->setAllAssetAccounts()->setTag($tag); + $sum = $collector->getJournals()->sum('transaction_amount'); + + return strval($sum); + } + + /** + * Can a tag become an advance payment? + * + * @param Tag $tag + * + * @return bool + */ + public function tagAllowAdvance(Tag $tag): bool + { + /* + * If this tag is a balancing act, and it contains transfers, it cannot be + * changed to an advancePayment. + */ + + if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') { + foreach ($tag->transactionjournals as $journal) { + if ($journal->isTransfer()) { + return false; + } + } + } + + /* + * If this tag contains more than one expenses, it cannot become an advance payment. + */ + $count = 0; + foreach ($tag->transactionjournals as $journal) { + if ($journal->isWithdrawal()) { + $count++; + } + } + if ($count > 1) { + return false; + } + + return true; + } + + /** + * Can a tag become a balancing act? + * + * @param Tag $tag + * + * @return bool + */ + public function tagAllowBalancing(Tag $tag): bool + { + /* + * If has more than two transactions already, cannot become a balancing act: + */ + if ($tag->transactionjournals->count() > 2) { + return false; + } + + /* + * If any transaction is a deposit, cannot become a balancing act. + */ + foreach ($tag->transactionjournals as $journal) { + if ($journal->isDeposit()) { + return false; + } + } + + return true; + + } + /** * @param Tag $tag * @param array $data @@ -319,8 +464,8 @@ class TagRepository implements TagRepositoryInterface */ private function matchAll(TransactionJournal $journal, Tag $tag): bool { - $journalSources = join(',', array_unique(TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray())); - $journalDestinations = join(',', array_unique(TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray())); + $journalSources = join(',', array_unique($journal->sourceAccountList()->pluck('id')->toArray())); + $journalDestinations = join(',', array_unique($journal->destinationAccountList()->pluck('id')->toArray())); $match = true; $journals = $tag->transactionJournals()->get(['transaction_journals.*']); @@ -331,8 +476,8 @@ class TagRepository implements TagRepositoryInterface Log::debug(sprintf('Now existingcomparing new journal #%d to existing journal #%d', $journal->id, $existing->id)); // $checkAccount is the source_account for a withdrawal // $checkAccount is the destination_account for a deposit - $existingSources = join(',', array_unique(TransactionJournal::sourceAccountList($existing)->pluck('id')->toArray())); - $existingDestinations = join(',', array_unique(TransactionJournal::destinationAccountList($existing)->pluck('id')->toArray())); + $existingSources = join(',', array_unique($existing->sourceAccountList()->pluck('id')->toArray())); + $existingDestinations = join(',', array_unique($existing->destinationAccountList()->pluck('id')->toArray())); if ($existing->isWithdrawal() && $existingSources !== $journalDestinations) { /* @@ -383,42 +528,4 @@ class TagRepository implements TagRepositoryInterface return false; } - - /** - * @param Tag $tag - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function earnedInPeriod(Tag $tag, Carbon $start, Carbon $end): string - { - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag); - $set = $collector->getJournals(); - $sum = strval($set->sum('transaction_amount')); - - return $sum; - } - - /** - * @param Tag $tag - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function spentInPeriod(Tag $tag, Carbon $start, Carbon $end): string - { - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag); - $set = $collector->getJournals(); - $sum = strval($set->sum('transaction_amount')); - - return $sum; - } } diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index b360e7d436..a97ff00c98 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\Tag; @@ -27,6 +27,7 @@ use Illuminate\Support\Collection; */ interface TagRepositoryInterface { + /** * This method will connect a journal with a tag. * @@ -37,6 +38,11 @@ interface TagRepositoryInterface */ public function connect(TransactionJournal $journal, Tag $tag): bool; + /** + * @return int + */ + public function count(): int; + /** * This method destroys a tag. * @@ -83,6 +89,13 @@ interface TagRepositoryInterface */ public function get(): Collection; + /** + * @param string $type + * + * @return Collection + */ + public function getByType(string $type): Collection; + /** * @param Tag $tag * @@ -113,6 +126,29 @@ interface TagRepositoryInterface */ public function store(array $data): Tag; + /** + * @param Tag $tag + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return string + */ + public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string; + + /** + * @param Tag $tag + * + * @return bool + */ + public function tagAllowAdvance(Tag $tag): bool; + + /** + * @param Tag $tag + * + * @return bool + */ + public function tagAllowBalancing(Tag $tag): bool; + /** * Update a tag. * diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index ec2a6c7442..ae31df83c9 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\User; @@ -66,6 +66,23 @@ class UserRepository implements UserRepositoryInterface return true; } + /** + * @param User $user + * @param bool $isBlocked + * @param string $code + * + * @return bool + */ + public function changeStatus(User $user, bool $isBlocked, string $code): bool + { + // change blocked status and code: + $user->blocked = $isBlocked; + $user->blocked_code = $code; + $user->save(); + + return true; + } + /** * @return int */ @@ -147,4 +164,15 @@ class UserRepository implements UserRepositoryInterface return $return; } + + /** + * @param User $user + * @param string $role + * + * @return bool + */ + public function hasRole(User $user, string $role): bool + { + return $user->hasRole($role); + } } diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php index 9e2bb096b3..118ecf4a84 100644 --- a/app/Repositories/User/UserRepositoryInterface.php +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Repositories\User; @@ -50,6 +50,15 @@ interface UserRepositoryInterface */ public function changePassword(User $user, string $password); + /** + * @param User $user + * @param bool $isBlocked + * @param string $code + * + * @return bool + */ + public function changeStatus(User $user, bool $isBlocked, string $code): bool; + /** * Returns a count of all users. * @@ -79,4 +88,12 @@ interface UserRepositoryInterface * @return array */ public function getUserData(User $user): array; + + /** + * @param User $user + * @param string $role + * + * @return bool + */ + public function hasRole(User $user, string $role): bool; } diff --git a/app/Rules/Actions/ActionInterface.php b/app/Rules/Actions/ActionInterface.php index 0c66106d7f..7d8696f047 100644 --- a/app/Rules/Actions/ActionInterface.php +++ b/app/Rules/Actions/ActionInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/AddTag.php b/app/Rules/Actions/AddTag.php index 8dc7cb17a2..38560c83aa 100644 --- a/app/Rules/Actions/AddTag.php +++ b/app/Rules/Actions/AddTag.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/AppendDescription.php b/app/Rules/Actions/AppendDescription.php index 4169399773..0bca1c7397 100644 --- a/app/Rules/Actions/AppendDescription.php +++ b/app/Rules/Actions/AppendDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/ClearBudget.php b/app/Rules/Actions/ClearBudget.php index fc29eb4aae..4f029089ac 100644 --- a/app/Rules/Actions/ClearBudget.php +++ b/app/Rules/Actions/ClearBudget.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/ClearCategory.php b/app/Rules/Actions/ClearCategory.php index d60afcf77a..6eea4ce560 100644 --- a/app/Rules/Actions/ClearCategory.php +++ b/app/Rules/Actions/ClearCategory.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/PrependDescription.php b/app/Rules/Actions/PrependDescription.php index 7e734873ee..a59b5e6172 100644 --- a/app/Rules/Actions/PrependDescription.php +++ b/app/Rules/Actions/PrependDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/RemoveAllTags.php b/app/Rules/Actions/RemoveAllTags.php index 66ddbc4810..32290d7a70 100644 --- a/app/Rules/Actions/RemoveAllTags.php +++ b/app/Rules/Actions/RemoveAllTags.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/RemoveTag.php b/app/Rules/Actions/RemoveTag.php index c002c1ff21..ca9912f5f0 100644 --- a/app/Rules/Actions/RemoveTag.php +++ b/app/Rules/Actions/RemoveTag.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/SetBudget.php b/app/Rules/Actions/SetBudget.php index e9f8c1fae9..62b530b8c8 100644 --- a/app/Rules/Actions/SetBudget.php +++ b/app/Rules/Actions/SetBudget.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; @@ -52,9 +52,9 @@ class SetBudget implements ActionInterface /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $repository->setUser($journal->user); - $search = $this->action->action_value; - $budgets = $repository->getActiveBudgets(); - $budget = $budgets->filter( + $search = $this->action->action_value; + $budgets = $repository->getActiveBudgets(); + $budget = $budgets->filter( function (Budget $current) use ($search) { return $current->name == $search; } diff --git a/app/Rules/Actions/SetCategory.php b/app/Rules/Actions/SetCategory.php index ff6e79fb66..8b1953db5d 100644 --- a/app/Rules/Actions/SetCategory.php +++ b/app/Rules/Actions/SetCategory.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/SetDescription.php b/app/Rules/Actions/SetDescription.php index 8be7339f03..fd882f405d 100644 --- a/app/Rules/Actions/SetDescription.php +++ b/app/Rules/Actions/SetDescription.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; diff --git a/app/Rules/Actions/SetDestinationAccount.php b/app/Rules/Actions/SetDestinationAccount.php index 25677e546b..00249b2660 100644 --- a/app/Rules/Actions/SetDestinationAccount.php +++ b/app/Rules/Actions/SetDestinationAccount.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; @@ -62,7 +62,7 @@ class SetDestinationAccount implements ActionInterface $this->journal = $journal; $this->repository = app(AccountRepositoryInterface::class); $this->repository->setUser($journal->user); - $count = $journal->transactions()->count(); + $count = $journal->transactions()->count(); if ($count > 2) { Log::error(sprintf('Cannot change destination account of journal #%d because it is a split journal.', $journal->id)); diff --git a/app/Rules/Actions/SetSourceAccount.php b/app/Rules/Actions/SetSourceAccount.php index fc8067856c..f651f0fbe1 100644 --- a/app/Rules/Actions/SetSourceAccount.php +++ b/app/Rules/Actions/SetSourceAccount.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Actions; @@ -62,7 +62,7 @@ class SetSourceAccount implements ActionInterface $this->journal = $journal; $this->repository = app(AccountRepositoryInterface::class); $this->repository->setUser($journal->user); - $count = $journal->transactions()->count(); + $count = $journal->transactions()->count(); if ($count > 2) { Log::error(sprintf('Cannot change source account of journal #%d because it is a split journal.', $journal->id)); diff --git a/app/Rules/Factory/ActionFactory.php b/app/Rules/Factory/ActionFactory.php index 9bbdb7468d..16d33605e3 100644 --- a/app/Rules/Factory/ActionFactory.php +++ b/app/Rules/Factory/ActionFactory.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Factory; diff --git a/app/Rules/Factory/TriggerFactory.php b/app/Rules/Factory/TriggerFactory.php index 9f1f76498e..9f98560f0f 100644 --- a/app/Rules/Factory/TriggerFactory.php +++ b/app/Rules/Factory/TriggerFactory.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Factory; @@ -45,8 +45,9 @@ class TriggerFactory $triggerType = $trigger->trigger_type; /** @var AbstractTrigger $class */ - $class = self::getTriggerClass($triggerType); - $obj = $class::makeFromTriggerValue($trigger->trigger_value); + $class = self::getTriggerClass($triggerType); + $obj = $class::makeFromTriggerValue($trigger->trigger_value); + $obj->stopProcessing = $trigger->stop_processing; Log::debug(sprintf('self::getTriggerClass("%s") = "%s"', $triggerType, $class)); Log::debug(sprintf('%s::makeFromTriggerValue(%s) = object of class "%s"', $class, $trigger->trigger_value, get_class($obj))); diff --git a/app/Rules/Processor.php b/app/Rules/Processor.php index 9b0daf0ae9..579521af61 100644 --- a/app/Rules/Processor.php +++ b/app/Rules/Processor.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules; @@ -69,6 +69,7 @@ final class Processor $triggerSet = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); /** @var RuleTrigger $trigger */ foreach ($triggerSet as $trigger) { + Log::debug(sprintf('Push trigger %d', $trigger->id)); $self->triggers->push(TriggerFactory::getTrigger($trigger)); } $self->actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php index 5241e4f84c..090d3553ba 100644 --- a/app/Rules/TransactionMatcher.php +++ b/app/Rules/TransactionMatcher.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules; diff --git a/app/Rules/Triggers/AbstractTrigger.php b/app/Rules/Triggers/AbstractTrigger.php index 0fd9a3bfd8..b008a1b91c 100644 --- a/app/Rules/Triggers/AbstractTrigger.php +++ b/app/Rules/Triggers/AbstractTrigger.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -75,7 +75,6 @@ class AbstractTrigger } - /** * @param RuleTrigger $trigger * @param TransactionJournal $journal diff --git a/app/Rules/Triggers/AmountExactly.php b/app/Rules/Triggers/AmountExactly.php index e7984deb02..d160f8495e 100644 --- a/app/Rules/Triggers/AmountExactly.php +++ b/app/Rules/Triggers/AmountExactly.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -58,7 +58,7 @@ final class AmountExactly extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $amount = $journal->destination_amount ?? $journal->amountPositive(); $compare = $this->triggerValue; $result = bccomp($amount, $compare); if ($result === 0) { diff --git a/app/Rules/Triggers/AmountLess.php b/app/Rules/Triggers/AmountLess.php index 1c856c6796..433ccb2491 100644 --- a/app/Rules/Triggers/AmountLess.php +++ b/app/Rules/Triggers/AmountLess.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -58,7 +58,7 @@ final class AmountLess extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $amount = $journal->destination_amount ?? $journal->amountPositive(); $compare = $this->triggerValue; $result = bccomp($amount, $compare); if ($result === -1) { diff --git a/app/Rules/Triggers/AmountMore.php b/app/Rules/Triggers/AmountMore.php index d8a7ce1643..8a7f1544b6 100644 --- a/app/Rules/Triggers/AmountMore.php +++ b/app/Rules/Triggers/AmountMore.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -64,7 +64,7 @@ final class AmountMore extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); + $amount = $journal->destination_amount ?? $journal->amountPositive(); $compare = $this->triggerValue; $result = bccomp($amount, $compare); if ($result === 1) { diff --git a/app/Rules/Triggers/BudgetIs.php b/app/Rules/Triggers/BudgetIs.php index fa444f3ffc..ecc2d01b92 100644 --- a/app/Rules/Triggers/BudgetIs.php +++ b/app/Rules/Triggers/BudgetIs.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/CategoryIs.php b/app/Rules/Triggers/CategoryIs.php index 28dcddb086..caf39abaaf 100644 --- a/app/Rules/Triggers/CategoryIs.php +++ b/app/Rules/Triggers/CategoryIs.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/DescriptionContains.php b/app/Rules/Triggers/DescriptionContains.php index d805d0ea10..45fbf08205 100644 --- a/app/Rules/Triggers/DescriptionContains.php +++ b/app/Rules/Triggers/DescriptionContains.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/DescriptionEnds.php b/app/Rules/Triggers/DescriptionEnds.php index ee01fb975f..31c8d269ea 100644 --- a/app/Rules/Triggers/DescriptionEnds.php +++ b/app/Rules/Triggers/DescriptionEnds.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/DescriptionIs.php b/app/Rules/Triggers/DescriptionIs.php index 385c17decc..de471e5dc7 100644 --- a/app/Rules/Triggers/DescriptionIs.php +++ b/app/Rules/Triggers/DescriptionIs.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/DescriptionStarts.php b/app/Rules/Triggers/DescriptionStarts.php index 9e412db938..6bb9232fc3 100644 --- a/app/Rules/Triggers/DescriptionStarts.php +++ b/app/Rules/Triggers/DescriptionStarts.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/FromAccountContains.php b/app/Rules/Triggers/FromAccountContains.php index 1b8241ef7b..54fef61b20 100644 --- a/app/Rules/Triggers/FromAccountContains.php +++ b/app/Rules/Triggers/FromAccountContains.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class FromAccountContains extends AbstractTrigger implements TriggerInterf $fromAccountName = ''; /** @var Account $account */ - foreach (TransactionJournal::sourceAccountList($journal) as $account) { + foreach ($journal->sourceAccountList() as $account) { $fromAccountName .= strtolower($account->name); } diff --git a/app/Rules/Triggers/FromAccountEnds.php b/app/Rules/Triggers/FromAccountEnds.php index 54afb67808..6121bb9580 100644 --- a/app/Rules/Triggers/FromAccountEnds.php +++ b/app/Rules/Triggers/FromAccountEnds.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class FromAccountEnds extends AbstractTrigger implements TriggerInterface $name = ''; /** @var Account $account */ - foreach (TransactionJournal::sourceAccountList($journal) as $account) { + foreach ($journal->sourceAccountList() as $account) { $name .= strtolower($account->name); } diff --git a/app/Rules/Triggers/FromAccountIs.php b/app/Rules/Triggers/FromAccountIs.php index c3f494d48c..06339eb331 100644 --- a/app/Rules/Triggers/FromAccountIs.php +++ b/app/Rules/Triggers/FromAccountIs.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -61,7 +61,7 @@ final class FromAccountIs extends AbstractTrigger implements TriggerInterface $name = ''; /** @var Account $account */ - foreach (TransactionJournal::sourceAccountList($journal) as $account) { + foreach ($journal->sourceAccountList() as $account) { $name .= strtolower($account->name); } diff --git a/app/Rules/Triggers/FromAccountStarts.php b/app/Rules/Triggers/FromAccountStarts.php index f32ce2b545..52ab178289 100644 --- a/app/Rules/Triggers/FromAccountStarts.php +++ b/app/Rules/Triggers/FromAccountStarts.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class FromAccountStarts extends AbstractTrigger implements TriggerInterfac $name = ''; /** @var Account $account */ - foreach (TransactionJournal::sourceAccountList($journal) as $account) { + foreach ($journal->sourceAccountList() as $account) { $name .= strtolower($account->name); } diff --git a/app/Rules/Triggers/HasAttachment.php b/app/Rules/Triggers/HasAttachment.php new file mode 100644 index 0000000000..4fc3bd720f --- /dev/null +++ b/app/Rules/Triggers/HasAttachment.php @@ -0,0 +1,61 @@ +triggerValue); + $attachments = $journal->attachments()->count(); + if ($attachments >= $minimum) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/app/Rules/Triggers/TagIs.php b/app/Rules/Triggers/TagIs.php index 684d806760..9ea19ac2ed 100644 --- a/app/Rules/Triggers/TagIs.php +++ b/app/Rules/Triggers/TagIs.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/ToAccountContains.php b/app/Rules/Triggers/ToAccountContains.php index d80c9ab4d5..96eeb960e8 100644 --- a/app/Rules/Triggers/ToAccountContains.php +++ b/app/Rules/Triggers/ToAccountContains.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class ToAccountContains extends AbstractTrigger implements TriggerInterfac $toAccountName = ''; /** @var Account $account */ - foreach (TransactionJournal::destinationAccountList($journal) as $account) { + foreach ($journal->destinationAccountList() as $account) { $toAccountName .= strtolower($account->name); } diff --git a/app/Rules/Triggers/ToAccountEnds.php b/app/Rules/Triggers/ToAccountEnds.php index f4fd676422..ff5f470618 100644 --- a/app/Rules/Triggers/ToAccountEnds.php +++ b/app/Rules/Triggers/ToAccountEnds.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class ToAccountEnds extends AbstractTrigger implements TriggerInterface $toAccountName = ''; /** @var Account $account */ - foreach (TransactionJournal::destinationAccountList($journal) as $account) { + foreach ($journal->destinationAccountList() as $account) { $toAccountName .= strtolower($account->name); } diff --git a/app/Rules/Triggers/ToAccountIs.php b/app/Rules/Triggers/ToAccountIs.php index 0a2fdd8ca9..fffb334153 100644 --- a/app/Rules/Triggers/ToAccountIs.php +++ b/app/Rules/Triggers/ToAccountIs.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class ToAccountIs extends AbstractTrigger implements TriggerInterface $toAccountName = ''; /** @var Account $account */ - foreach (TransactionJournal::destinationAccountList($journal) as $account) { + foreach ($journal->destinationAccountList() as $account) { $toAccountName .= strtolower($account->name); } diff --git a/app/Rules/Triggers/ToAccountStarts.php b/app/Rules/Triggers/ToAccountStarts.php index f5f162c283..05eebbe028 100644 --- a/app/Rules/Triggers/ToAccountStarts.php +++ b/app/Rules/Triggers/ToAccountStarts.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; @@ -66,7 +66,7 @@ final class ToAccountStarts extends AbstractTrigger implements TriggerInterface $toAccountName = ''; /** @var Account $account */ - foreach (TransactionJournal::destinationAccountList($journal) as $account) { + foreach ($journal->destinationAccountList() as $account) { $toAccountName .= strtolower($account->name); } diff --git a/app/Rules/Triggers/TransactionType.php b/app/Rules/Triggers/TransactionType.php index 691a16ff9d..e6f220ffce 100644 --- a/app/Rules/Triggers/TransactionType.php +++ b/app/Rules/Triggers/TransactionType.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/TriggerInterface.php b/app/Rules/Triggers/TriggerInterface.php index df05a331b6..ac58021ddc 100644 --- a/app/Rules/Triggers/TriggerInterface.php +++ b/app/Rules/Triggers/TriggerInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Rules/Triggers/UserAction.php b/app/Rules/Triggers/UserAction.php index 576ddfe6aa..fb4d6508c6 100644 --- a/app/Rules/Triggers/UserAction.php +++ b/app/Rules/Triggers/UserAction.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Rules\Triggers; diff --git a/app/Services/Currency/ExchangeRateInterface.php b/app/Services/Currency/ExchangeRateInterface.php new file mode 100644 index 0000000000..a37133db9a --- /dev/null +++ b/app/Services/Currency/ExchangeRateInterface.php @@ -0,0 +1,38 @@ +format('Y-m-d'), $fromCurrency->code, $toCurrency->code); + $result = Requests::get($uri); + $rate = 1.0; + $content = null; + if ($result->status_code !== 200) { + Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $result->status_code, $result->body)); + } + // get rate from body: + if ($result->status_code === 200) { + $content = json_decode($result->body, true); + } + if (!is_null($content)) { + $code = $toCurrency->code; + $rate = isset($content['rates'][$code]) ? $content['rates'][$code] : '1'; + } + + // create new currency exchange rate object: + $exchangeRate = new CurrencyExchangeRate; + $exchangeRate->user()->associate($this->user); + $exchangeRate->fromCurrency()->associate($fromCurrency); + $exchangeRate->toCurrency()->associate($toCurrency); + $exchangeRate->date = $date; + $exchangeRate->rate = $rate; + $exchangeRate->save(); + + return $exchangeRate; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } +} \ No newline at end of file diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 9434352b80..32e42cf3bb 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -9,15 +9,15 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use Illuminate\Support\Collection; -use Log; use Preferences as Prefs; /** @@ -97,7 +97,6 @@ class Amount // alternative is currency before amount $format = $pos_a . $pos_b . '%s' . $pos_c . $space . $pos_d . '%v' . $pos_e; } - Log::debug(sprintf('Final format: "%s"', $format)); return $format; } @@ -130,7 +129,7 @@ class Amount setlocale(LC_MONETARY, $locale); $float = round($amount, 12); $info = localeconv(); - $formatted = number_format($float, $format->decimal_places, $info['mon_decimal_point'], $info['mon_thousands_sep']); + $formatted = number_format($float, intval($format->decimal_places), $info['mon_decimal_point'], $info['mon_thousands_sep']); // some complicated switches to format the amount correctly: $precedes = $amount < 0 ? $info['n_cs_precedes'] : $info['p_cs_precedes']; @@ -171,7 +170,7 @@ class Amount */ public function formatByCode(string $currencyCode, string $amount, bool $coloured = true): string { - $currency = TransactionCurrency::whereCode($currencyCode)->first(); + $currency = TransactionCurrency::where('code', $currencyCode)->first(); return $this->formatAnything($currency, $amount, $coloured); } @@ -187,7 +186,7 @@ class Amount { $currency = $journal->transactionCurrency; - return $this->formatAnything($currency, TransactionJournal::amount($journal), $coloured); + return $this->formatAnything($currency, $journal->amount(), $coloured); } /** @@ -220,11 +219,11 @@ class Amount $cache = new CacheProperties; $cache->addProperty('getCurrencyCode'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } else { $currencyPreference = Prefs::get('currencyPreference', config('firefly.default_currency', 'EUR')); - $currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); + $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); if ($currency) { $cache->store($currency->code); @@ -245,10 +244,10 @@ class Amount $cache = new CacheProperties; $cache->addProperty('getCurrencySymbol'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } else { $currencyPreference = Prefs::get('currencyPreference', config('firefly.default_currency', 'EUR')); - $currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); + $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); $cache->store($currency->symbol); @@ -257,17 +256,21 @@ class Amount } /** - * @return \FireflyIII\Models\TransactionCurrency + * @return TransactionCurrency + * @throws FireflyException */ public function getDefaultCurrency(): TransactionCurrency { $cache = new CacheProperties; $cache->addProperty('getDefaultCurrency'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $currencyPreference = Prefs::get('currencyPreference', config('firefly.default_currency', 'EUR')); - $currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); + $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); + if (is_null($currency)) { + throw new FireflyException(sprintf('No currency found with code "%s"', $currencyPreference->data)); + } $cache->store($currency); return $currency; diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index da684a089f..8bd5210d17 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/BinderInterface.php b/app/Support/Binder/BinderInterface.php index f75ae11ee6..6d8313686b 100644 --- a/app/Support/Binder/BinderInterface.php +++ b/app/Support/Binder/BinderInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/BudgetList.php b/app/Support/Binder/BudgetList.php index eb617a0ac3..299206e16b 100644 --- a/app/Support/Binder/BudgetList.php +++ b/app/Support/Binder/BudgetList.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/CategoryList.php b/app/Support/Binder/CategoryList.php index 852ac1e396..4bd60aedef 100644 --- a/app/Support/Binder/CategoryList.php +++ b/app/Support/Binder/CategoryList.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/CurrencyCode.php b/app/Support/Binder/CurrencyCode.php new file mode 100644 index 0000000000..4616ce5ef2 --- /dev/null +++ b/app/Support/Binder/CurrencyCode.php @@ -0,0 +1,39 @@ +first(); + if (!is_null($currency)) { + return $currency; + } + throw new NotFoundHttpException; + } +} \ No newline at end of file diff --git a/app/Support/Binder/Date.php b/app/Support/Binder/Date.php index be03cc14d2..73dba325e1 100644 --- a/app/Support/Binder/Date.php +++ b/app/Support/Binder/Date.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php index b36af5149c..cc31681323 100644 --- a/app/Support/Binder/JournalList.php +++ b/app/Support/Binder/JournalList.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; @@ -39,7 +39,13 @@ class JournalList implements BinderInterface $object = TransactionJournal::whereIn('transaction_journals.id', $ids) ->expanded() ->where('transaction_journals.user_id', auth()->user()->id) - ->get(TransactionJournal::queryFields()); + ->get( + [ + 'transaction_journals.*', + 'transaction_types.type AS transaction_type_type', + 'transaction_currencies.code AS transaction_currency_code', + ] + ); if ($object->count() > 0) { return $object; diff --git a/app/Support/Binder/TagList.php b/app/Support/Binder/TagList.php index b1c42bb4c8..3acc8f4195 100644 --- a/app/Support/Binder/TagList.php +++ b/app/Support/Binder/TagList.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/Binder/UnfinishedJournal.php b/app/Support/Binder/UnfinishedJournal.php index 607f442e46..799a152743 100644 --- a/app/Support/Binder/UnfinishedJournal.php +++ b/app/Support/Binder/UnfinishedJournal.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Binder; diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index b8e493d134..555fe427b0 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; diff --git a/app/Support/ChartColour.php b/app/Support/ChartColour.php index c04b75b28e..eeb6ef6bfb 100644 --- a/app/Support/ChartColour.php +++ b/app/Support/ChartColour.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; diff --git a/app/Support/Domain.php b/app/Support/Domain.php index 427bd4628c..8c04fceb9f 100644 --- a/app/Support/Domain.php +++ b/app/Support/Domain.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; diff --git a/app/Support/Events/BillScanner.php b/app/Support/Events/BillScanner.php index 7d1d362783..dc2ed7a7d1 100644 --- a/app/Support/Events/BillScanner.php +++ b/app/Support/Events/BillScanner.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Events; diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 85b85fd5d2..dab2ed286d 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; @@ -40,6 +40,8 @@ class ExpandedForm */ public function amount(string $name, $value = null, array $options = []): string { + $options['min'] = '0.01'; + return $this->currencyField($name, 'amount', $value, $options); } @@ -52,6 +54,8 @@ class ExpandedForm */ public function amountSmall(string $name, $value = null, array $options = []): string { + $options['min'] = '0.01'; + return $this->currencyField($name, 'amount-small', $value, $options); } @@ -261,6 +265,67 @@ class ExpandedForm return $html; } + /** + * @param string $name + * @param null $value + * @param array $options + * + * @return string + */ + public function nonSelectableAmount(string $name, $value = null, array $options = []): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $options['step'] = 'any'; + $options['min'] = '0.01'; + $selectedCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); + unset($options['currency']); + unset($options['placeholder']); + + // make sure value is formatted nicely: + if (!is_null($value) && $value !== '') { + $value = round($value, $selectedCurrency->decimal_places); + } + + + $html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render(); + + return $html; + } + + + /** + * @param string $name + * @param null $value + * @param array $options + * + * @return string + */ + public function nonSelectableBalance(string $name, $value = null, array $options = []): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $options['step'] = 'any'; + $selectedCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); + unset($options['currency']); + unset($options['placeholder']); + + // make sure value is formatted nicely: + if (!is_null($value) && $value !== '') { + $decimals = $selectedCurrency->decimal_places ?? 2; + $value = round($value, $decimals); + } + + + $html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render(); + + return $html; + } + /** * @param $type * @param $name diff --git a/app/Support/Facades/Amount.php b/app/Support/Facades/Amount.php index b7a4d53304..4a3df176a6 100644 --- a/app/Support/Facades/Amount.php +++ b/app/Support/Facades/Amount.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/Facades/ExpandedForm.php b/app/Support/Facades/ExpandedForm.php index fe6afa5af9..59f2e973ef 100644 --- a/app/Support/Facades/ExpandedForm.php +++ b/app/Support/Facades/ExpandedForm.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/Facades/FireflyConfig.php b/app/Support/Facades/FireflyConfig.php index 8a1a5ff617..4fcc685c95 100644 --- a/app/Support/Facades/FireflyConfig.php +++ b/app/Support/Facades/FireflyConfig.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/Facades/Navigation.php b/app/Support/Facades/Navigation.php index b59fce49fb..10ed3eb3b4 100644 --- a/app/Support/Facades/Navigation.php +++ b/app/Support/Facades/Navigation.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/Facades/Preferences.php b/app/Support/Facades/Preferences.php index d0155f6375..59b83dec80 100644 --- a/app/Support/Facades/Preferences.php +++ b/app/Support/Facades/Preferences.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/Facades/Steam.php b/app/Support/Facades/Steam.php index da00e57317..c8b78622c5 100644 --- a/app/Support/Facades/Steam.php +++ b/app/Support/Facades/Steam.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Facades; diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 743dc40c2a..68f2323396 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; diff --git a/app/Support/Models/TagSupport.php b/app/Support/Models/TagSupport.php deleted file mode 100644 index f053ef42cf..0000000000 --- a/app/Support/Models/TagSupport.php +++ /dev/null @@ -1,93 +0,0 @@ -tagMode == 'balancingAct' || $tag->tagMode == 'nothing') { - foreach ($tag->transactionjournals as $journal) { - if ($journal->isTransfer()) { - return false; - } - } - } - - /* - * If this tag contains more than one expenses, it cannot become an advance payment. - */ - $count = 0; - foreach ($tag->transactionjournals as $journal) { - if ($journal->isWithdrawal()) { - $count++; - } - } - if ($count > 1) { - return false; - } - - return true; - } - - /** - * Can a tag become a balancing act? - * - * @param Tag $tag - * - * @return bool - */ - public static function tagAllowBalancing(Tag $tag): bool - { - /* - * If has more than two transactions already, cannot become a balancing act: - */ - if ($tag->transactionjournals->count() > 2) { - return false; - } - - /* - * If any transaction is a deposit, cannot become a balancing act. - */ - foreach ($tag->transactionjournals as $journal) { - if ($journal->isDeposit()) { - return false; - } - } - - return true; - - } - -} diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php deleted file mode 100644 index 513fec2623..0000000000 --- a/app/Support/Models/TransactionJournalSupport.php +++ /dev/null @@ -1,309 +0,0 @@ -addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('amount'); - if ($cache->has()) { - return $cache->get(); - } - - // saves on queries: - $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); - - if ($journal->isWithdrawal()) { - $amount = $amount * -1; - } - $amount = strval($amount); - $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(); - } - - // saves on queries: - $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); - - $amount = strval($amount); - $cache->store($amount); - - return $amount; - } - - /** - * @param TransactionJournal $journal - * - * @return int - */ - public static function budgetId(TransactionJournal $journal): int - { - $budget = $journal->budgets()->first(); - if (!is_null($budget)) { - return $budget->id; - } - - return 0; - } - - /** - * @param TransactionJournal $journal - * - * @return string - */ - public static function categoryAsString(TransactionJournal $journal): string - { - $category = $journal->categories()->first(); - if (!is_null($category)) { - return $category->name; - } - - return ''; - } - - /** - * @param TransactionJournal $journal - * @param string $dateField - * - * @return string - */ - public static function dateAsString(TransactionJournal $journal, string $dateField = ''): string - { - if ($dateField === '') { - return $journal->date->format('Y-m-d'); - } - if (!is_null($journal->$dateField) && $journal->$dateField instanceof Carbon) { - // make field NULL - $carbon = clone $journal->$dateField; - $journal->$dateField = null; - $journal->save(); - - // create meta entry - $journal->setMeta($dateField, $carbon); - - // return that one instead. - return $carbon->format('Y-m-d'); - } - $metaField = $journal->getMeta($dateField); - if (!is_null($metaField)) { - $carbon = new Carbon($metaField); - - return $carbon->format('Y-m-d'); - } - - return ''; - - - } - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public static function destinationAccountList(TransactionJournal $journal): Collection - { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('destination-account-list'); - if ($cache->has()) { - return $cache->get(); - } - $transactions = $journal->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get(); - $list = new Collection; - /** @var Transaction $t */ - foreach ($transactions as $t) { - $list->push($t->account); - } - $cache->store($list); - - return $list; - } - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public static function destinationTransactionList(TransactionJournal $journal): Collection - { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('destination-transaction-list'); - if ($cache->has()) { - return $cache->get(); - } - $list = $journal->transactions()->where('amount', '>', 0)->with('account')->get(); - $cache->store($list); - - return $list; - } - - /** - * @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 int - */ - public static function piggyBankId(TransactionJournal $journal): int - { - if ($journal->piggyBankEvents()->count() > 0) { - return $journal->piggyBankEvents()->orderBy('date', 'DESC')->first()->piggy_bank_id; - } - - return 0; - } - - /** - * @return array - */ - public static function queryFields(): array - { - return [ - 'transaction_journals.*', - 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent. - 'transaction_currencies.code AS transaction_currency_code', - ]; - } - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public static function sourceAccountList(TransactionJournal $journal): Collection - { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('source-account-list'); - if ($cache->has()) { - return $cache->get(); - } - $transactions = $journal->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get(); - $list = new Collection; - /** @var Transaction $t */ - foreach ($transactions as $t) { - $list->push($t->account); - } - $cache->store($list); - - return $list; - } - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public static function sourceTransactionList(TransactionJournal $journal): Collection - { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('source-transaction-list'); - if ($cache->has()) { - return $cache->get(); - } - $list = $journal->transactions()->where('amount', '<', 0)->with('account')->get(); - $cache->store($list); - - return $list; - } - - /** - * @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(); - } - - $typeStr = $journal->transaction_type_type ?? $journal->transactionType->type; - $cache->store($typeStr); - - return $typeStr; - } -} diff --git a/app/Support/Models/TransactionJournalTrait.php b/app/Support/Models/TransactionJournalTrait.php new file mode 100644 index 0000000000..c9cea3ec49 --- /dev/null +++ b/app/Support/Models/TransactionJournalTrait.php @@ -0,0 +1,276 @@ +addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('amount'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + // saves on queries: + $amount = $this->transactions()->where('amount', '>', 0)->get()->sum('amount'); + + if ($this->isWithdrawal()) { + $amount = $amount * -1; + } + $amount = strval($amount); + $cache->store($amount); + + return $amount; + } + + /** + * @return string + */ + public function amountPositive(): string + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('amount-positive'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + // saves on queries: + $amount = $this->transactions()->where('amount', '>', 0)->get()->sum('amount'); + + $amount = strval($amount); + $cache->store($amount); + + return $amount; + } + + /** + * @return int + */ + public function budgetId(): int + { + $budget = $this->budgets()->first(); + if (!is_null($budget)) { + return $budget->id; + } + + return 0; + } + + /** + * @return string + */ + public function categoryAsString(): string + { + $category = $this->categories()->first(); + if (!is_null($category)) { + return $category->name; + } + + return ''; + } + + /** + * @param string $dateField + * + * @return string + */ + public function dateAsString(string $dateField = ''): string + { + if ($dateField === '') { + return $this->date->format('Y-m-d'); + } + if (!is_null($this->$dateField) && $this->$dateField instanceof Carbon) { + // make field NULL + $carbon = clone $this->$dateField; + $this->$dateField = null; + $this->save(); + + // create meta entry + $this->setMeta($dateField, $carbon); + + // return that one instead. + return $carbon->format('Y-m-d'); + } + $metaField = $this->getMeta($dateField); + if (!is_null($metaField)) { + $carbon = new Carbon($metaField); + + return $carbon->format('Y-m-d'); + } + + return ''; + + + } + + /** + * @return Collection + */ + public function destinationAccountList(): Collection + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-account-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transactions = $this->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $list = $list->unique('id'); + $cache->store($list); + + return $list; + } + + /** + * @return Collection + */ + public function destinationTransactionList(): Collection + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-transaction-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $list = $this->transactions()->where('amount', '>', 0)->with('account')->get(); + $cache->store($list); + + return $list; + } + + /** + * @param Builder $query + * @param string $table + * + * @return bool + */ + public 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; + } + + /** + * @return int + */ + public function piggyBankId(): int + { + if ($this->piggyBankEvents()->count() > 0) { + return $this->piggyBankEvents()->orderBy('date', 'DESC')->first()->piggy_bank_id; + } + + return 0; + } + + /** + * @return Collection + */ + public function sourceAccountList(): Collection + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-account-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transactions = $this->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $list = $list->unique('id'); + $cache->store($list); + + return $list; + } + + /** + * @return Collection + */ + public function sourceTransactionList(): Collection + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-transaction-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $list = $this->transactions()->where('amount', '<', 0)->with('account')->get(); + $cache->store($list); + + return $list; + } + + /** + * @return string + */ + public function transactionTypeStr(): string + { + $cache = new CacheProperties; + $cache->addProperty($this->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('type-string'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $typeStr = $this->transaction_type_type ?? $this->transactionType->type; + $cache->store($typeStr); + + return $typeStr; + } +} diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index cbdeafbabc..dc28a613db 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; @@ -72,7 +72,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon + public function endOfPeriod(\Carbon\Carbon $end, string $repeatFreq): Carbon { $currentEnd = clone $end; diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 999b8847ef..743a6444e1 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; @@ -118,9 +118,13 @@ class Preferences */ public function lastActivity(): string { - $preference = $this->get('lastActivity', microtime())->data; + $lastActivity = microtime(); + $preference = $this->get('lastActivity', microtime()); + if (!is_null($preference)) { + $lastActivity = $preference->data; + } - return md5($preference); + return md5($lastActivity); } /** diff --git a/app/Support/Search/Modifier.php b/app/Support/Search/Modifier.php new file mode 100644 index 0000000000..18e94d3aff --- /dev/null +++ b/app/Support/Search/Modifier.php @@ -0,0 +1,211 @@ +transaction_amount); + + $compare = bccomp($amount, $transactionAmount); + Log::debug(sprintf('%s vs %s is %d', $amount, $transactionAmount, $compare)); + + return $compare === $expected; + } + + public static function apply(array $modifier, Transaction $transaction): bool + { + switch ($modifier['type']) { + default: + throw new FireflyException(sprintf('Search modifier "%s" is not (yet) supported. Sorry!', $modifier['type'])); + case 'amount': + case 'amount_is': + $res = self::amountCompare($transaction, $modifier['value'], 0); + Log::debug(sprintf('Amount is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'amount_min': + case 'amount_less': + $res = self::amountCompare($transaction, $modifier['value'], 1); + Log::debug(sprintf('Amount less than %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'amount_max': + case 'amount_more': + $res = self::amountCompare($transaction, $modifier['value'], -1); + Log::debug(sprintf('Amount more than %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'source': + $res = self::stringCompare($transaction->account_name, $modifier['value']); + Log::debug(sprintf('Source is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'destination': + $res = self::stringCompare($transaction->opposing_account_name, $modifier['value']); + Log::debug(sprintf('Destination is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'category': + $res = self::category($transaction, $modifier['value']); + Log::debug(sprintf('Category is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'budget': + $res = self::budget($transaction, $modifier['value']); + Log::debug(sprintf('Budget is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'bill': + $res = self::stringCompare(strval($transaction->bill_name), $modifier['value']); + Log::debug(sprintf('Bill is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'type': + $res = self::stringCompare($transaction->transaction_type_type, $modifier['value']); + Log::debug(sprintf('Transaction type is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'date': + case 'on': + $res = self::sameDate($transaction->date, $modifier['value']); + Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'date_before': + case 'before': + $res = self::dateBefore($transaction->date, $modifier['value']); + Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true))); + break; + case 'date_after': + case 'after': + $res = self::dateAfter($transaction->date, $modifier['value']); + Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true))); + break; + } + + return $res; + } + + /** + * @param Carbon $date + * @param string $compare + * + * @return bool + */ + public static function dateAfter(Carbon $date, string $compare): bool + { + try { + $compareDate = new Carbon($compare); + } catch (Exception $e) { + return false; + } + + return $date->greaterThanOrEqualTo($compareDate); + } + + /** + * @param Carbon $date + * @param string $compare + * + * @return bool + */ + public static function dateBefore(Carbon $date, string $compare): bool + { + try { + $compareDate = new Carbon($compare); + } catch (Exception $e) { + return false; + } + + return $date->lessThanOrEqualTo($compareDate); + } + + /** + * @param Carbon $date + * @param string $compare + * + * @return bool + */ + public static function sameDate(Carbon $date, string $compare): bool + { + try { + $compareDate = new Carbon($compare); + } catch (Exception $e) { + return false; + } + + return $compareDate->isSameDay($date); + } + + /** + * @param string $haystack + * @param string $needle + * + * @return bool + */ + public static function stringCompare(string $haystack, string $needle): bool + { + $res = !(strpos(strtolower($haystack), strtolower($needle)) === false); + Log::debug(sprintf('"%s" is in "%s"? %s', $needle, $haystack, var_export($res, true))); + + return $res; + + } + + /** + * @param Transaction $transaction + * @param string $search + * + * @return bool + */ + private static function budget(Transaction $transaction, string $search): bool + { + $journalBudget = ''; + if (!is_null($transaction->transaction_journal_budget_name)) { + $journalBudget = Steam::decrypt(intval($transaction->transaction_journal_budget_encrypted), $transaction->transaction_journal_budget_name); + } + $transactionBudget = ''; + if (!is_null($transaction->transaction_budget_name)) { + $journalBudget = Steam::decrypt(intval($transaction->transaction_budget_encrypted), $transaction->transaction_budget_name); + } + + return self::stringCompare($journalBudget, $search) || self::stringCompare($transactionBudget, $search); + } + + /** + * @param Transaction $transaction + * @param string $search + * + * @return bool + */ + private static function category(Transaction $transaction, string $search): bool + { + $journalCategory = ''; + if (!is_null($transaction->transaction_journal_category_name)) { + $journalCategory = Steam::decrypt(intval($transaction->transaction_journal_category_encrypted), $transaction->transaction_journal_category_name); + } + $transactionCategory = ''; + if (!is_null($transaction->transaction_category_name)) { + $journalCategory = Steam::decrypt(intval($transaction->transaction_category_encrypted), $transaction->transaction_category_name); + } + + return self::stringCompare($journalCategory, $search) || self::stringCompare($transactionCategory, $search); + } +} \ No newline at end of file diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index b37af85daa..4b9ffacb31 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -9,12 +9,14 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Search; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; @@ -34,19 +36,74 @@ class Search implements SearchInterface { /** @var int */ private $limit = 100; + /** @var Collection */ + private $modifiers; + /** @var string */ + private $originalQuery = ''; /** @var User */ private $user; + /** @var array */ + private $validModifiers = []; + /** @var array */ + private $words = []; + + /** + * Search constructor. + */ + public function __construct() + { + $this->modifiers = new Collection; + $this->validModifiers = config('firefly.search_modifiers'); + } + + /** + * @return string + */ + public function getWordsAsString(): string + { + $string = join(' ', $this->words); + if (strlen($string) === 0) { + return is_string($this->originalQuery) ? $this->originalQuery : ''; + } + + return $string; + } + + /** + * @return bool + */ + public function hasModifiers(): bool + { + return $this->modifiers->count() > 0; + } + + /** + * @param string $query + */ + public function parseQuery(string $query) + { + $filteredQuery = $query; + $this->originalQuery = $query; + $pattern = '/[a-z_]*:[0-9a-z-.]*/i'; + $matches = []; + preg_match_all($pattern, $query, $matches); + + foreach ($matches[0] as $match) { + $this->extractModifier($match); + $filteredQuery = str_replace($match, '', $filteredQuery); + } + $filteredQuery = trim(str_replace(['"', "'"], '', $filteredQuery)); + if (strlen($filteredQuery) > 0) { + $this->words = array_map('trim', explode(' ', $filteredQuery)); + } + } /** - * The search will assume that the user does not have so many accounts - * that this search should be paginated. - * - * @param array $words - * * @return Collection */ - public function searchAccounts(array $words): Collection + public function searchAccounts(): Collection { + $words = $this->words; $accounts = $this->user->accounts() ->accountTypeIn([AccountType::DEFAULT, AccountType::ASSET, AccountType::EXPENSE, AccountType::REVENUE, AccountType::BENEFICIARY]) ->get(['accounts.*']); @@ -67,14 +124,13 @@ class Search implements SearchInterface } /** - * @param array $words - * * @return Collection */ - public function searchBudgets(array $words): Collection + public function searchBudgets(): Collection { /** @var Collection $set */ - $set = auth()->user()->budgets()->get(); + $set = auth()->user()->budgets()->get(); + $words = $this->words; /** @var Collection $result */ $result = $set->filter( function (Budget $budget) use ($words) { @@ -92,14 +148,11 @@ class Search implements SearchInterface } /** - * Search assumes the user does not have that many categories. So no paginated search. - * - * @param array $words - * * @return Collection */ - public function searchCategories(array $words): Collection + public function searchCategories(): Collection { + $words = $this->words; $categories = $this->user->categories()->get(); /** @var Collection $result */ $result = $categories->filter( @@ -117,15 +170,12 @@ class Search implements SearchInterface } /** - * - * @param array $words - * * @return Collection */ - public function searchTags(array $words): Collection + public function searchTags(): Collection { - $tags = $this->user->tags()->get(); - + $words = $this->words; + $tags = $this->user->tags()->get(); /** @var Collection $result */ $result = $tags->filter( function (Tag $tag) use ($words) { @@ -142,11 +192,9 @@ class Search implements SearchInterface } /** - * @param array $words - * * @return Collection */ - public function searchTransactions(array $words): Collection + public function searchTransactions(): Collection { $pageSize = 100; $processed = 0; @@ -157,19 +205,20 @@ class Search implements SearchInterface $collector = app(JournalCollectorInterface::class); $collector->setUser($this->user); $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page); - $set = $collector->getPaginatedJournals(); + if ($this->hasModifiers()) { + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + } + $collector->removeFilter(InternalTransferFilter::class); + $set = $collector->getPaginatedJournals()->getCollection(); + $words = $this->words; + Log::debug(sprintf('Found %d journals to check. ', $set->count())); // Filter transactions that match the given triggers. $filtered = $set->filter( function (Transaction $transaction) use ($words) { - // check descr of journal: - if ($this->strpos_arr(strtolower(strval($transaction->description)), $words)) { - return $transaction; - } - // check descr of transaction - if ($this->strpos_arr(strtolower(strval($transaction->transaction_description)), $words)) { + if ($this->matchModifiers($transaction)) { return $transaction; } @@ -221,6 +270,54 @@ class Search implements SearchInterface $this->user = $user; } + /** + * @param string $string + */ + private function extractModifier(string $string) + { + $parts = explode(':', $string); + if (count($parts) === 2 && strlen(trim(strval($parts[0]))) > 0 && strlen(trim(strval($parts[1])))) { + $type = trim(strval($parts[0])); + $value = trim(strval($parts[1])); + if (in_array($type, $this->validModifiers)) { + // filter for valid type + $this->modifiers->push(['type' => $type, 'value' => $value,]); + } + } + } + + /** + * @param Transaction $transaction + * + * @return bool + * @throws FireflyException + */ + private function matchModifiers(Transaction $transaction): bool + { + Log::debug(sprintf('Now at transaction #%d', $transaction->id)); + // first "modifier" is always the text of the search: + // check descr of journal: + if (count($this->words) > 0 + && !$this->strpos_arr(strtolower(strval($transaction->description)), $this->words) + && !$this->strpos_arr(strtolower(strval($transaction->transaction_description)), $this->words) + ) { + Log::debug('Description does not match', $this->words); + + return false; + } + + // then a for-each and a switch for every possible other thingie. + foreach ($this->modifiers as $modifier) { + $res = Modifier::apply($modifier, $transaction); + if ($res === false) { + return $res; + } + } + + return true; + + } + /** * @param string $haystack * @param array $needle diff --git a/app/Support/Search/SearchInterface.php b/app/Support/Search/SearchInterface.php index ed9c3c6c88..a06ea95d27 100644 --- a/app/Support/Search/SearchInterface.php +++ b/app/Support/Search/SearchInterface.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Search; @@ -24,43 +24,52 @@ use Illuminate\Support\Collection; interface SearchInterface { /** - * @param array $words - * + * @return string + */ + public function getWordsAsString(): string; + + /** + * @return bool + */ + public function hasModifiers(): bool; + + /** + * @param string $query + */ + public function parseQuery(string $query); + + /** * @return Collection */ - public function searchAccounts(array $words): Collection; + public function searchAccounts(): Collection; + + /** + * @return Collection + */ + public function searchBudgets(): Collection; + + /** + * @return Collection + */ + public function searchCategories(): Collection; + + /** + * @return Collection + */ + public function searchTags(): Collection; + + /** + * @return Collection + */ + public function searchTransactions(): Collection; + + /** + * @param int $limit + */ + public function setLimit(int $limit); /** * @param User $user */ public function setUser(User $user); - - /** - * @param array $words - * - * @return Collection - */ - public function searchBudgets(array $words): Collection; - - /** - * @param array $words - * - * @return Collection - */ - public function searchCategories(array $words): Collection; - - /** - * - * @param array $words - * - * @return Collection - */ - public function searchTags(array $words): Collection; - - /** - * @param array $words - * - * @return Collection - */ - public function searchTransactions(array $words): Collection; } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 870b55566c..3b5a27ad0d 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -9,11 +9,12 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support; use Carbon\Carbon; +use Crypt; use DB; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; @@ -42,7 +43,7 @@ class Steam $cache->addProperty('balance'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $balance = strval( @@ -73,7 +74,7 @@ class Steam $cache->addProperty('balance-no-virtual'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $balance = strval( @@ -107,7 +108,7 @@ class Steam $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $balances = []; @@ -157,7 +158,7 @@ class Steam $cache->addProperty('balances'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $balances = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') @@ -180,6 +181,21 @@ class Steam return $result; } + /** + * @param int $isEncrypted + * @param $value + * + * @return string + */ + public function decrypt(int $isEncrypted, string $value) + { + if ($isEncrypted === 1) { + return Crypt::decrypt($value); + } + + return $value; + } + /** * @param array $accounts * diff --git a/app/Support/Twig/Account.php b/app/Support/Twig/Account.php new file mode 100644 index 0000000000..d2a5520505 --- /dev/null +++ b/app/Support/Twig/Account.php @@ -0,0 +1,63 @@ +formatAmountByAccount(), + ]; + + } + + /** + * Will return "active" when a part of the route matches the argument. + * ie. "accounts" will match "accounts.index". + * + * @return Twig_SimpleFunction + */ + protected function formatAmountByAccount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string { + $currencyId = intval($account->getMeta('currency_id')); + if ($currencyId === 0) { + // Format using default currency: + return AmountFacade::format($amount, $coloured); + } + $currency = TransactionCurrency::find($currencyId); + + return AmountFacade::formatAnything($currency, $amount, $coloured); + }, ['is_safe' => ['html']] + ); + } + + +} \ No newline at end of file diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 5d339960fa..5db0ba95f6 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; @@ -310,7 +310,7 @@ class General extends Twig_Extension { return new Twig_SimpleFunction( 'getAmount', function (TransactionJournal $journal): string { - return TransactionJournal::amount($journal); + return $journal->amount(); } ); } diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 757264a240..d3c0f314e5 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; @@ -44,10 +44,10 @@ class Journal extends Twig_Extension $cache->addProperty('transaction-journal'); $cache->addProperty('destination-account-string'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - $list = TransactionJournal::destinationAccountList($journal); + $list = $journal->destinationAccountList(); $array = []; /** @var Account $entry */ foreach ($list as $entry) { @@ -116,10 +116,10 @@ class Journal extends Twig_Extension $cache->addProperty('transaction-journal'); $cache->addProperty('source-account-string'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } - $list = TransactionJournal::sourceAccountList($journal); + $list = $journal->sourceAccountList(); $array = []; /** @var Account $entry */ foreach ($list as $entry) { @@ -152,7 +152,7 @@ class Journal extends Twig_Extension $cache->addProperty('transaction-journal'); $cache->addProperty('budget-string'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } @@ -189,7 +189,7 @@ class Journal extends Twig_Extension $cache->addProperty('transaction-journal'); $cache->addProperty('category-string'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $categories = []; // get all categories for the journal itself (easy): diff --git a/app/Support/Twig/PiggyBank.php b/app/Support/Twig/PiggyBank.php index 8a11fb546e..01ab696171 100644 --- a/app/Support/Twig/PiggyBank.php +++ b/app/Support/Twig/PiggyBank.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; @@ -39,6 +39,12 @@ class PiggyBank extends Twig_Extension } ); + $functions[] = new Twig_SimpleFunction( + 'suggestedMonthlyAmount', function (PB $piggyBank) { + return $piggyBank->getSuggestedMonthlyAmount(); + } + ); + return $functions; } diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php index 630cddc408..2503cec452 100644 --- a/app/Support/Twig/Rule.php +++ b/app/Support/Twig/Rule.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php index 5285a8ee90..d4b8e84850 100644 --- a/app/Support/Twig/Transaction.php +++ b/app/Support/Twig/Transaction.php @@ -9,16 +9,16 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; use Amount; -use Crypt; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; +use Steam; use Twig_Extension; use Twig_SimpleFilter; use Twig_SimpleFunction; @@ -195,7 +195,7 @@ class Transaction extends Twig_Extension return new Twig_SimpleFunction( 'transactionDestinationAccount', function (TransactionModel $transaction): string { - $name = intval($transaction->account_encrypted) === 1 ? Crypt::decrypt($transaction->account_name) : $transaction->account_name; + $name = Steam::decrypt(intval($transaction->account_encrypted), $transaction->account_name); $id = intval($transaction->account_id); $type = $transaction->account_type; @@ -204,7 +204,7 @@ class Transaction extends Twig_Extension $name = $transaction->opposing_account_name; $id = intval($transaction->opposing_account_id); - $type = intval($transaction->opposing_account_type); + $type = $transaction->opposing_account_type; } // Find the opposing account and use that one: @@ -217,7 +217,7 @@ class Transaction extends Twig_Extension ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - $name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name; + $name = Steam::decrypt(intval($other->encrypted), $other->name); $id = $other->account_id; $type = $other->type; } @@ -269,7 +269,7 @@ class Transaction extends Twig_Extension 'transactionSourceAccount', function (TransactionModel $transaction): string { // if the amount is negative, assume that the current account (the one in $transaction) is indeed the source account. - $name = intval($transaction->account_encrypted) === 1 ? Crypt::decrypt($transaction->account_name) : $transaction->account_name; + $name = Steam::decrypt(intval($transaction->account_encrypted), $transaction->account_name); $id = intval($transaction->account_id); $type = $transaction->account_type; @@ -278,7 +278,7 @@ class Transaction extends Twig_Extension $name = $transaction->opposing_account_name; $id = intval($transaction->opposing_account_id); - $type = intval($transaction->opposing_account_type); + $type = $transaction->opposing_account_type; } // Find the opposing account and use that one: if (bccomp($transaction->transaction_amount, '0') === 1 && is_null($transaction->opposing_account_id)) { @@ -289,7 +289,7 @@ class Transaction extends Twig_Extension ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - $name = intval($other->encrypted) === 1 ? Crypt::decrypt($other->name) : $other->name; + $name = Steam::decrypt(intval($other->encrypted), $other->name); $id = $other->account_id; $type = $other->type; } @@ -337,21 +337,6 @@ class Transaction extends Twig_Extension ); } - /** - * @param int $isEncrypted - * @param string $value - * - * @return string - */ - private function encrypted(int $isEncrypted, string $value): string - { - if ($isEncrypted === 1) { - return Crypt::decrypt($value); - } - - return $value; - } - /** * @param TransactionModel $transaction * @@ -361,14 +346,14 @@ class Transaction extends Twig_Extension { // journal has a budget: if (isset($transaction->transaction_journal_budget_id)) { - $name = $this->encrypted(intval($transaction->transaction_journal_budget_encrypted), $transaction->transaction_journal_budget_name); + $name = Steam::decrypt(intval($transaction->transaction_journal_budget_encrypted), $transaction->transaction_journal_budget_name); return sprintf('%s', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name); } // transaction has a budget if (isset($transaction->transaction_budget_id)) { - $name = $this->encrypted(intval($transaction->transaction_budget_encrypted), $transaction->transaction_budget_name); + $name = Steam::decrypt(intval($transaction->transaction_budget_encrypted), $transaction->transaction_budget_name); return sprintf('%s', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name); } @@ -400,14 +385,14 @@ class Transaction extends Twig_Extension { // journal has a category: if (isset($transaction->transaction_journal_category_id)) { - $name = $this->encrypted(intval($transaction->transaction_journal_category_encrypted), $transaction->transaction_journal_category_name); + $name = Steam::decrypt(intval($transaction->transaction_journal_category_encrypted), $transaction->transaction_journal_category_name); return sprintf('%s', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name); } // transaction has a category: if (isset($transaction->transaction_category_id)) { - $name = $this->encrypted(intval($transaction->transaction_category_encrypted), $transaction->transaction_category_name); + $name = Steam::decrypt(intval($transaction->transaction_category_encrypted), $transaction->transaction_category_name); return sprintf('%s', route('categories.show', [$transaction->transaction_category_id]), $name, $name); } diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php index cb84f85c8e..a01b616bd6 100644 --- a/app/Support/Twig/Translation.php +++ b/app/Support/Twig/Translation.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Support\Twig; diff --git a/app/User.php b/app/User.php index 06a150ef49..f5ee28f920 100644 --- a/app/User.php +++ b/app/User.php @@ -9,12 +9,13 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Models\CurrencyExchangeRate; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; @@ -119,6 +120,14 @@ class User extends Authenticatable return $this->hasMany('FireflyIII\Models\Category'); } + /** + * @return HasMany + */ + public function currencyExchangeRates(): HasMany + { + return $this->hasMany(CurrencyExchangeRate::class); + } + /** * @return HasMany */ diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index f38a164993..cf4b60c3f9 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace FireflyIII\Validation; @@ -29,7 +29,6 @@ use Google2FA; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Translation\Translator; use Illuminate\Validation\Validator; -use Session; /** * Class FireflyValidator @@ -66,7 +65,7 @@ class FireflyValidator extends Validator return false; } - $secret = Session::get('two-factor-secret'); + $secret = session('two-factor-secret'); return Google2FA::verifyKey($secret, $value); } diff --git a/composer.json b/composer.json index aa09eb9b44..0286006772 100755 --- a/composer.json +++ b/composer.json @@ -48,6 +48,10 @@ "require": { "php": ">=7.0.0", "ext-intl": "*", + "ext-bcmath": "*", + "ext-mbstring": "*", + "ext-curl": "*", + "ext-zip": "*", "laravel/framework": "5.4.*", "davejamesmiller/laravel-breadcrumbs": "3.*", "watson/validating": "3.*", @@ -68,7 +72,8 @@ "symfony/css-selector": "3.1.*", "symfony/dom-crawler": "3.1.*", "barryvdh/laravel-debugbar": "2.*", - "barryvdh/laravel-ide-helper": "2.*" + "barryvdh/laravel-ide-helper": "2.*", + "satooshi/php-coveralls": "^1.0" }, "autoload": { "classmap": [ @@ -88,7 +93,7 @@ "php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ - "php artisan key:generate" + "php artisan key:generate --force" ], "post-install-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", @@ -101,6 +106,9 @@ "php artisan firefly:verify", "php artisan firefly:instructions update", "php artisan optimize" + ], + "compile": [ + "php artisan migrate:refresh --seed --force" ] }, "config": { diff --git a/composer.lock b/composer.lock index d7fca941ad..5363e7571a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "254ffef07304b93679b48182a179123f", + "content-hash": "3570b1bf4768d7a7db0667317e82c668", "packages": [ { "name": "bacon/bacon-qr-code", @@ -153,16 +153,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.3.1", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558" + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/bd4461328621bde0ae6b1b2675fbc6aca4ceb558", - "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", "shasum": "" }, "require": { @@ -171,7 +171,7 @@ }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.6.1" + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { @@ -217,7 +217,7 @@ "docblock", "parser" ], - "time": "2016-12-30T15:59:45+00:00" + "time": "2017-02-24T16:22:25+00:00" }, { "name": "doctrine/cache", @@ -623,16 +623,16 @@ }, { "name": "erusev/parsedown", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb" + "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01", + "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01", "shasum": "" }, "require": { @@ -661,20 +661,20 @@ "markdown", "parser" ], - "time": "2016-11-02T15:56:58+00:00" + "time": "2017-03-29T16:04:15+00:00" }, { "name": "laravel/framework", - "version": "v5.4.12", + "version": "v5.4.21", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "707f32d32dce58232f7a860e0a1d62caf6f9dbfc" + "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/707f32d32dce58232f7a860e0a1d62caf6f9dbfc", - "reference": "707f32d32dce58232f7a860e0a1d62caf6f9dbfc", + "url": "https://api.github.com/repos/laravel/framework/zipball/2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee", + "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee", "shasum": "" }, "require": { @@ -790,7 +790,7 @@ "framework", "laravel" ], - "time": "2017-02-15T14:31:32+00:00" + "time": "2017-04-28T15:40:01+00:00" }, { "name": "laravelcollective/html", @@ -917,16 +917,16 @@ }, { "name": "league/csv", - "version": "8.2.0", + "version": "8.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "ef7eef710810c8bd0cf9371582ccd0123ff96d4b" + "reference": "43fd8b022815a0758d85e925dd92a43fe0d41bb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/ef7eef710810c8bd0cf9371582ccd0123ff96d4b", - "reference": "ef7eef710810c8bd0cf9371582ccd0123ff96d4b", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/43fd8b022815a0758d85e925dd92a43fe0d41bb4", + "reference": "43fd8b022815a0758d85e925dd92a43fe0d41bb4", "shasum": "" }, "require": { @@ -970,20 +970,20 @@ "read", "write" ], - "time": "2017-01-25T13:32:07+00:00" + "time": "2017-02-23T08:25:03+00:00" }, { "name": "league/flysystem", - "version": "1.0.35", + "version": "1.0.40", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "dda7f3ab94158a002d9846a97dc18ebfb7acc062" + "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/dda7f3ab94158a002d9846a97dc18ebfb7acc062", - "reference": "dda7f3ab94158a002d9846a97dc18ebfb7acc062", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61", + "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61", "shasum": "" }, "require": { @@ -1005,12 +1005,12 @@ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", "league/flysystem-copy": "Allows you to use Copy.com storage", - "league/flysystem-dropbox": "Allows you to use Dropbox storage", "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage" }, "type": "library", "extra": { @@ -1053,20 +1053,20 @@ "sftp", "storage" ], - "time": "2017-02-09T11:33:58+00:00" + "time": "2017-04-28T10:15:08+00:00" }, { "name": "monolog/monolog", - "version": "1.22.0", + "version": "1.22.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", - "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", "shasum": "" }, "require": { @@ -1131,7 +1131,7 @@ "logging", "psr-3" ], - "time": "2016-11-26T00:15:39+00:00" + "time": "2017-03-13T07:08:03+00:00" }, { "name": "mtdowling/cron-expression", @@ -1232,16 +1232,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.4", + "version": "v2.0.10", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e" + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", - "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", "shasum": "" }, "require": { @@ -1276,7 +1276,7 @@ "pseudorandom", "random" ], - "time": "2016-11-07T23:38:38+00:00" + "time": "2017-03-13T16:27:32+00:00" }, { "name": "pragmarx/google2fa", @@ -1388,30 +1388,30 @@ }, { "name": "ramsey/uuid", - "version": "3.5.2", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "5677cfe02397dd6b58c861870dfaa5d9007d3954" + "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/5677cfe02397dd6b58c861870dfaa5d9007d3954", - "reference": "5677cfe02397dd6b58c861870dfaa5d9007d3954", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", + "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", "shasum": "" }, "require": { "paragonie/random_compat": "^1.0|^2.0", - "php": ">=5.4" + "php": "^5.4 || ^7.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { "apigen/apigen": "^4.1", - "codeception/aspect-mock": "1.0.0", + "codeception/aspect-mock": "^1.0 | ^2.0", "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", "ircmaxell/random-lib": "^1.1", "jakub-onderka/php-parallel-lint": "^0.9.0", "mockery/mockery": "^0.9.4", @@ -1466,7 +1466,7 @@ "identifier", "uuid" ], - "time": "2016-11-22T19:21:44+00:00" + "time": "2017-03-26T20:37:53+00:00" }, { "name": "rcrowe/twigbridge", @@ -1583,16 +1583,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.6", + "version": "v5.4.7", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e" + "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e", - "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4", + "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4", "shasum": "" }, "require": { @@ -1633,20 +1633,20 @@ "mail", "mailer" ], - "time": "2017-02-13T07:52:53+00:00" + "time": "2017-04-20T17:32:18+00:00" }, { "name": "symfony/console", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7a8405a9fc175f87fed8a3c40856b0d866d61936" + "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7a8405a9fc175f87fed8a3c40856b0d866d61936", - "reference": "7a8405a9fc175f87fed8a3c40856b0d866d61936", + "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", + "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", "shasum": "" }, "require": { @@ -1696,7 +1696,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-02-06T12:04:21+00:00" + "time": "2017-04-26T01:39:17+00:00" }, { "name": "symfony/css-selector", @@ -1753,16 +1753,16 @@ }, { "name": "symfony/debug", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "b4d9818f127c60ce21ed62c395da7df868dc8477" + "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/b4d9818f127c60ce21ed62c395da7df868dc8477", - "reference": "b4d9818f127c60ce21ed62c395da7df868dc8477", + "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686", + "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686", "shasum": "" }, "require": { @@ -1806,31 +1806,31 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-01-28T02:37:08+00:00" + "time": "2017-04-19T20:17:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.2.3", + "version": "v2.8.20", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6" + "reference": "7fc8e2b4118ff316550596357325dfd92a51f531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9137eb3a3328e413212826d63eeeb0217836e2b6", - "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fc8e2b4118ff316550596357325dfd92a51f531", + "reference": "7fc8e2b4118ff316550596357325dfd92a51f531", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": ">=5.3.9" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" }, "suggest": { "symfony/dependency-injection": "", @@ -1839,7 +1839,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -1866,20 +1866,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:32:22+00:00" + "time": "2017-04-26T16:56:54+00:00" }, { "name": "symfony/finder", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8c71141cae8e2957946b403cc71a67213c0380d6" + "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8c71141cae8e2957946b403cc71a67213c0380d6", - "reference": "8c71141cae8e2957946b403cc71a67213c0380d6", + "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", + "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", "shasum": "" }, "require": { @@ -1915,20 +1915,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:32:22+00:00" + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0" + "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", - "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9de6add7f731e5af7f5b2e9c0da365e43383ebef", + "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef", "shasum": "" }, "require": { @@ -1968,20 +1968,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-02-02T13:47:35+00:00" + "time": "2017-05-01T14:55:58+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "96443239baf674b143604fb87cb27cb01672ab77" + "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96443239baf674b143604fb87cb27cb01672ab77", - "reference": "96443239baf674b143604fb87cb27cb01672ab77", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/46e8b209abab55c072c47d72d5cd1d62c0585e05", + "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05", "shasum": "" }, "require": { @@ -2050,7 +2050,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-02-06T13:15:19+00:00" + "time": "2017-05-01T17:46:48+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2221,16 +2221,16 @@ }, { "name": "symfony/process", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32646a7cf53f3956c76dcb5c82555224ae321858" + "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32646a7cf53f3956c76dcb5c82555224ae321858", - "reference": "32646a7cf53f3956c76dcb5c82555224ae321858", + "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", + "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", "shasum": "" }, "require": { @@ -2266,20 +2266,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-02-03T12:11:38+00:00" + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/routing", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "af464432c177dbcdbb32295113b7627500331f2d" + "reference": "5029745d6d463585e8b487dbc83d6333f408853a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/af464432c177dbcdbb32295113b7627500331f2d", - "reference": "af464432c177dbcdbb32295113b7627500331f2d", + "url": "https://api.github.com/repos/symfony/routing/zipball/5029745d6d463585e8b487dbc83d6333f408853a", + "reference": "5029745d6d463585e8b487dbc83d6333f408853a", "shasum": "" }, "require": { @@ -2341,20 +2341,20 @@ "uri", "url" ], - "time": "2017-01-28T02:37:08+00:00" + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/translation", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "ca032cc56976d88b85e7386b17020bc6dc95dbc5" + "reference": "f4a04d2df710f81515df576b2de06bdeee518b83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/ca032cc56976d88b85e7386b17020bc6dc95dbc5", - "reference": "ca032cc56976d88b85e7386b17020bc6dc95dbc5", + "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83", + "reference": "f4a04d2df710f81515df576b2de06bdeee518b83", "shasum": "" }, "require": { @@ -2367,7 +2367,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.8|~3.0", - "symfony/intl": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", "symfony/yaml": "~2.8|~3.0" }, "suggest": { @@ -2405,30 +2405,35 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:06:35+00:00" + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5bb4435a03a4f05c211f4a9a8ee2756965924511" + "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5bb4435a03a4f05c211f4a9a8ee2756965924511", - "reference": "5bb4435a03a4f05c211f4a9a8ee2756965924511", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8", + "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8", "shasum": "" }, "require": { "php": ">=5.5.9", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, "require-dev": { + "ext-iconv": "*", "twig/twig": "~1.20|~2.0" }, "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", "ext-symfony_debug": "" }, "type": "library", @@ -2468,7 +2473,7 @@ "debug", "dump" ], - "time": "2017-01-24T12:58:58+00:00" + "time": "2017-05-01T14:55:58+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -2736,16 +2741,16 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.3.0", + "version": "v2.3.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "555d3e37009bdb78f5d8bcea6eb8a816529a5cfa" + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/555d3e37009bdb78f5d8bcea6eb8a816529a5cfa", - "reference": "555d3e37009bdb78f5d8bcea6eb8a816529a5cfa", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", "shasum": "" }, "require": { @@ -2768,7 +2773,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -2798,7 +2803,7 @@ "phpstorm", "sublime" ], - "time": "2017-02-13T19:20:12+00:00" + "time": "2017-02-22T12:27:33+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -2951,6 +2956,102 @@ ], "time": "2016-04-29T12:21:54+00:00" }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2015-03-18T18:23:50+00:00" + }, { "name": "hamcrest/hamcrest-php", "version": "v1.2.2", @@ -3059,16 +3160,16 @@ }, { "name": "mockery/mockery", - "version": "0.9.8", + "version": "0.9.9", "source": { "type": "git", - "url": "https://github.com/padraic/mockery.git", - "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855" + "url": "https://github.com/mockery/mockery.git", + "reference": "6fdb61243844dc924071d3404bb23994ea0b6856" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/padraic/mockery/zipball/1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", - "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856", + "reference": "6fdb61243844dc924071d3404bb23994ea0b6856", "shasum": "" }, "require": { @@ -3120,20 +3221,20 @@ "test double", "testing" ], - "time": "2017-02-09T13:29:38+00:00" + "time": "2017-02-28T12:52:32+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", "shasum": "" }, "require": { @@ -3162,7 +3263,7 @@ "object", "object graph" ], - "time": "2017-01-26T22:05:40+00:00" + "time": "2017-04-12T18:52:22+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3312,27 +3413,27 @@ }, { "name": "phpspec/prophecy", - "version": "v1.6.2", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0|^2.0" + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.0", + "phpspec/phpspec": "^2.5|^3.2", "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", @@ -3371,39 +3472,39 @@ "spy", "stub" ], - "time": "2016-11-21T14:58:47+00:00" + "time": "2017-03-02T20:05:34+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.5", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c19cfc7cbb0e9338d8c469c7eedecc2a428b0971" + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c19cfc7cbb0e9338d8c469c7eedecc2a428b0971", - "reference": "c19cfc7cbb0e9338d8c469c7eedecc2a428b0971", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "~1.0|~2.0" + "sebastian/version": "^1.0 || ^2.0" }, "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" }, "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.4.0", - "ext-xmlwriter": "*" + "ext-xdebug": "^2.5.1" }, "type": "library", "extra": { @@ -3434,7 +3535,7 @@ "testing", "xunit" ], - "time": "2017-01-20T15:06:43+00:00" + "time": "2017-04-02T07:44:40+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3526,25 +3627,30 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4|~5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -3566,20 +3672,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12T18:03:57+00:00" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.9", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", "shasum": "" }, "require": { @@ -3615,20 +3721,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15T14:06:22+00:00" + "time": "2017-02-27T10:12:30+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.13", + "version": "5.7.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34" + "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", - "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1", + "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1", "shasum": "" }, "require": { @@ -3652,7 +3758,7 @@ "sebastian/global-state": "^1.1", "sebastian/object-enumerator": "~2.0", "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", + "sebastian/version": "~1.0.3|~2.0", "symfony/yaml": "~2.1|~3.0" }, "conflict": { @@ -3697,7 +3803,7 @@ "testing", "xunit" ], - "time": "2017-02-10T09:05:10+00:00" + "time": "2017-04-03T02:22:27+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -3759,24 +3865,82 @@ "time": "2016-12-08T20:27:08+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", + "name": "satooshi/php-coveralls", + "version": "v1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + "url": "https://github.com/satooshi/php-coveralls.git", + "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/da51d304fe8622bf9a6da39a8446e7afd432115c", + "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c", "shasum": "" }, "require": { - "php": ">=5.6" + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8|^3.0", + "php": ">=5.3.3", + "psr/log": "^1.0", + "symfony/config": "^2.1|^3.0", + "symfony/console": "^2.1|^3.0", + "symfony/stopwatch": "^2.0|^3.0", + "symfony/yaml": "^2.0|^3.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/satooshi/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2016-01-20T17:35:46+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^5.7 || ^6.0" }, "type": "library", "extra": { @@ -3801,7 +3965,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13T06:45:14+00:00" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", @@ -4089,16 +4253,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", "shasum": "" }, "require": { @@ -4131,7 +4295,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-11-19T07:35:10+00:00" + "time": "2017-02-18T15:18:39+00:00" }, { "name": "sebastian/recursion-context", @@ -4273,16 +4437,16 @@ }, { "name": "symfony/class-loader", - "version": "v3.2.3", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "2847d56f518ad5721bf85aa9174b3aa3fd12aa03" + "reference": "fc4c04bfd17130a9dccfded9578353f311967da7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/2847d56f518ad5721bf85aa9174b3aa3fd12aa03", - "reference": "2847d56f518ad5721bf85aa9174b3aa3fd12aa03", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/fc4c04bfd17130a9dccfded9578353f311967da7", + "reference": "fc4c04bfd17130a9dccfded9578353f311967da7", "shasum": "" }, "require": { @@ -4325,7 +4489,63 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:06:35+00:00" + "time": "2017-04-12T14:13:17+00:00" + }, + { + "name": "symfony/config", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "e5533fcc0b3dd377626153b2852707878f363728" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/e5533fcc0b3dd377626153b2852707878f363728", + "reference": "e5533fcc0b3dd377626153b2852707878f363728", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "require-dev": { + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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 Config Component", + "homepage": "https://symfony.com", + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/dom-crawler", @@ -4384,17 +4604,115 @@ "time": "2017-01-21T17:13:55+00:00" }, { - "name": "symfony/yaml", - "version": "v3.2.3", + "name": "symfony/filesystem", + "version": "v3.2.8", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b" + "url": "https://github.com/symfony/filesystem.git", + "reference": "040651db13cf061827a460cc10f6e36a445c45b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e1718c6bf57e1efbb8793ada951584b2ab27775b", - "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4", + "reference": "040651db13cf061827a460cc10f6e36a445c45b4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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 Filesystem Component", + "homepage": "https://symfony.com", + "time": "2017-04-12T14:13:17+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a0105afb670dbd38f521105c444de1b8e10cfe3", + "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2017-04-12T14:13:17+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6", + "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", "shasum": "" }, "require": { @@ -4436,7 +4754,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:06:35+00:00" + "time": "2017-05-01T14:55:58+00:00" }, { "name": "webmozart/assert", @@ -4496,7 +4814,11 @@ "prefer-lowest": false, "platform": { "php": ">=7.0.0", - "ext-intl": "*" + "ext-intl": "*", + "ext-bcmath": "*", + "ext-mbstring": "*", + "ext-curl": "*", + "ext-zip": "*" }, "platform-dev": [] } diff --git a/config/app.php b/config/app.php index f92541fb61..1f0172e6b5 100644 --- a/config/app.php +++ b/config/app.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ 'name' => 'Firefly III', @@ -21,7 +21,7 @@ return [ 'fallback_locale' => 'en_US', 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', - 'log' => env('APP_LOG', 'daily'), + 'log' => env('APP_LOG', 'errorlog'), 'log_level' => env('APP_LOG_LEVEL', 'info'), 'providers' => [ @@ -70,7 +70,7 @@ return [ //Barryvdh\Debugbar\ServiceProvider::class, DaveJamesMiller\Breadcrumbs\ServiceProvider::class, TwigBridge\ServiceProvider::class, - 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider', + PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, /* * More service providers. @@ -125,7 +125,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, - 'Twig' => 'TwigBridge\Facade\Twig', + 'Twig' => TwigBridge\Facade\Twig::class, 'Form' => Collective\Html\FormFacade::class, 'Html' => Collective\Html\HtmlFacade::class, 'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade', @@ -137,7 +137,7 @@ return [ 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', 'Entrust' => 'Zizaco\Entrust\EntrustFacade', 'Input' => 'Illuminate\Support\Facades\Input', - 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', + 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, ], ]; diff --git a/config/auth.php b/config/auth.php index 55a6840414..9145ce8641 100644 --- a/config/auth.php +++ b/config/auth.php @@ -10,32 +10,32 @@ */ return [ - 'defaults' => [ - 'guard' => 'web', + 'defaults' => [ + 'guard' => 'web', 'passwords' => 'users', ], - 'guards' => [ + 'guards' => [ 'web' => [ - 'driver' => 'session', + 'driver' => 'session', 'provider' => 'users', ], 'api' => [ - 'driver' => 'token', + 'driver' => 'token', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => FireflyIII\User::class, + 'model' => FireflyIII\User::class, ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', - 'table' => 'password_resets', - 'expire' => 120, + 'table' => 'password_resets', + 'expire' => 120, ], ], diff --git a/config/broadcasting.php b/config/broadcasting.php index 8100899690..b165564840 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -40,17 +40,17 @@ return [ 'connections' => [ 'pusher' => [ - 'driver' => 'pusher', - 'key' => env('PUSHER_KEY'), - 'secret' => env('PUSHER_SECRET'), - 'app_id' => env('PUSHER_APP_ID'), + 'driver' => 'pusher', + 'key' => env('PUSHER_KEY'), + 'secret' => env('PUSHER_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), 'options' => [ // ], ], 'redis' => [ - 'driver' => 'redis', + 'driver' => 'redis', 'connection' => 'default', ], diff --git a/config/csv.php b/config/csv.php index 0cd7e1183f..5da40e41e0 100644 --- a/config/csv.php +++ b/config/csv.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ diff --git a/config/database.php b/config/database.php index 451ffd9741..f9b37b8c01 100644 --- a/config/database.php +++ b/config/database.php @@ -9,10 +9,25 @@ * See the LICENSE file for details. */ +// for heroku: +$databaseUrl = getenv('DATABASE_URL'); +$host = ''; +$username = ''; +$password = ''; +$database = ''; + +if (!($databaseUrl === false)) { + $options = parse_url($databaseUrl); + $host = $options['host']; + $username = $options['user']; + $password = $options['pass']; + $database = substr($options['path'], 1); +} + return [ 'fetch' => PDO::FETCH_OBJ, - 'default' => env('DB_CONNECTION', 'mysql'), + 'default' => env('DB_CONNECTION', 'pgsql'), 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', @@ -36,11 +51,11 @@ return [ 'pgsql' => [ 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), + 'host' => env('DB_HOST', $host), 'port' => env('DB_PORT', '5432'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), + 'database' => env('DB_DATABASE', $database), + 'username' => env('DB_USERNAME', $username), + 'password' => env('DB_PASSWORD', $password), 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', diff --git a/config/firefly.php b/config/firefly.php index 5ffebe0c43..cb76de0723 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); /* * DO NOT EDIT THIS FILE. IT IS AUTO GENERATED. @@ -18,34 +18,29 @@ declare(strict_types = 1); */ return [ - 'configuration' => [ + 'configuration' => [ 'single_user_mode' => true, 'is_demo_site' => false, ], - 'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true), - 'chart' => 'chartjs', - 'version' => '4.3.4', - 'csv_import_enabled' => true, - 'maxUploadSize' => 5242880, - 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], - 'resend_confirmation' => 3600, - 'confirmation_age' => 14400, // four hours - 'list_length' => 10, - - 'export_formats' => [ + 'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true), + 'version' => '4.4.3', + 'maxUploadSize' => 5242880, + 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], + 'list_length' => 10, + 'export_formats' => [ 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', ], - 'import_formats' => [ + 'import_formats' => [ 'csv' => 'FireflyIII\Import\Importer\CsvImporter', ], - 'default_export_format' => 'csv', - 'default_import_format' => 'csv', - 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], - 'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset',], - 'ccTypes' => [ + 'default_export_format' => 'csv', + 'default_import_format' => 'csv', + 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], + 'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset',], + 'ccTypes' => [ 'monthlyFull' => 'Full payment every month', ], - 'range_to_repeat_freq' => [ + 'range_to_repeat_freq' => [ '1D' => 'weekly', '1W' => 'weekly', '1M' => 'monthly', @@ -54,14 +49,14 @@ return [ '1Y' => 'yearly', 'custom' => 'custom', ], - 'subTitlesByIdentifier' => + 'subTitlesByIdentifier' => [ 'asset' => 'Asset accounts', 'expense' => 'Expense accounts', 'revenue' => 'Revenue accounts', 'cash' => 'Cash accounts', ], - 'subIconsByIdentifier' => + 'subIconsByIdentifier' => [ 'asset' => 'fa-money', 'Asset account' => 'fa-money', @@ -75,14 +70,14 @@ return [ 'import' => 'fa-download', 'Import account' => 'fa-download', ], - 'accountTypesByIdentifier' => + 'accountTypesByIdentifier' => [ 'asset' => ['Default account', 'Asset account'], 'expense' => ['Expense account', 'Beneficiary account'], 'revenue' => ['Revenue account'], 'import' => ['Import account'], ], - 'accountTypeByIdentifier' => + 'accountTypeByIdentifier' => [ 'asset' => 'Asset account', 'expense' => 'Expense account', @@ -91,7 +86,7 @@ return [ 'initial' => 'Initial balance account', 'import' => 'Import account', ], - 'shortNamesByFullName' => + 'shortNamesByFullName' => [ 'Default account' => 'asset', 'Asset account' => 'asset', @@ -101,20 +96,17 @@ return [ 'Revenue account' => 'revenue', 'Cash account' => 'cash', ], - 'languages' => [ + 'languages' => [ 'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German', 'complete' => true], 'en_US' => ['name_locale' => 'English', 'name_english' => 'English', 'complete' => true], - 'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish', 'complete' => false], 'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French', 'complete' => false], - 'hr_HR' => ['name_locale' => 'hrvatski', 'name_english' => 'Croatian', 'complete' => false], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch', 'complete' => true], 'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish ', 'complete' => false], 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)', 'complete' => true], - 'ru-RU' => ['name_locale' => 'Russian', 'name_english' => 'Russian', 'complete' => false], - 'zh-HK' => ['name_locale' => '繁體中文(香港)', 'name_english' => 'Chinese Traditional, Hong Kong', 'complete' => false], + 'sl-SI' => ['name_locale' => 'Slovenščina', 'name_english' => 'Slovenian', 'complete' => false], 'zh-TW' => ['name_locale' => '正體中文', 'name_english' => 'Chinese Traditional', 'complete' => false], ], - 'transactionTypesByWhat' => [ + 'transactionTypesByWhat' => [ 'expenses' => ['Withdrawal'], 'withdrawal' => ['Withdrawal'], 'revenue' => ['Deposit'], @@ -122,7 +114,7 @@ return [ 'transfer' => ['Transfer'], 'transfers' => ['Transfer'], ], - 'transactionIconsByWhat' => [ + 'transactionIconsByWhat' => [ 'expenses' => 'fa-long-arrow-left', 'withdrawal' => 'fa-long-arrow-left', 'revenue' => 'fa-long-arrow-right', @@ -131,7 +123,7 @@ return [ 'transfers' => 'fa-exchange', ], - 'bindables' => [ + 'bindables' => [ 'account' => 'FireflyIII\Models\Account', 'attachment' => 'FireflyIII\Models\Attachment', 'bill' => 'FireflyIII\Models\Bill', @@ -139,6 +131,8 @@ return [ 'category' => 'FireflyIII\Models\Category', 'transaction_type' => 'FireflyIII\Models\TransactionType', 'currency' => 'FireflyIII\Models\TransactionCurrency', + 'fromCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode', + 'toCurrencyCode' => 'FireflyIII\Support\Binder\CurrencyCode', 'limitrepetition' => 'FireflyIII\Models\LimitRepetition', 'budgetlimit' => 'FireflyIII\Models\BudgetLimit', 'piggyBank' => 'FireflyIII\Models\PiggyBank', @@ -156,8 +150,9 @@ return [ 'tagList' => 'FireflyIII\Support\Binder\TagList', 'start_date' => 'FireflyIII\Support\Binder\Date', 'end_date' => 'FireflyIII\Support\Binder\Date', + 'date' => 'FireflyIII\Support\Binder\Date', ], - 'rule-triggers' => [ + 'rule-triggers' => [ 'user_action' => 'FireflyIII\Rules\Triggers\UserAction', 'from_account_starts' => 'FireflyIII\Rules\Triggers\FromAccountStarts', 'from_account_ends' => 'FireflyIII\Rules\Triggers\FromAccountEnds', @@ -178,8 +173,9 @@ return [ 'category_is' => 'FireflyIII\Rules\Triggers\CategoryIs', 'budget_is' => 'FireflyIII\Rules\Triggers\BudgetIs', 'tag_is' => 'FireflyIII\Rules\Triggers\TagIs', + 'has_attachments' => 'FireflyIII\Rules\Triggers\HasAttachment', ], - 'rule-actions' => [ + 'rule-actions' => [ 'set_category' => 'FireflyIII\Rules\Actions\SetCategory', 'clear_category' => 'FireflyIII\Rules\Actions\ClearCategory', 'set_budget' => 'FireflyIII\Rules\Actions\SetBudget', @@ -194,7 +190,7 @@ return [ 'set_source_account' => 'FireflyIII\Rules\Actions\SetSourceAccount', 'set_destination_account' => 'FireflyIII\Rules\Actions\SetDestinationAccount', ], - 'rule-actions-text' => [ + 'rule-actions-text' => [ 'set_category', 'set_budget', 'add_tag', @@ -203,10 +199,18 @@ return [ 'append_description', 'prepend_description', ], - 'test-triggers' => [ + 'test-triggers' => [ 'limit' => 10, 'range' => 200, ], - 'default_currency' => 'EUR', - 'default_language' => 'en_US', + 'default_currency' => 'EUR', + 'default_language' => 'en_US', + 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', + 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'], + // tag notes has_attachments + 'currency_exchange_services' => [ + 'fixerio' => 'FireflyIII\Services\Currency\FixerIO', + ], + 'preferred_exchange_service' => 'fixerio', + ]; diff --git a/config/queue.php b/config/queue.php index 664188021f..c50e7c2a44 100644 --- a/config/queue.php +++ b/config/queue.php @@ -44,32 +44,32 @@ return [ ], 'database' => [ - 'driver' => 'database', - 'table' => 'jobs', - 'queue' => 'default', + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', 'retry_after' => 90, ], 'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => 'localhost', - 'queue' => 'default', + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', 'retry_after' => 90, ], 'sqs' => [ 'driver' => 'sqs', - 'key' => 'your-public-key', + 'key' => 'your-public-key', 'secret' => 'your-secret-key', 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', - 'queue' => 'your-queue-name', + 'queue' => 'your-queue-name', 'region' => 'us-east-1', ], 'redis' => [ - 'driver' => 'redis', - 'connection' => 'default', - 'queue' => 'default', + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => 'default', 'retry_after' => 90, ], @@ -88,7 +88,7 @@ return [ 'failed' => [ 'database' => env('DB_CONNECTION', 'mysql'), - 'table' => 'failed_jobs', + 'table' => 'failed_jobs', ], ]; diff --git a/config/services.php b/config/services.php index 5990b52c66..1176da0214 100644 --- a/config/services.php +++ b/config/services.php @@ -29,7 +29,7 @@ return [ ], 'ses' => [ - 'key' => env('SES_KEY'), + 'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), 'region' => 'us-east-1', ], @@ -39,8 +39,8 @@ return [ ], 'stripe' => [ - 'model' => FireflyIII\User::class, - 'key' => env('STRIPE_KEY'), + 'model' => FireflyIII\User::class, + 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], diff --git a/config/session.php b/config/session.php index f5af7f0fdc..4a635d2321 100644 --- a/config/session.php +++ b/config/session.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ diff --git a/config/twigbridge.php b/config/twigbridge.php index dc51e30651..095c342c9c 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -159,7 +159,7 @@ return [ 'ExpandedForm' => [ 'is_safe' => [ 'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', - 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', + 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', ], ], 'Form' => [ diff --git a/config/upgrade.php b/config/upgrade.php index 199596e690..58c0ad66bd 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ 'text' => [ diff --git a/config/view.php b/config/view.php index 9d4930f213..bb60cc6e7e 100644 --- a/config/view.php +++ b/config/view.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 8a65c4ee73..283a342b20 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -8,7 +8,10 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); + +use Carbon\Carbon; + /* |-------------------------------------------------------------------------- @@ -32,3 +35,206 @@ $factory->define( ]; } ); + +$factory->define( + FireflyIII\Models\CurrencyExchangeRate::class, function (Faker\Generator $faker) { + + return [ + 'user_id' => 1, + 'from_currency_id' => 1, + 'to_currency_id' => 2, + 'date' => '2017-01-01', + 'rate' => '1.5', + 'user_rate' => null, + ]; +} +); + +$factory->define( + FireflyIII\Models\TransactionCurrency::class, function (Faker\Generator $faker) { + + return [ + 'name' => $faker->words(1, true), + 'code' => 'ABC', + 'symbol' => 'x', + ]; +} +); + +$factory->define( + FireflyIII\Models\ImportJob::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 100), + 'user_id' => 1, + 'key' => $faker->words(1, true), + 'file_type' => 'csv', + 'status' => 'import_status_never_started', + 'configuration' => null, + 'extended_status' => [ + 'total_steps' => 0, + 'steps_done' => 0, + 'import_count' => 0, + 'importTag' => 0, + 'errors' => [], + ], + ]; +} +); + + +$factory->define( + FireflyIII\Models\TransactionJournal::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(1000, 10000), + 'user_id' => 1, + 'transaction_type_id' => 1, + 'bill_id' => null, + 'transaction_currency_id' => 1, + 'description' => $faker->words(3, true), + 'date' => '2017-01-01', + 'interest_date' => null, + 'book_date' => null, + 'process_date' => null, + 'order' => 0, + 'tag_count' => 0, + 'encrypted' => 0, + 'completed' => 1, + ]; +} +); + +$factory->define( + FireflyIII\Models\Bill::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 10), + 'user_id' => 1, + 'name' => $faker->words(3, true), + 'match' => $faker->words(3, true), + 'amount_min' => '100.00', + 'amount_max' => '100.00', + 'date' => '2017-01-01', + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'automatch' => 1, + 'name_encrypted' => 0, + 'match_encrypted' => 0, + ]; +} +); + +$factory->define( + FireflyIII\Models\PiggyBankRepetition::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(100, 10000), + 'piggy_bank_id' => $faker->numberBetween(1, 10), + 'startdate' => '2017-01-01', + 'targetdate' => '2020-01-01', + 'currentamount' => 10, + ]; +} +); + +$factory->define( + FireflyIII\Models\PiggyBank::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(100, 10000), + 'account_id' => $faker->numberBetween(1, 10), + 'name' => $faker->words(3, true), + 'target_amount' => '1000.00', + 'startdate' => '2017-01-01', + 'order' => 1, + 'active' => 1, + 'encrypted' => 0, + ]; +} +); + +$factory->define( + FireflyIII\Models\Tag::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(200, 10000), + 'user_id' => 1, + 'tagMode' => 'nothing', + 'tag' => $faker->words(1, true), + ]; +} +); + +$factory->define( + FireflyIII\Models\Category::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 10), + 'name' => $faker->words(3, true), + ]; +} +); + +$factory->define( + FireflyIII\Models\Budget::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 10), + 'name' => $faker->words(3, true), + ]; +} +); + +$factory->define( + FireflyIII\Models\PiggyBankEvent::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 10), + 'piggy_bank_id' => $faker->numberBetween(1, 10), + 'transaction_journal_id' => $faker->numberBetween(1, 10), + 'date' => $faker->date('Y-m-d'), + 'amount' => '100', + ]; +} +); + +$factory->define( + FireflyIII\Models\BudgetLimit::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->numberBetween(1, 10), + 'start_date' => '2017-01-01', + 'end_date' => '2017-01-31', + 'amount' => '300', + 'budget_id' => $faker->numberBetween(1, 6), + + ]; +} +); + +$factory->define( + FireflyIII\Models\Account::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(1000, 10000), + 'name' => $faker->words(3, true), + 'account_type_id' => 1, + ]; +} +); + +$factory->define( + FireflyIII\Models\Transaction::class, function (Faker\Generator $faker) { + return [ + 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)), + 'destination_amount' => strval($faker->randomFloat(2, -100, 100)), + 'opposing_account_id' => $faker->numberBetween(1, 10), + 'source_account_id' => $faker->numberBetween(1, 10), + 'opposing_account_name' => $faker->words(3, true), + 'description' => $faker->words(3, true), + 'source_account_name' => $faker->words(3, true), + 'destination_account_id' => $faker->numberBetween(1, 10), + 'date' => new Carbon, + 'destination_account_name' => $faker->words(3, true), + 'amount' => strval($faker->randomFloat(2, -100, 100)), + 'budget_id' => 0, + 'category' => $faker->words(3, true), + 'transaction_journal_id' => $faker->numberBetween(1, 10), + 'journal_id' => $faker->numberBetween(1, 10), + 'transaction_currency_code' => 'EUR', + 'transaction_type_type' => 'Withdrawal', + 'account_encrypted' => 0, + 'account_name' => 'Some name', + ]; +} +); \ No newline at end of file diff --git a/database/migrations/2016_06_16_000000_create_support_tables.php b/database/migrations/2016_06_16_000000_create_support_tables.php index 6d0234802c..cd16910972 100644 --- a/database/migrations/2016_06_16_000000_create_support_tables.php +++ b/database/migrations/2016_06_16_000000_create_support_tables.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; @@ -76,6 +76,23 @@ class CreateSupportTables extends Migration } } + private function createConfigurationTable() + { + if (!Schema::hasTable('configuration')) { + Schema::create( + 'configuration', function (Blueprint $table) { + + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->string('name', 50); + $table->text('data'); + $table->unique(['name']); + } + ); + } + } + /** * */ @@ -99,23 +116,6 @@ class CreateSupportTables extends Migration } } - private function createConfigurationTable() - { - if (!Schema::hasTable('configuration')) { - Schema::create( - 'configuration', function (Blueprint $table) { - - $table->increments('id'); - $table->timestamps(); - $table->softDeletes(); - $table->string('name', 50); - $table->text('data'); - $table->unique(['name']); - } - ); - } - } - /** * */ diff --git a/database/migrations/2016_06_16_000001_create_users_table.php b/database/migrations/2016_06_16_000001_create_users_table.php index 0120a485ed..563fbd1584 100644 --- a/database/migrations/2016_06_16_000001_create_users_table.php +++ b/database/migrations/2016_06_16_000001_create_users_table.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; @@ -18,6 +18,14 @@ use Illuminate\Database\Schema\Blueprint; */ class CreateUsersTable extends Migration { + /** + * Reverse the migrations. + */ + public function down() + { + Schema::drop('users'); + } + /** * Run the migrations. * @@ -40,12 +48,4 @@ class CreateUsersTable extends Migration ); } } - - /** - * Reverse the migrations. - */ - public function down() - { - Schema::drop('users'); - } } diff --git a/database/migrations/2016_06_16_000002_create_main_tables.php b/database/migrations/2016_06_16_000002_create_main_tables.php index 1090bcb05f..0f5fe8d4e4 100644 --- a/database/migrations/2016_06_16_000002_create_main_tables.php +++ b/database/migrations/2016_06_16_000002_create_main_tables.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; diff --git a/database/migrations/2016_08_25_091522_changes_for_3101.php b/database/migrations/2016_08_25_091522_changes_for_3101.php index 9d0e38a548..b317ef163c 100644 --- a/database/migrations/2016_08_25_091522_changes_for_3101.php +++ b/database/migrations/2016_08_25_091522_changes_for_3101.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; diff --git a/database/migrations/2016_09_12_121359_fix_nullables.php b/database/migrations/2016_09_12_121359_fix_nullables.php index 54033eac70..ba037d328b 100644 --- a/database/migrations/2016_09_12_121359_fix_nullables.php +++ b/database/migrations/2016_09_12_121359_fix_nullables.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; diff --git a/database/migrations/2016_12_28_203205_changes_for_v431.php b/database/migrations/2016_12_28_203205_changes_for_v431.php index 310ccac5a8..2411bfe498 100644 --- a/database/migrations/2016_12_28_203205_changes_for_v431.php +++ b/database/migrations/2016_12_28_203205_changes_for_v431.php @@ -42,13 +42,6 @@ class ChangesForV431 extends Migration } ); - // change field "start_date" to "startdate" -// Schema::table( -// 'budget_limits', function (Blueprint $table) { -// $table->renameColumn('startdate', 'start_date'); -// } -// ); - } /** diff --git a/database/migrations/2017_04_13_163623_changes_for_v440.php b/database/migrations/2017_04_13_163623_changes_for_v440.php new file mode 100644 index 0000000000..e19755c515 --- /dev/null +++ b/database/migrations/2017_04_13_163623_changes_for_v440.php @@ -0,0 +1,58 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('from_currency_id', false, true); + $table->integer('to_currency_id', false, true); + $table->date('date'); + $table->decimal('rate', 22, 12); + $table->decimal('user_rate', 22, 12)->nullable(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('from_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->foreign('to_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + } + ); + } + // + Schema::table( + 'transactions', function (Blueprint $table) { + $table->integer('transaction_currency_id', false, true)->after('description')->nullable(); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); + } + ); + + } +} diff --git a/database/seeds/AccountTypeSeeder.php b/database/seeds/AccountTypeSeeder.php index 64422f3a5d..e98b6b8db7 100644 --- a/database/seeds/AccountTypeSeeder.php +++ b/database/seeds/AccountTypeSeeder.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use FireflyIII\Models\AccountType; use Illuminate\Database\Seeder; @@ -21,8 +21,6 @@ class AccountTypeSeeder extends Seeder { public function run() { - DB::table('account_types')->delete(); - AccountType::create(['type' => 'Default account']); AccountType::create(['type' => 'Cash account']); AccountType::create(['type' => 'Asset account']); @@ -31,6 +29,7 @@ class AccountTypeSeeder extends Seeder AccountType::create(['type' => 'Initial balance account']); AccountType::create(['type' => 'Beneficiary account']); AccountType::create(['type' => 'Import account']); + AccountType::create(['type' => 'Loan']); } diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index a92b556f6a..504c129e47 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -8,7 +8,7 @@ * * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use Illuminate\Database\Seeder; diff --git a/database/seeds/PermissionSeeder.php b/database/seeds/PermissionSeeder.php index 2916470354..94b3e48afa 100644 --- a/database/seeds/PermissionSeeder.php +++ b/database/seeds/PermissionSeeder.php @@ -9,8 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); - +declare(strict_types=1); use FireflyIII\Models\Role; @@ -29,10 +28,10 @@ class PermissionSeeder extends Seeder $owner->description = 'User runs this instance of FF3'; // optional $owner->save(); - $demo = new Role; - $demo->name ='demo'; + $demo = new Role; + $demo->name = 'demo'; $demo->display_name = 'Demo User'; - $demo->description = 'User is a demo user'; + $demo->description = 'User is a demo user'; $demo->save(); } diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php index f1204569a3..8f51c69cd1 100644 --- a/database/seeds/TransactionCurrencySeeder.php +++ b/database/seeds/TransactionCurrencySeeder.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use FireflyIII\Models\TransactionCurrency; use Illuminate\Database\Seeder; @@ -21,8 +21,6 @@ class TransactionCurrencySeeder extends Seeder { public function run() { - DB::table('transaction_currencies')->delete(); - TransactionCurrency::create(['code' => 'EUR', 'name' => 'Euro', 'symbol' => '€', 'decimal_places' => 2]); TransactionCurrency::create(['code' => 'USD', 'name' => 'US Dollar', 'symbol' => '$', 'decimal_places' => 2]); TransactionCurrency::create(['code' => 'HUF', 'name' => 'Hungarian forint', 'symbol' => 'Ft', 'decimal_places' => 2]); diff --git a/database/seeds/TransactionTypeSeeder.php b/database/seeds/TransactionTypeSeeder.php index 690d333aba..6c1a709bb3 100644 --- a/database/seeds/TransactionTypeSeeder.php +++ b/database/seeds/TransactionTypeSeeder.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); use FireflyIII\Models\TransactionType; use Illuminate\Database\Seeder; @@ -21,9 +21,6 @@ class TransactionTypeSeeder extends Seeder { public function run() { - - DB::table('transaction_types')->delete(); - TransactionType::create(['type' => TransactionType::WITHDRAWAL]); TransactionType::create(['type' => TransactionType::DEPOSIT]); TransactionType::create(['type' => TransactionType::TRANSFER]); diff --git a/nginx_app.conf b/nginx_app.conf new file mode 100644 index 0000000000..b0a4d0ee32 --- /dev/null +++ b/nginx_app.conf @@ -0,0 +1,7 @@ +location / { + try_files $uri @rewriteapp; +} + +location @rewriteapp { + rewrite ^(.*)$ /index.php$1 last; +} \ No newline at end of file diff --git a/phpunit.coverage.specific.xml b/phpunit.coverage.specific.xml new file mode 100755 index 0000000000..0a42b9d726 --- /dev/null +++ b/phpunit.coverage.specific.xml @@ -0,0 +1,51 @@ + + + + + + + ./tests/Feature + + + + ./tests/Unit + + + + + + + + + + ./app + + + vendor/ + + + + + + + + + + + + diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml index d643a5d6ec..0997eb6bb2 100755 --- a/phpunit.coverage.xml +++ b/phpunit.coverage.xml @@ -32,7 +32,7 @@ - + diff --git a/public/css/firefly.css b/public/css/firefly.css index cc051e6442..80eea37df7 100644 --- a/public/css/firefly.css +++ b/public/css/firefly.css @@ -25,11 +25,6 @@ body.waiting * { cursor: progress; } -.ui-sortable-placeholder { - display: inline-block; - height: 1px; -} - .preferences-box { border: 1px #ddd solid; border-radius: 4px 4px 0 0; @@ -48,12 +43,6 @@ body.waiting * { 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; diff --git a/public/js/ff/accounts/create.js b/public/js/ff/accounts/create.js index 675a5fe730..228a2bfb4a 100644 --- a/public/js/ff/accounts/create.js +++ b/public/js/ff/accounts/create.js @@ -6,7 +6,7 @@ * See the LICENSE file for details. */ -/** global: Modernizr */ +/** global: Modernizr, currencies */ $(document).ready(function () { "use strict"; @@ -17,4 +17,14 @@ $(document).ready(function () { } ); } + // on change currency drop down list: + $('#ffInput_currency_id').change(updateCurrencyItems); + updateCurrencyItems(); + }); + +function updateCurrencyItems() { + var value = $('#ffInput_currency_id').val(); + var symbol = currencies[value]; + $('.non-selectable-currency-symbol').text(symbol); +} diff --git a/public/js/ff/accounts/edit.js b/public/js/ff/accounts/edit.js index 675a5fe730..cb275d4ab6 100644 --- a/public/js/ff/accounts/edit.js +++ b/public/js/ff/accounts/edit.js @@ -6,7 +6,7 @@ * See the LICENSE file for details. */ -/** global: Modernizr */ +/** global: Modernizr, currencies */ $(document).ready(function () { "use strict"; @@ -17,4 +17,14 @@ $(document).ready(function () { } ); } + + // on change currency drop down list: + $('#ffInput_currency_id').change(updateCurrencyItems); + }); + +function updateCurrencyItems() { + var value = $('#ffInput_currency_id').val(); + var symbol = currencies[value]; + $('.non-selectable-currency-symbol').text(symbol); +} diff --git a/public/js/ff/budgets/show.js b/public/js/ff/budgets/show.js index 07cf18ed9e..9d276150df 100644 --- a/public/js/ff/budgets/show.js +++ b/public/js/ff/budgets/show.js @@ -8,11 +8,23 @@ * See the LICENSE file for details. */ -/** global: budgetChartUri */ +/** global: budgetChartUri,budgetLimitID */ $(function () { "use strict"; + if (budgetLimitID > 0) { + lineChart(budgetChartUri, 'budgetOverview'); + } + if (budgetLimitID === 0) { + columnChart(budgetChartUri, 'budgetOverview'); + } + + // other three charts: + pieChart(expenseCategoryUri, 'budget-cat-out'); + pieChart(expenseAssetUri, 'budget-asset-out'); + pieChart(expenseExpenseUri, 'budget-expense-out'); + + - columnChart(budgetChartUri, 'budgetOverview'); }); diff --git a/public/js/ff/categories/show-by-date.js b/public/js/ff/categories/show-by-date.js index e97dab4db8..f3bdde08a3 100644 --- a/public/js/ff/categories/show-by-date.js +++ b/public/js/ff/categories/show-by-date.js @@ -12,5 +12,5 @@ $(function () { "use strict"; - columnChart(specific, 'period-specific-period'); + columnChart(specific, 'period-specific-period'); }); \ No newline at end of file diff --git a/public/js/ff/categories/show.js b/public/js/ff/categories/show.js index e1d5905d24..9efadd8828 100644 --- a/public/js/ff/categories/show.js +++ b/public/js/ff/categories/show.js @@ -8,11 +8,15 @@ * See the LICENSE file for details. */ -/** global: all, current, specific */ +/** global: everything, current, specific */ $(function () { "use strict"; - columnChart(all, 'all'); - columnChart(current, 'period'); - columnChart(specific, 'period-specific-period'); + + console.log('Getting charts'); + columnChart(everything, 'category-everything'); + + console.log('Specific: ' + specific); + columnChart(specific, 'specific-period'); + }); \ No newline at end of file diff --git a/public/js/ff/charts.defaults.js b/public/js/ff/charts.defaults.js index 4679f6565b..3e56c2f55f 100644 --- a/public/js/ff/charts.defaults.js +++ b/public/js/ff/charts.defaults.js @@ -42,7 +42,8 @@ var defaultChartOptions = { callbacks: { label: function (tooltipItem, data) { "use strict"; - return data.datasets[tooltipItem.datasetIndex].label + ': ' + accounting.formatMoney(tooltipItem.yLabel); + return data.datasets[tooltipItem.datasetIndex].label + ': ' + + accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol); } } } diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index d402ab67f8..35b99f0b00 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -91,7 +91,7 @@ function doubleYChart(URI, container) { "use strict"; var colorData = true; - var options = defaultChartOptions; + var options = $.extend(true, {}, defaultChartOptions); options.scales.yAxes = [ // y axis 0: { @@ -141,7 +141,7 @@ function doubleYNonStackedChart(URI, container) { "use strict"; var colorData = true; - var options = defaultChartOptions; + var options = $.extend(true, {}, defaultChartOptions); options.scales.yAxes = [ // y axis 0: { @@ -186,7 +186,7 @@ function doubleYNonStackedChart(URI, container) { */ function columnChart(URI, container) { "use strict"; - + console.log('Going to draw column chart for ' + URI + ' in ' + container); var colorData = true; var options = defaultChartOptions; var chartType = 'bar'; @@ -204,9 +204,11 @@ function stackedColumnChart(URI, container) { "use strict"; var colorData = true; - var options = defaultChartOptions; + var options = $.extend(true, {}, defaultChartOptions); + options.stacked = true; options.scales.xAxes[0].stacked = true; + options.scales.yAxes[0].stacked = true; var chartType = 'bar'; diff --git a/public/js/ff/export/index.js b/public/js/ff/export/index.js index e7af126f51..c7d0f3fd40 100644 --- a/public/js/ff/export/index.js +++ b/public/js/ff/export/index.js @@ -79,7 +79,7 @@ function showDownload() { function showError(text) { "use strict"; $('#export-error').show(); - $('#export-error>p').text(text); + $('#export-error').find('p').text(text); } function callExport() { diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index 4f33376896..c0a223edf0 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -13,6 +13,8 @@ $(function () { "use strict"; + configAccounting(currencySymbol); + $.ajaxSetup({ headers: { 'X-CSRF-Token': $('meta[name="_token"]').attr('content') @@ -20,7 +22,7 @@ $(function () { }); // when you click on a currency, this happens: - $('.currency-option').click(currencySelect); + $('.currency-option').on('click', currencySelect); var ranges = {}; ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])]; @@ -102,28 +104,30 @@ function currencySelect(e) { $('#' + spanId).text(symbol); // close the menu (hack hack) - $('#' + menuID).click(); + $('#' + menuID).dropdown('toggle'); return false; } -// Settings object that controls default parameters for library methods: -accounting.settings = { - currency: { - symbol: currencySymbol, // default currency symbol is '$' - format: accountingConfig, // controls output: %s = symbol, %v = value/number (can be object: see below) - decimal: mon_decimal_point, // decimal point separator - thousand: mon_thousands_sep, // thousands separator - precision: frac_digits // decimal places - }, - number: { - precision: 0, // default precision on numbers is 0 - thousand: ",", - decimal: "." - } -}; +function configAccounting(customCurrency) { +// Settings object that controls default parameters for library methods: + accounting.settings = { + currency: { + symbol: customCurrency, // default currency symbol is '$' + format: accountingConfig, // controls output: %s = symbol, %v = value/number (can be object: see below) + decimal: mon_decimal_point, // decimal point separator + thousand: mon_thousands_sep, // thousands separator + precision: frac_digits // decimal places + }, + number: { + precision: 0, // default precision on numbers is 0 + thousand: ",", + decimal: "." + } + }; +} function listLengthInitial() { "use strict"; diff --git a/public/js/ff/import/status.js b/public/js/ff/import/status.js index cf97ab9935..cc1ec69a9e 100644 --- a/public/js/ff/import/status.js +++ b/public/js/ff/import/status.js @@ -62,7 +62,7 @@ function updateBar(data) { function reportErrors(data) { "use strict"; - if (data.errors.length == 1) { + if (data.errors.length === 1) { $('#import-status-error-intro').text(langImportSingleError); //'An error has occured during the import. The import can continue, however.' } @@ -93,7 +93,7 @@ function kickStartJob() { function updateTimeout(data) { "use strict"; - if (data.stepsDone != stepCount) { + if (data.stepsDone !== stepCount) { stepCount = data.stepsDone; currentLimit = 0; return; diff --git a/public/js/ff/index.js b/public/js/ff/index.js index 1cd119c383..242768712d 100644 --- a/public/js/ff/index.js +++ b/public/js/ff/index.js @@ -14,7 +14,7 @@ $(function () { "use strict"; // do chart JS stuff. drawChart(); - if (showTour == true) { + if (showTour === true) { $.getJSON('json/tour').done(function (data) { var tour = new Tour( { diff --git a/public/js/ff/piggy-banks/index.js b/public/js/ff/piggy-banks/index.js index 70630ad6d5..f83320f24f 100644 --- a/public/js/ff/piggy-banks/index.js +++ b/public/js/ff/piggy-banks/index.js @@ -24,7 +24,7 @@ $(function () { $('.addMoney').on('click', addMoney); $('.removeMoney').on('click', removeMoney); - $('#sortable-piggy tbody').sortable( + $('#sortable-piggy').find('tbody').sortable( { helper: fixHelper, stop: stopSorting, diff --git a/public/js/ff/reports/default/all.js b/public/js/ff/reports/default/all.js index 1f838a0ebc..6fc394d7dd 100644 --- a/public/js/ff/reports/default/all.js +++ b/public/js/ff/reports/default/all.js @@ -99,9 +99,9 @@ function displayAjaxPartial(data, holder) { function failAjaxPartial(uri, holder) { "use strict"; - var holder = $('#' + holder); - holder.parent().find('.overlay').remove(); - holder.addClass('general-chart-error'); + var holderObject = $('#' + holder); + holderObject.parent().find('.overlay').remove(); + holderObject.addClass('general-chart-error'); } diff --git a/public/js/ff/reports/default/month.js b/public/js/ff/reports/default/month.js index 1c2f08be63..fb35e4f8ba 100644 --- a/public/js/ff/reports/default/month.js +++ b/public/js/ff/reports/default/month.js @@ -16,7 +16,7 @@ $(function () { loadAjaxPartial('categoryReport', categoryReportUri); loadAjaxPartial('budgetReport', budgetReportUri); - loadAjaxPartial('balanceReport',balanceReportUri); + loadAjaxPartial('balanceReport', balanceReportUri); }); function drawChart() { diff --git a/public/js/ff/reports/index.js b/public/js/ff/reports/index.js index df65b9704f..02994f209f 100644 --- a/public/js/ff/reports/index.js +++ b/public/js/ff/reports/index.js @@ -29,10 +29,10 @@ $(function () { { locale: { format: 'YYYY-MM-DD', - firstDay: 1, + firstDay: 1 }, minDate: minDate, - drops: 'up', + drops: 'up' } ); @@ -56,7 +56,7 @@ $(function () { // set date from cookie var startStr = readCookie('report-start'); var endStr = readCookie('report-end'); - if (startStr !== null && endStr !== null && startStr.length == 8 && endStr.length == 8) { + if (startStr !== null && endStr !== null && startStr.length === 8 && endStr.length === 8) { var startDate = moment(startStr, "YYYY-MM-DD"); var endDate = moment(endStr, "YYYY-MM-DD"); var datePicker = $('#inputDateRange').data('daterangepicker'); diff --git a/resources/lang/es_ES/pagination.php b/public/js/ff/reports/tag/all.js similarity index 67% rename from resources/lang/es_ES/pagination.php rename to public/js/ff/reports/tag/all.js index 4eeab21dee..25a412d1c5 100644 --- a/resources/lang/es_ES/pagination.php +++ b/public/js/ff/reports/tag/all.js @@ -1,6 +1,5 @@ - '« Previous', - 'next' => 'Next »', - -]; \ No newline at end of file diff --git a/public/js/ff/reports/tag/month.js b/public/js/ff/reports/tag/month.js new file mode 100644 index 0000000000..1f1584655c --- /dev/null +++ b/public/js/ff/reports/tag/month.js @@ -0,0 +1,66 @@ +/* + * month.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. + * + * See the LICENSE file for details. + */ + +/** global: tagIncomeUri, tagExpenseUri, accountIncomeUri, accountExpenseUri, tagBudgetUri, tagCategoryUri, mainUri */ + +$(function () { + "use strict"; + drawChart(); + + $('#tags-in-pie-chart-checked').on('change', function () { + redrawPieChart('tags-in-pie-chart', tagIncomeUri); + }); + + $('#tags-out-pie-chart-checked').on('change', function () { + redrawPieChart('tags-out-pie-chart', tagExpenseUri); + }); + + $('#accounts-in-pie-chart-checked').on('change', function () { + redrawPieChart('accounts-in-pie-chart', accountIncomeUri); + }); + + $('#accounts-out-pie-chart-checked').on('change', function () { + redrawPieChart('accounts-out-pie-chart', accountExpenseUri); + }); + + // two extra charts: + pieChart(tagBudgetUri, 'budgets-out-pie-chart'); + pieChart(tagCategoryUri, 'categories-out-pie-chart'); + +}); + + +function drawChart() { + "use strict"; + + // month view: + doubleYChart(mainUri, 'in-out-chart'); + + // draw pie chart of income, depending on "show other transactions too": + redrawPieChart('tags-in-pie-chart', tagIncomeUri); + redrawPieChart('tags-out-pie-chart', tagExpenseUri); + redrawPieChart('accounts-in-pie-chart', accountIncomeUri); + redrawPieChart('accounts-out-pie-chart', accountExpenseUri); + + +} + +function redrawPieChart(container, uri) { + "use strict"; + var checkbox = $('#' + container + '-checked'); + + var others = '0'; + // check if box is checked: + if (checkbox.prop('checked')) { + others = '1'; + } + uri = uri.replace('OTHERS', others); + + pieChart(uri, container); + +} diff --git a/public/js/ff/rules/create-edit.js b/public/js/ff/rules/create-edit.js index 9100050012..3b8e871e04 100644 --- a/public/js/ff/rules/create-edit.js +++ b/public/js/ff/rules/create-edit.js @@ -104,14 +104,14 @@ function addNewAction() { function removeTrigger(e) { "use strict"; var target = $(e.target); - if (target.prop("tagName") == "I") { + 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) { + if ($('.rule-trigger-tbody tr').length === 0) { addNewTrigger(); } return false; @@ -125,14 +125,14 @@ function removeTrigger(e) { function removeAction(e) { "use strict"; var target = $(e.target); - if (target.prop("tagName") == "I") { + 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) { + if ($('.rule-action-tbody tr').length === 0) { addNewAction(); } return false; @@ -300,7 +300,7 @@ function testRuleTriggers() { } // Show the modal dialog - $("#testTriggerModal").modal(); + modal.modal(); }).fail(function () { alert('Cannot get transactions for given triggers.'); }); diff --git a/public/js/ff/rules/index.js b/public/js/ff/rules/index.js index 0fc0ef7371..a64d6a6cc6 100644 --- a/public/js/ff/rules/index.js +++ b/public/js/ff/rules/index.js @@ -25,7 +25,7 @@ $(function () { { helper: fixHelper, stop: sortStop, - cursor: "move", + cursor: "move" } ); diff --git a/public/js/ff/tags/create-edit.js b/public/js/ff/tags/create-edit.js index 7184b17ae2..369d1134ef 100644 --- a/public/js/ff/tags/create-edit.js +++ b/public/js/ff/tags/create-edit.js @@ -51,28 +51,28 @@ function clearLocation() { function initialize() { "use strict"; /* - Create new map: + Create new map: */ map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions); /* - Respond to click event. + Respond to click event. */ google.maps.event.addListener(map, 'rightclick', function (event) { placeMarker(event); }); /* - Respond to zoom event. + Respond to zoom event. */ google.maps.event.addListener(map, 'zoom_changed', function () { saveZoomLevel(); }); /* - Maybe place marker? + Maybe place marker? */ - if(doPlaceMarker == true) { - var myLatlng = new google.maps.LatLng(latitude,longitude); + if (doPlaceMarker === true) { + var myLatlng = new google.maps.LatLng(latitude, longitude); var fakeEvent = {}; fakeEvent.latLng = myLatlng; placeMarker(fakeEvent); diff --git a/public/js/ff/tags/show.js b/public/js/ff/tags/show.js new file mode 100644 index 0000000000..e7142191a9 --- /dev/null +++ b/public/js/ff/tags/show.js @@ -0,0 +1,42 @@ +/* + * show.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. + * + * See the LICENSE file for details. + */ + +/* + Some vars as prep for the map: + */ +var map; +var markers = []; +var setTag = false; + +var mapOptions = { + zoom: zoomLevel, + center: new google.maps.LatLng(latitude, longitude), + disableDefaultUI: true, + zoomControl: false, + scaleControl: true, + draggable: false +}; + + +function initialize() { + "use strict"; + if (doPlaceMarker === true) { + /* + Create new map: + */ + map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions); + + var myLatlng = new google.maps.LatLng(latitude, longitude); + var marker = new google.maps.Marker({ + position: myLatlng, + map: map + }); + marker.setMap(map); + } +} +google.maps.event.addDomListener(window, 'load', initialize); diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js index 74dc53f24d..39cc72fc9a 100644 --- a/public/js/ff/transactions/list.js +++ b/public/js/ff/transactions/list.js @@ -72,8 +72,8 @@ function countChecked() { "use strict"; var checked = $('.select_all_single:checked').length; if (checked > 0) { - $('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')') - $('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')') + $('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')'); + $('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')'); $('.mass_button_options').show(); } else { diff --git a/public/js/ff/transactions/single/common.js b/public/js/ff/transactions/single/common.js new file mode 100644 index 0000000000..0fd23dd6ac --- /dev/null +++ b/public/js/ff/transactions/single/common.js @@ -0,0 +1,206 @@ +/* + * common.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. + * + * See the LICENSE file for details. + */ + +$(document).ready(function () { + "use strict"; + setCommonAutocomplete(); + runModernizer(); +}); + +/** + * Give date a datepicker if not natively supported. + */ +function runModernizer() { + if (!Modernizr.inputtypes.date) { + $('input[type="date"]').datepicker( + { + dateFormat: 'yy-mm-dd' + } + ); + } +} + +/** + * Auto complete things in both edit and create routines: + */ +function setCommonAutocomplete() { + $.getJSON('json/tags').done(function (data) { + var opt = { + typeahead: { + source: data, + afterSelect: function () { + this.$element.val(""); + } + } + }; + $('input[name="tags"]').tagsinput( + opt + ); + }); + + if ($('input[name="destination_account_name"]').length > 0) { + $.getJSON('json/expense-accounts').done(function (data) { + $('input[name="destination_account_name"]').typeahead({source: data}); + }); + } + + if ($('input[name="source_account_name"]').length > 0) { + $.getJSON('json/revenue-accounts').done(function (data) { + $('input[name="source_account_name"]').typeahead({source: data}); + }); + } + + $.getJSON('json/categories').done(function (data) { + $('input[name="category"]').typeahead({source: data}); + }); +} + +/** + * When the user changes the currency in the amount drop down, it may jump from being + * the native currency to a foreign currency. This triggers the display of several + * information things that make sure that the user always supplies the amount in the native currency. + * + * @returns {boolean} + */ +function selectsForeignCurrency() { + var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); + var selectedAccountId = getAccountId(); + var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); + + if (foreignCurrencyId !== nativeCurrencyId) { + console.log('User has selected currency #' + foreignCurrencyId + ' and this is different from native currency #' + nativeCurrencyId); + + // the input where the native amount is entered gets the symbol for the native currency: + $('.non-selectable-currency-symbol').text(currencyInfo[nativeCurrencyId].symbol); + + // the instructions get updated: + $('#ffInput_exchange_rate_instruction').text(getExchangeInstructions()); + + // both holders are shown to the user: + $('#exchange_rate_instruction_holder').show(); + $('#native_amount_holder').show(); + + // if possible the amount is already exchanged for the foreign currency + convertForeignToNative(); + + } + if (foreignCurrencyId === nativeCurrencyId) { + console.log('User has selected currency #' + foreignCurrencyId + ' and this is equal to native currency #' + nativeCurrencyId + ' (phew).'); + $('#exchange_rate_instruction_holder').hide(); + $('#native_amount_holder').hide(); + } + + return false; +} + +/** + * Converts any foreign amount to the native currency. + */ +function convertForeignToNative() { + var accountId = getAccountId(); + var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); + var nativeCurrencyId = parseInt(accountInfo[accountId].preferredCurrency); + var foreignCurrencyCode = currencyInfo[foreignCurrencyId].code; + var nativeCurrencyCode = currencyInfo[nativeCurrencyId].code; + var date = $('#ffInput_date').val(); + var amount = $('#ffInput_amount').val(); + var uri = 'json/rate/' + foreignCurrencyCode + '/' + nativeCurrencyCode + '/' + date + '?amount=' + amount; + console.log('Will grab ' + uri); + $.get(uri).done(updateNativeAmount); +} + +/** + * Once the data has been grabbed will update the field in the form. + * @param data + */ +function updateNativeAmount(data) { + console.log('Returned data:'); + console.log(data); + $('#ffInput_native_amount').val(data.amount); +} + +/** + * Instructions for transfers + */ +function getTransferExchangeInstructions() { + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + + return transferInstructions.replace('@source_name', accountInfo[sourceAccount].name) + .replace('@dest_name', accountInfo[destAccount].name) + .replace(/@source_currency/g, currencyInfo[sourceCurrency].name) + .replace(/@dest_currency/g, currencyInfo[destinationCurrency].name); +} + +/** + * When the transaction to create is a transfer some more checks are necessary. + */ +function validateCurrencyForTransfer() { + if (what !== "transfer") { + return; + } + $('#source_amount_holder').show(); + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var sourceSymbol = currencyInfo[sourceCurrency].symbol; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + var destinationSymbol = currencyInfo[destinationCurrency].symbol; + + $('#source_amount_holder').show().find('.non-selectable-currency-symbol').text(sourceSymbol); + + if (sourceCurrency === destinationCurrency) { + console.log('Both accounts accept #' + sourceCurrency); + $('#destination_amount_holder').hide(); + $('#amount_holder').hide(); + return; + } + console.log('Source accepts #' + sourceCurrency + ', destination #' + destinationCurrency); + $('#ffInput_exchange_rate_instruction').text(getTransferExchangeInstructions()); + $('#exchange_rate_instruction_holder').show(); + $('input[name="source_amount"]').val($('input[name="amount"]').val()); + convertSourceToDestination(); + + $('#destination_amount_holder').show().find('.non-selectable-currency-symbol').text(destinationSymbol); + $('#amount_holder').hide(); +} + +/** + * Convert from source amount currency to destination currency for transfers. + * + */ +function convertSourceToDestination() { + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + + var sourceCurrencyCode = currencyInfo[sourceCurrency].code; + var destinationCurrencyCode = currencyInfo[destinationCurrency].code; + + var date = $('#ffInput_date').val(); + var amount = $('#ffInput_source_amount').val(); + $('#ffInput_amount').val(amount); + var uri = 'json/rate/' + sourceCurrencyCode + '/' + destinationCurrencyCode + '/' + date + '?amount=' + amount; + console.log('Will grab ' + uri); + $.get(uri).done(updateDestinationAmount); +} + +/** + * Once the data has been grabbed will update the field (for transfers) + * @param data + */ +function updateDestinationAmount(data) { + console.log('Returned data:'); + console.log(data); + $('#ffInput_destination_amount').val(data.amount); +} \ No newline at end of file diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index 7a38946984..56109f855c 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -6,76 +6,85 @@ * See the LICENSE file for details. */ -/** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, doSwitch, middleCrumbUrl */ +/** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, middleCrumbUrl,exchangeRateInstructions */ $(document).ready(function () { "use strict"; - // respond to switch buttons when - // creating stuff: - if (doSwitch == true) { - updateButtons(); - updateForm(); - updateLayout(); - updateDescription(); - } + // hide ALL exchange things and AMOUNT things + $('#exchange_rate_instruction_holder').hide(); + $('#native_amount_holder').hide(); + $('#amount_holder').hide(); + $('#source_amount_holder').hide(); + $('#destination_amount_holder').hide(); - if (!Modernizr.inputtypes.date) { - $('input[type="date"]').datepicker( - { - dateFormat: 'yy-mm-dd' - } - ); - } + // respond to switch buttons (first time always triggers) + updateButtons(); + updateForm(); + updateLayout(); + updateDescription(); + updateNativeCurrency(); - // get JSON things: - getJSONautocomplete(); + // when user changes source account or destination, native currency may be different. + $('select[name="source_account_id"]').on('change', updateNativeCurrency); + $('select[name="destination_account_id"]').on('change', updateNativeCurrency); + + // convert foreign currency to native currency (when input changes, exchange rate) + $('#ffInput_amount').on('change', convertForeignToNative); + + // convert source currency to destination currency (slightly different routine for transfers) + $('#ffInput_source_amount').on('change', convertSourceToDestination); + + // when user selects different currency, + $('.currency-option').on('click', selectsForeignCurrency); }); +/** + * This function generates a small helper text to explain the user + * that they have selected a foreign currency. + * @returns {XML|string|void} + */ +function getExchangeInstructions() { + var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); + var selectedAccountId = getAccountId(); + var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); + + var text = exchangeRateInstructions.replace('@name', accountInfo[selectedAccountId].name); + text = text.replace(/@native_currency/g, currencyInfo[nativeCurrencyId].name); + text = text.replace(/@foreign_currency/g, currencyInfo[foreignCurrencyId].name); + return text; +} + +/** + * There is an input that shows the currency symbol that is native to the selected + * acccount. So when the user changes the selected account, the native currency is updated: + */ +function updateNativeCurrency() { + var newAccountId = getAccountId(); + var nativeCurrencyId = accountInfo[newAccountId].preferredCurrency; + + console.log('User selected account #' + newAccountId + '. Native currency is #' + nativeCurrencyId); + + $('.currency-option[data-id="' + nativeCurrencyId + '"]').click(); + $('[data-toggle="dropdown"]').parent().removeClass('open'); + //$('select[name="source_account_id"]').focus(); + + validateCurrencyForTransfer(); +} + +/** + * + */ function updateDescription() { $.getJSON('json/transaction-journals/' + what).done(function (data) { - $('input[name="description"]').typeahead('destroy'); - $('input[name="description"]').typeahead({source: data}); + $('input[name="description"]').typeahead('destroy').typeahead({source: data}); }); } -function getJSONautocomplete() { - - // for withdrawals - $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data}); - }); - - // for tags: - if ($('input[name="tags"]').length > 0) { - $.getJSON('json/tags').done(function (data) { - - var opt = { - typeahead: { - source: data, - afterSelect: function () { - this.$element.val(""); - } - } - }; - $('input[name="tags"]').tagsinput( - opt - ); - }); - } - - // for deposits - $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data}); - }); - - $.getJSON('json/categories').done(function (data) { - $('input[name="category"]').typeahead({source: data}); - }); - -} - +/** + * + */ function updateLayout() { "use strict"; $('#subTitle').text(title[what]); @@ -84,78 +93,96 @@ function updateLayout() { $('#transaction-btn').text(button[what]); } +/** + * + */ function updateForm() { "use strict"; $('input[name="what"]').val(what); + + var destName = $('#ffInput_destination_account_name'); + var srcName = $('#ffInput_source_account_name'); + switch (what) { case 'withdrawal': - // show source_id and dest_name: - $('#source_account_id_holder').show(); - $('#destination_account_name_holder').show(); + // show source_id and dest_name + document.getElementById('source_account_id_holder').style.display = 'block'; + document.getElementById('destination_account_name_holder').style.display = 'block'; // hide others: - $('#source_account_name_holder').hide(); - $('#destination_account_id_holder').hide(); - - // show budget: - $('#budget_id_holder').show(); + document.getElementById('source_account_name_holder').style.display = 'none'; + document.getElementById('destination_account_id_holder').style.display = 'none'; + document.getElementById('budget_id_holder').style.display = 'block'; // hide piggy bank: - $('#piggy_bank_id_holder').hide(); + document.getElementById('piggy_bank_id_holder').style.display = 'none'; - // copy destination account name to - // source account name: - if ($('#ffInput_destination_account_name').val().length > 0) { - $('#ffInput_source_account_name').val($('#ffInput_destination_account_name').val()); + // copy destination account name to source account name: + if (destName.val().length > 0) { + srcName.val(destName.val()); } + // exchange / foreign currencies: + // hide explanation, hide source and destination amounts, show normal amount + document.getElementById('exchange_rate_instruction_holder').style.display = 'none'; + document.getElementById('source_amount_holder').style.display = 'none'; + document.getElementById('destination_amount_holder').style.display = 'none'; + document.getElementById('amount_holder').style.display = 'block'; break; case 'deposit': // show source_name and dest_id: - $('#source_account_name_holder').show(); - $('#destination_account_id_holder').show(); + document.getElementById('source_account_name_holder').style.display = 'block'; + document.getElementById('destination_account_id_holder').style.display = 'block'; // hide others: - $('#source_account_id_holder').hide(); - $('#destination_account_name_holder').hide(); + document.getElementById('source_account_id_holder').style.display = 'none'; + document.getElementById('destination_account_name_holder').style.display = 'none'; // hide budget - $('#budget_id_holder').hide(); + document.getElementById('budget_id_holder').style.display = 'none'; // hide piggy bank - $('#piggy_bank_id_holder').hide(); + document.getElementById('piggy_bank_id_holder').style.display = 'none'; - if ($('#ffInput_source_account_name').val().length > 0) { - $('#ffInput_destination_account_name').val($('#ffInput_source_account_name').val()); + // copy name + if (srcName.val().length > 0) { + destName.val(srcName.val()); } + // exchange / foreign currencies: + // hide explanation, hide source and destination amounts, show amount + document.getElementById('exchange_rate_instruction_holder').style.display = 'none'; + document.getElementById('source_amount_holder').style.display = 'none'; + document.getElementById('destination_amount_holder').style.display = 'none'; + document.getElementById('amount_holder').style.display = 'block'; break; case 'transfer': // show source_id and dest_id: - $('#source_account_id_holder').show(); - $('#destination_account_id_holder').show(); + document.getElementById('source_account_id_holder').style.display = 'block'; + document.getElementById('destination_account_id_holder').style.display = 'block'; // hide others: - $('#source_account_name_holder').hide(); - $('#destination_account_name_holder').hide(); - + document.getElementById('source_account_name_holder').style.display = 'none'; + document.getElementById('destination_account_name_holder').style.display = 'none'; // hide budget - $('#budget_id_holder').hide(); + document.getElementById('budget_id_holder').style.display = 'none'; + + // optional piggies + var showPiggies = 'block'; if (piggiesLength === 0) { - $('#piggy_bank_id_holder').hide(); - } else { - $('#piggy_bank_id_holder').show(); + showPiggies = 'none'; } - break; - default: - // no action. + document.getElementById('piggy_bank_id_holder').style.display = showPiggies; break; } + updateNativeCurrency(); } - +/** + * + */ function updateButtons() { "use strict"; $('.switch').each(function (i, v) { @@ -166,7 +193,7 @@ function updateButtons() { // new click event: button.bind('click', clickButton); - if (button.data('what') == what) { + if (button.data('what') === what) { button.removeClass('btn-default').addClass('btn-info').html(' ' + txt[button.data('what')]); } else { button.removeClass('btn-info').addClass('btn-default').text(txt[button.data('what')]); @@ -174,11 +201,16 @@ function updateButtons() { }); } +/** + * + * @param e + * @returns {boolean} + */ function clickButton(e) { "use strict"; var button = $(e.target); var newWhat = button.data('what'); - if (newWhat != what) { + if (newWhat !== what) { what = newWhat; updateButtons(); updateForm(); @@ -186,4 +218,16 @@ function clickButton(e) { updateDescription(); } return false; -} \ No newline at end of file +} + +/** + * Get accountID based on some meta info. + */ +function getAccountId() { + if (what === "withdrawal") { + return $('select[name="source_account_id"]').val(); + } + if (what === "deposit" || what === "transfer") { + return $('select[name="destination_account_id"]').val(); + } +} diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js index a025212d43..17f2d0a5c0 100644 --- a/public/js/ff/transactions/single/edit.js +++ b/public/js/ff/transactions/single/edit.js @@ -12,51 +12,95 @@ $(document).ready(function () { "use strict"; - // give date a datepicker if not natively supported. - if (!Modernizr.inputtypes.date) { - $('input[type="date"]').datepicker( - { - dateFormat: 'yy-mm-dd' - } - ); + setAutocompletes(); + updateInitialPage(); + + // respond to user input: + $('.currency-option').on('click', selectsForeignCurrency); + $('#ffInput_amount').on('change', convertForeignToNative); + + // respond to transfer changes: + $('#ffInput_source_account_id').on('change',validateCurrencyForTransfer); + $('#ffInput_destination_account_id').on('change',validateCurrencyForTransfer); + + // convert source currency to destination currency (slightly different routine for transfers) + $('#ffInput_source_amount').on('change', convertSourceToDestination); +}); + +/** + * Set some initial values for the user to see. + */ +function updateInitialPage() { + + console.log('Native currency is #' + journalData.native_currency.id + ' and (foreign) currency id is #' + journalData.currency.id); + + if (journal.transaction_type.type === "Transfer") { + $('#native_amount_holder').hide(); + $('#amount_holder').hide(); + + if (journalData.native_currency.id === journalData.currency.id) { + $('#exchange_rate_instruction_holder').hide(); + $('#destination_amount_holder').hide(); + } + if (journalData.native_currency.id !== journalData.currency.id) { + $('#exchange_rate_instruction_holder').show().find('p').text(getTransferExchangeInstructions()); + + } + + return; + } + $('#source_amount_holder').hide(); + $('#destination_amount_holder').hide(); + + + if (journalData.native_currency.id === journalData.currency.id) { + $('#exchange_rate_instruction_holder').hide(); + $('#native_amount_holder').hide(); } - // the destination account name is always an expense account name. - if ($('input[name="destination_account_name"]').length > 0) { - $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data}); - }); + if (journalData.native_currency.id !== journalData.currency.id) { + $('#ffInput_exchange_rate_instruction').text(getExchangeInstructions()); } - $.getJSON('json/tags').done(function (data) { +} - var opt = { - typeahead: { - source: data, - afterSelect: function () { - this.$element.val(""); - } - } - }; - $('input[name="tags"]').tagsinput( - opt - ); - }); - // the source account name is always a revenue account name. - if ($('input[name="source_account_name"]').length > 0) { - $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data}); - }); + +/** + * Get accountID based on some meta info. + */ +function getAccountId() { + if (journal.transaction_type.type === "Withdrawal") { + return $('select[name="source_account_id"]').val(); + } + if (journal.transaction_type.type === "Deposit") { + return $('select[name="destination_account_id"]').val(); } + alert('Cannot handle ' + journal.transaction_type.type); +} + +/** + * Set the auto-complete JSON things. + */ +function setAutocompletes() { $.getJSON('json/transaction-journals/' + what).done(function (data) { $('input[name="description"]').typeahead({source: data}); }); +} +/** + * This function generates a small helper text to explain the user + * that they have selected a foreign currency. + * @returns {XML|string|void} + */ +function getExchangeInstructions() { + var selectedAccountId = getAccountId(); + var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); + var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); - $.getJSON('json/categories').done(function (data) { - $('input[name="category"]').typeahead({source: data}); - }); - -}); + var text = exchangeRateInstructions.replace('@name', accountInfo[selectedAccountId].name); + text = text.replace(/@native_currency/g, currencyInfo[nativeCurrencyId].name); + text = text.replace(/@foreign_currency/g, currencyInfo[foreignCurrencyId].name); + return text; +} \ No newline at end of file diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index 19fffbdf4b..a91c0a3422 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -184,12 +184,12 @@ function calculateSum() { var set = $('input[name$="][amount]"]'); for (var i = 0; i < set.length; i++) { var current = $(set[i]); - sum += (current.val() == "" ? 0 : parseFloat(current.val())); + sum += (current.val() === "" ? 0 : parseFloat(current.val())); } sum = Math.round(sum * 100) / 100; $('.amount-warning').remove(); - if (sum != originalSum) { + if (sum !== originalSum) { var holder = $('#journal_amount_holder'); var par = holder.find('p.form-control-static'); $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); diff --git a/public/js/lib/Chart.bundle.min.js b/public/js/lib/Chart.bundle.min.js index 792c8e14b5..4621b008fc 100644 --- a/public/js/lib/Chart.bundle.min.js +++ b/public/js/lib/Chart.bundle.min.js @@ -1,15 +1,16 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 2.3.0 + * Version: 2.5.0 * - * Copyright 2016 Nick Downie + * Copyright 2017 Nick Downie * Released under the MIT license * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Chart=t()}}(function(){var t;return function e(t,n,i){function a(r,s){if(!n[r]){if(!t[r]){var l="function"==typeof require&&require;if(!s&&l)return l(r,!0);if(o)return o(r,!0);var d=new Error("Cannot find module '"+r+"'");throw d.code="MODULE_NOT_FOUND",d}var u=n[r]={exports:{}};t[r][0].call(u.exports,function(e){var n=t[r][1][e];return a(n?n:e)},u,u.exports,e,t,n,i)}return n[r].exports}for(var o="function"==typeof require&&require,r=0;re||t[3]&&t[3]<1?c(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function c(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function h(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"}function f(t,e){var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgba("+n+"%, "+i+"%, "+a+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?m(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function v(t){return k[t.slice(0,3)]}function b(t,e,n){return Math.min(Math.max(e,t),n)}function y(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var x=t(5);e.exports={getRgba:i,getHsla:a,getRgb:r,getHsl:s,getHwb:o,getAlpha:l,hexString:d,rgbString:u,rgbaString:c,percentString:h,percentaString:f,hslString:g,hslaString:m,hwbString:p,keyword:v};var k={};for(var S in x)k[x[S]]=S},{5:5}],2:[function(t,e,n){var i=t(4),a=t(1),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1};var e;if("string"==typeof t)if(e=a.getRgba(t))this.setValues("rgb",e);else if(e=a.getHsla(t))this.setValues("hsl",e);else{if(!(e=a.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}else if("object"==typeof t)if(e=t,void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}};o.prototype={rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t%=360,t=0>t?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return a.hexString(this.values.rgb)},rgbString:function(){return a.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return a.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return a.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return a.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return a.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return a.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return a.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],n=0;n=i?i/12.92:Math.pow((i+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),n=t.luminosity();return e>n?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=0>n?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=this,i=t,a=void 0===e?.5:e,o=2*a-1,r=n.alpha()-i.alpha(),s=((o*r===-1?o:(o+r)/(1+o*r))+1)/2,l=1-s;return this.rgb(s*n.red()+l*i.red(),s*n.green()+l*i.green(),s*n.blue()+l*i.blue()).alpha(n.alpha()*a+i.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new o,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],e={}.toString.call(t),"[object Array]"===e?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},o.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},o.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},o.prototype.getValues=function(t){for(var e=this.values,n={},i=0;ie&&(e+=360),i=(s+l)/2,n=l==s?0:.5>=i?d/(l+s):d/(2-l-s),[e,100*n,100*i]}function a(t){var e,n,i,a=t[0],o=t[1],r=t[2],s=Math.min(a,o,r),l=Math.max(a,o,r),d=l-s;return n=0==l?0:d/l*1e3/10,l==s?e=0:a==l?e=(o-r)/d:o==l?e=2+(r-a)/d:r==l&&(e=4+(a-o)/d),e=Math.min(60*e,360),0>e&&(e+=360),i=l/255*1e3/10,[e,n,i]}function o(t){var e=t[0],n=t[1],a=t[2],o=i(t)[0],r=1/255*Math.min(e,Math.min(n,a)),a=1-1/255*Math.max(e,Math.max(n,a));return[o,100*r,100*a]}function s(t){var e,n,i,a,o=t[0]/255,r=t[1]/255,s=t[2]/255;return a=Math.min(1-o,1-r,1-s),e=(1-o-a)/(1-a)||0,n=(1-r-a)/(1-a)||0,i=(1-s-a)/(1-a)||0,[100*e,100*n,100*i,100*a]}function l(t){return K[JSON.stringify(t)]}function d(t){var e=t[0]/255,n=t[1]/255,i=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92;var a=.4124*e+.3576*n+.1805*i,o=.2126*e+.7152*n+.0722*i,r=.0193*e+.1192*n+.9505*i;return[100*a,100*o,100*r]}function u(t){var e,n,i,a=d(t),o=a[0],r=a[1],s=a[2];return o/=95.047,r/=100,s/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,e=116*r-16,n=500*(o-r),i=200*(r-s),[e,n,i]}function c(t){return B(u(t))}function h(t){var e,n,i,a,o,r=t[0]/360,s=t[1]/100,l=t[2]/100;if(0==s)return o=255*l,[o,o,o];n=.5>l?l*(1+s):l+s-l*s,e=2*l-n,a=[0,0,0];for(var d=0;3>d;d++)i=r+1/3*-(d-1),0>i&&i++,i>1&&i--,o=1>6*i?e+6*(n-e)*i:1>2*i?n:2>3*i?e+(n-e)*(2/3-i)*6:e,a[d]=255*o;return a}function f(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,a*=1>=o?o:2-o,n=(o+a)/2,e=2*a/(o+a),[i,100*e,100*n])}function m(t){return o(h(t))}function p(t){return s(h(t))}function v(t){return l(h(t))}function y(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),r=255*i*(1-n),s=255*i*(1-n*o),l=255*i*(1-n*(1-o)),i=255*i;switch(a){case 0:return[i,l,r];case 1:return[s,i,r];case 2:return[r,i,l];case 3:return[r,s,i];case 4:return[l,r,i];case 5:return[i,r,s]}}function x(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return n=(2-a)*o,e=a*o,e/=1>=n?n:2-n,e=e||0,n/=2,[i,100*e,100*n]}function k(t){return o(y(t))}function S(t){return s(y(t))}function w(t){return l(y(t))}function _(t){var e,n,i,a,o=t[0]/360,s=t[1]/100,l=t[2]/100,d=s+l;switch(d>1&&(s/=d,l/=d),e=Math.floor(6*o),n=1-l,i=6*o-e,0!=(1&e)&&(i=1-i),a=s+i*(n-s),e){default:case 6:case 0:r=n,g=a,b=s;break;case 1:r=a,g=n,b=s;break;case 2:r=s,g=n,b=a;break;case 3:r=s,g=a,b=n;break;case 4:r=a,g=s,b=n;break;case 5:r=n,g=s,b=a}return[255*r,255*g,255*b]}function M(t){return i(_(t))}function D(t){return a(_(t))}function C(t){return s(_(t))}function T(t){return l(_(t))}function P(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100,s=t[3]/100;return e=1-Math.min(1,a*(1-s)+s),n=1-Math.min(1,o*(1-s)+s),i=1-Math.min(1,r*(1-s)+s),[255*e,255*n,255*i]}function F(t){return i(P(t))}function I(t){return a(P(t))}function A(t){return o(P(t))}function O(t){return l(P(t))}function R(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100;return e=3.2406*a+-1.5372*o+r*-.4986,n=a*-.9689+1.8758*o+.0415*r,i=.0557*a+o*-.204+1.057*r,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n=12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,e=Math.min(Math.max(0,e),1),n=Math.min(Math.max(0,n),1),i=Math.min(Math.max(0,i),1),[255*e,255*n,255*i]}function W(t){var e,n,i,a=t[0],o=t[1],r=t[2];return a/=95.047,o/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,n=500*(a-o),i=200*(o-r),[e,n,i]}function L(t){return B(W(t))}function V(t){var e,n,i,a,o=t[0],r=t[1],s=t[2];return 8>=o?(n=100*o/903.3,a=7.787*(n/100)+16/116):(n=100*Math.pow((o+16)/116,3),a=Math.pow(n/100,1/3)),e=.008856>=e/95.047?e=95.047*(r/500+a-16/116)/7.787:95.047*Math.pow(r/500+a,3),i=.008859>=i/108.883?i=108.883*(a-s/200-16/116)/7.787:108.883*Math.pow(a-s/200,3),[e,n,i]}function B(t){var e,n,i,a=t[0],o=t[1],r=t[2];return e=Math.atan2(r,o),n=360*e/2/Math.PI,0>n&&(n+=360),i=Math.sqrt(o*o+r*r),[a,i,n]}function Y(t){return R(V(t))}function z(t){var e,n,i,a=t[0],o=t[1],r=t[2];return i=r/360*2*Math.PI,e=o*Math.cos(i),n=o*Math.sin(i),[a,e,n]}function H(t){return V(z(t))}function N(t){return Y(z(t))}function E(t){return X[t]}function U(t){return i(E(t))}function j(t){return a(E(t))}function G(t){return o(E(t))}function q(t){return s(E(t))}function Z(t){return u(E(t))}function J(t){return d(E(t))}e.exports={rgb2hsl:i,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:s,rgb2keyword:l,rgb2xyz:d,rgb2lab:u,rgb2lch:c,hsl2rgb:h,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:y,hsv2hsl:x,hsv2hwb:k,hsv2cmyk:S,hsv2keyword:w,hwb2rgb:_,hwb2hsl:M,hwb2hsv:D,hwb2cmyk:C,hwb2keyword:T,cmyk2rgb:P,cmyk2hsl:F,cmyk2hsv:I,cmyk2hwb:A,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:G,keyword2cmyk:q,keyword2lab:Z,keyword2xyz:J,xyz2rgb:R,xyz2lab:W,xyz2lch:L,lab2xyz:V,lab2rgb:Y,lab2lch:B,lch2lab:z,lch2xyz:H,lch2rgb:N};var X={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},K={};for(var Q in X)K[JSON.stringify(X[Q])]=Q},{}],4:[function(t,e,n){var i=t(3),a=function(){return new d};for(var o in i){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(o);var r=/(\w+)2(\w+)/.exec(o),s=r[1],l=r[2];a[s]=a[s]||{},a[s][l]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var n=i[t](e);if("string"==typeof n||void 0===n)return n;for(var a=0;a0)for(n in vi)i=vi[n],a=e[i],p(a)||(t[i]=a);return t}function b(e){v(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),bi===!1&&(bi=!0,t.updateOffset(this),bi=!1)}function y(t){return t instanceof b||null!=t&&null!=t._isAMomentObject}function x(t){return 0>t?Math.ceil(t)||0:Math.floor(t)}function k(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=x(e)),n}function S(t,e,n){var i,a=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),r=0;for(i=0;a>i;i++)(n&&t[i]!==e[i]||!n&&k(t[i])!==k(e[i]))&&r++;return r+o}function w(e){t.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function _(e,n){var i=!0;return u(function(){if(null!=t.deprecationHandler&&t.deprecationHandler(null,e),i){for(var a,o=[],r=0;r0?"future":"past"];return D(n)?n(e):n.replace(/%s/i,e)}function L(t,e){var n=t.toLowerCase();Ti[n]=Ti[n+"s"]=Ti[e]=t}function V(t){return"string"==typeof t?Ti[t]||Ti[t.toLowerCase()]:void 0}function B(t){var e,n,i={};for(n in t)d(t,n)&&(e=V(n),e&&(i[e]=t[n]));return i}function Y(t,e){Pi[t]=e}function z(t){var e=[];for(var n in t)e.push({unit:n,priority:Pi[n]});return e.sort(function(t,e){return t.priority-e.priority}),e}function H(e,n){return function(i){return null!=i?(E(this,e,i),t.updateOffset(this,n),this):N(this,e)}}function N(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function E(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function U(t){return t=V(t),D(this[t])?this[t]():this}function j(t,e){if("object"==typeof t){t=B(t);for(var n=z(t),i=0;i=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}function q(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(Oi[t]=a),e&&(Oi[e[0]]=function(){return G(a.apply(this,arguments),e[1],e[2])}),n&&(Oi[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function Z(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function J(t){var e,n,i=t.match(Fi);for(e=0,n=i.length;n>e;e++)Oi[i[e]]?i[e]=Oi[i[e]]:i[e]=Z(i[e]);return function(e){var a,o="";for(a=0;n>a;a++)o+=i[a]instanceof Function?i[a].call(e,t):i[a];return o}}function X(t,e){return t.isValid()?(e=K(e,t.localeData()),Ai[e]=Ai[e]||J(e),Ai[e](t)):t.localeData().invalidDate()}function K(t,e){function n(t){return e.longDateFormat(t)||t}var i=5;for(Ii.lastIndex=0;i>=0&&Ii.test(t);)t=t.replace(Ii,n),Ii.lastIndex=0,i-=1;return t}function Q(t,e,n){Ki[t]=D(e)?e:function(t,i){return t&&n?n:e}}function $(t,e){return d(Ki,t)?Ki[t](e._strict,e._locale):new RegExp(tt(t))}function tt(t){return et(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,i,a){return e||n||i||a}))}function et(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function nt(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(i=function(t,n){n[e]=k(t)}),n=0;ni;++i)o=c([2e3,i]),this._shortMonthsParse[i]=this.monthsShort(o,"").toLocaleLowerCase(),this._longMonthsParse[i]=this.months(o,"").toLocaleLowerCase();return n?"MMM"===e?(a=ki.call(this._shortMonthsParse,r),-1!==a?a:null):(a=ki.call(this._longMonthsParse,r),-1!==a?a:null):"MMM"===e?(a=ki.call(this._shortMonthsParse,r),-1!==a?a:(a=ki.call(this._longMonthsParse,r),-1!==a?a:null)):(a=ki.call(this._longMonthsParse,r),-1!==a?a:(a=ki.call(this._shortMonthsParse,r),-1!==a?a:null))}function dt(t,e,n){var i,a,o;if(this._monthsParseExact)return lt.call(this,t,e,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),i=0;12>i;i++){if(a=c([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(o="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[i]=new RegExp(o.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i;if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i;if(!n&&this._monthsParse[i].test(t))return i}}function ut(t,e){var n;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=k(e);else if(e=t.localeData().monthsParse(e),"number"!=typeof e)return t;return n=Math.min(t.date(),ot(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function ct(e){return null!=e?(ut(this,e), -t.updateOffset(this,!0),this):N(this,"Month")}function ht(){return ot(this.year(),this.month())}function ft(t){return this._monthsParseExact?(d(this,"_monthsRegex")||mt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):(d(this,"_monthsShortRegex")||(this._monthsShortRegex=ca),this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex)}function gt(t){return this._monthsParseExact?(d(this,"_monthsRegex")||mt.call(this),t?this._monthsStrictRegex:this._monthsRegex):(d(this,"_monthsRegex")||(this._monthsRegex=ha),this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex)}function mt(){function t(t,e){return e.length-t.length}var e,n,i=[],a=[],o=[];for(e=0;12>e;e++)n=c([2e3,e]),i.push(this.monthsShort(n,"")),a.push(this.months(n,"")),o.push(this.months(n,"")),o.push(this.monthsShort(n,""));for(i.sort(t),a.sort(t),o.sort(t),e=0;12>e;e++)i[e]=et(i[e]),a[e]=et(a[e]);for(e=0;24>e;e++)o[e]=et(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+i.join("|")+")","i")}function pt(t){return vt(t)?366:365}function vt(t){return t%4===0&&t%100!==0||t%400===0}function bt(){return vt(this.year())}function yt(t,e,n,i,a,o,r){var s=new Date(t,e,n,i,a,o,r);return 100>t&&t>=0&&isFinite(s.getFullYear())&&s.setFullYear(t),s}function xt(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function kt(t,e,n){var i=7+e-n,a=(7+xt(t,0,i).getUTCDay()-e)%7;return-a+i-1}function St(t,e,n,i,a){var o,r,s=(7+n-i)%7,l=kt(t,i,a),d=1+7*(e-1)+s+l;return 0>=d?(o=t-1,r=pt(o)+d):d>pt(t)?(o=t+1,r=d-pt(t)):(o=t,r=d),{year:o,dayOfYear:r}}function wt(t,e,n){var i,a,o=kt(t.year(),e,n),r=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>r?(a=t.year()-1,i=r+_t(a,e,n)):r>_t(t.year(),e,n)?(i=r-_t(t.year(),e,n),a=t.year()+1):(a=t.year(),i=r),{week:i,year:a}}function _t(t,e,n){var i=kt(t,e,n),a=kt(t+1,e,n);return(pt(t)-i+a)/7}function Mt(t){return wt(t,this._week.dow,this._week.doy).week}function Dt(){return this._week.dow}function Ct(){return this._week.doy}function Tt(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function Pt(t){var e=wt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function Ft(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function It(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}function At(t,e){return t?a(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]:this._weekdays}function Ot(t){return t?this._weekdaysShort[t.day()]:this._weekdaysShort}function Rt(t){return t?this._weekdaysMin[t.day()]:this._weekdaysMin}function Wt(t,e,n){var i,a,o,r=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;7>i;++i)o=c([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(o,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(o,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(o,"").toLocaleLowerCase();return n?"dddd"===e?(a=ki.call(this._weekdaysParse,r),-1!==a?a:null):"ddd"===e?(a=ki.call(this._shortWeekdaysParse,r),-1!==a?a:null):(a=ki.call(this._minWeekdaysParse,r),-1!==a?a:null):"dddd"===e?(a=ki.call(this._weekdaysParse,r),-1!==a?a:(a=ki.call(this._shortWeekdaysParse,r),-1!==a?a:(a=ki.call(this._minWeekdaysParse,r),-1!==a?a:null))):"ddd"===e?(a=ki.call(this._shortWeekdaysParse,r),-1!==a?a:(a=ki.call(this._weekdaysParse,r),-1!==a?a:(a=ki.call(this._minWeekdaysParse,r),-1!==a?a:null))):(a=ki.call(this._minWeekdaysParse,r),-1!==a?a:(a=ki.call(this._weekdaysParse,r),-1!==a?a:(a=ki.call(this._shortWeekdaysParse,r),-1!==a?a:null)))}function Lt(t,e,n){var i,a,o;if(this._weekdaysParseExact)return Wt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;7>i;i++){if(a=c([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".",".?")+"$","i")),this._weekdaysParse[i]||(o="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(o.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}}function Vt(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=Ft(t,this.localeData()),this.add(t-e,"d")):e}function Bt(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function Yt(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=It(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7}function zt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Et.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(d(this,"_weekdaysRegex")||(this._weekdaysRegex=ba),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)}function Ht(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Et.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(d(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ya),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Nt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Et.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(d(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=xa),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Et(){function t(t,e){return e.length-t.length}var e,n,i,a,o,r=[],s=[],l=[],d=[];for(e=0;7>e;e++)n=c([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),o=this.weekdays(n,""),r.push(i),s.push(a),l.push(o),d.push(i),d.push(a),d.push(o);for(r.sort(t),s.sort(t),l.sort(t),d.sort(t),e=0;7>e;e++)s[e]=et(s[e]),l[e]=et(l[e]),d[e]=et(d[e]);this._weekdaysRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Ut(){return this.hours()%12||12}function jt(){return this.hours()||24}function Gt(t,e){q(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function qt(t,e){return e._meridiemParse}function Zt(t){return"p"===(t+"").toLowerCase().charAt(0)}function Jt(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function Xt(t){return t?t.toLowerCase().replace("_","-"):t}function Kt(t){for(var e,n,i,a,o=0;o0;){if(i=Qt(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&S(a,n,!0)>=e-1)break;e--}o++}return null}function Qt(t){var i=null;if(!Ma[t]&&"undefined"!=typeof n&&n&&n.exports)try{i=ka._abbr,e("./locale/"+t),$t(i)}catch(a){}return Ma[t]}function $t(t,e){var n;return t&&(n=p(e)?ne(t):te(t,e),n&&(ka=n)),ka._abbr}function te(t,e){if(null!==e){var n=_a;return e.abbr=t,null!=Ma[t]?(M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),n=Ma[t]._config):null!=e.parentLocale&&(null!=Ma[e.parentLocale]?n=Ma[e.parentLocale]._config:M("parentLocaleUndefined","specified parentLocale is not defined yet. See http://momentjs.com/guides/#/warnings/parent-locale/")),Ma[t]=new P(T(n,e)),$t(t),Ma[t]}return delete Ma[t],null}function ee(t,e){if(null!=e){var n,i=_a;null!=Ma[t]&&(i=Ma[t]._config),e=T(i,e),n=new P(e),n.parentLocale=Ma[t],Ma[t]=n,$t(t)}else null!=Ma[t]&&(null!=Ma[t].parentLocale?Ma[t]=Ma[t].parentLocale:null!=Ma[t]&&delete Ma[t]);return Ma[t]}function ne(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return ka;if(!a(t)){if(e=Qt(t))return e;t=[t]}return Kt(t)}function ie(){return xi(Ma)}function ae(t){var e,n=t._a;return n&&-2===f(t).overflow&&(e=n[ta]<0||n[ta]>11?ta:n[ea]<1||n[ea]>ot(n[$i],n[ta])?ea:n[na]<0||n[na]>24||24===n[na]&&(0!==n[ia]||0!==n[aa]||0!==n[oa])?na:n[ia]<0||n[ia]>59?ia:n[aa]<0||n[aa]>59?aa:n[oa]<0||n[oa]>999?oa:-1,f(t)._overflowDayOfYear&&($i>e||e>ea)&&(e=ea),f(t)._overflowWeeks&&-1===e&&(e=ra),f(t)._overflowWeekday&&-1===e&&(e=sa),f(t).overflow=e),t}function oe(t){var e,n,i,a,o,r,s=t._i,l=Da.exec(s)||Ca.exec(s);if(l){for(f(t).iso=!0,e=0,n=Pa.length;n>e;e++)if(Pa[e][1].exec(l[1])){a=Pa[e][0],i=Pa[e][2]!==!1;break}if(null==a)return void(t._isValid=!1);if(l[3]){for(e=0,n=Fa.length;n>e;e++)if(Fa[e][1].exec(l[3])){o=(l[2]||" ")+Fa[e][0];break}if(null==o)return void(t._isValid=!1)}if(!i&&null!=o)return void(t._isValid=!1);if(l[4]){if(!Ta.exec(l[4]))return void(t._isValid=!1);r="Z"}t._f=a+(o||"")+(r||""),ce(t)}else t._isValid=!1}function re(e){var n=Ia.exec(e._i);return null!==n?void(e._d=new Date(+n[1])):(oe(e),void(e._isValid===!1&&(delete e._isValid,t.createFromInputFallback(e))))}function se(t,e,n){return null!=t?t:null!=e?e:n}function le(e){var n=new Date(t.now());return e._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function de(t){var e,n,i,a,o=[];if(!t._d){for(i=le(t),t._w&&null==t._a[ea]&&null==t._a[ta]&&ue(t),t._dayOfYear&&(a=se(t._a[$i],i[$i]),t._dayOfYear>pt(a)&&(f(t)._overflowDayOfYear=!0),n=xt(a,0,t._dayOfYear),t._a[ta]=n.getUTCMonth(),t._a[ea]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=i[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[na]&&0===t._a[ia]&&0===t._a[aa]&&0===t._a[oa]&&(t._nextDay=!0,t._a[na]=0),t._d=(t._useUTC?xt:yt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[na]=24)}}function ue(t){var e,n,i,a,o,r,s,l;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,r=4,n=se(e.GG,t._a[$i],wt(ye(),1,4).year),i=se(e.W,1),a=se(e.E,1),(1>a||a>7)&&(l=!0)):(o=t._locale._week.dow,r=t._locale._week.doy,n=se(e.gg,t._a[$i],wt(ye(),o,r).year),i=se(e.w,1),null!=e.d?(a=e.d,(0>a||a>6)&&(l=!0)):null!=e.e?(a=e.e+o,(e.e<0||e.e>6)&&(l=!0)):a=o),1>i||i>_t(n,o,r)?f(t)._overflowWeeks=!0:null!=l?f(t)._overflowWeekday=!0:(s=St(n,i,a,o,r),t._a[$i]=s.year,t._dayOfYear=s.dayOfYear)}function ce(e){if(e._f===t.ISO_8601)return void oe(e);e._a=[],f(e).empty=!0;var n,i,a,o,r,s=""+e._i,l=s.length,d=0;for(a=K(e._f,e._locale).match(Fi)||[],n=0;n0&&f(e).unusedInput.push(r),s=s.slice(s.indexOf(i)+i.length),d+=i.length),Oi[o]?(i?f(e).empty=!1:f(e).unusedTokens.push(o),at(o,i,e)):e._strict&&!i&&f(e).unusedTokens.push(o);f(e).charsLeftOver=l-d,s.length>0&&f(e).unusedInput.push(s),e._a[na]<=12&&f(e).bigHour===!0&&e._a[na]>0&&(f(e).bigHour=void 0),f(e).parsedDateParts=e._a.slice(0),f(e).meridiem=e._meridiem,e._a[na]=he(e._locale,e._a[na],e._meridiem),de(e),ae(e)}function he(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(i=t.isPM(n),i&&12>e&&(e+=12),i||12!==e||(e=0),e):e}function fe(t){var e,n,i,a,o;if(0===t._f.length)return f(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;ao)&&(i=o,n=e));u(t,n||e)}function ge(t){if(!t._d){var e=B(t._i);t._a=l([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),de(t)}}function me(t){var e=new b(ae(pe(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function pe(t){var e=t._i,n=t._f;return t._locale=t._locale||ne(t._l),null===e||void 0===n&&""===e?m({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),y(e)?new b(ae(e)):(a(n)?fe(t):s(e)?t._d=e:n?ce(t):ve(t),g(t)||(t._d=null),t))}function ve(e){var n=e._i;void 0===n?e._d=new Date(t.now()):s(n)?e._d=new Date(n.valueOf()):"string"==typeof n?re(e):a(n)?(e._a=l(n.slice(0),function(t){return parseInt(t,10)}),de(e)):"object"==typeof n?ge(e):"number"==typeof n?e._d=new Date(n):t.createFromInputFallback(e)}function be(t,e,n,i,s){var l={};return"boolean"==typeof n&&(i=n,n=void 0),(o(t)&&r(t)||a(t)&&0===t.length)&&(t=void 0),l._isAMomentObject=!0,l._useUTC=l._isUTC=s,l._l=n,l._i=t,l._f=e,l._strict=i,me(l)}function ye(t,e,n,i){return be(t,e,n,i,!1)}function xe(t,e){var n,i;if(1===e.length&&a(e[0])&&(e=e[0]),!e.length)return ye();for(n=e[0],i=1;it?-1*Math.round(-1*t):Math.round(t)}function De(t,e){q(t,0,0,function(){var t=this.utcOffset(),n="+";return 0>t&&(t=-t,n="-"),n+G(~~(t/60),2)+e+G(~~t%60,2)})}function Ce(t,e){var n=(e||"").match(t)||[],i=n[n.length-1]||[],a=(i+"").match(Wa)||["-",0,0],o=+(60*a[1])+k(a[2]);return"+"===a[0]?o:-o}function Te(e,n){var i,a;return n._isUTC?(i=n.clone(),a=(y(e)||s(e)?e.valueOf():ye(e).valueOf())-i.valueOf(),i._d.setTime(i._d.valueOf()+a),t.updateOffset(i,!1),i):ye(e).local()}function Pe(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Fe(e,n){var i,a=this._offset||0;return this.isValid()?null!=e?("string"==typeof e?e=Ce(Zi,e):Math.abs(e)<16&&(e=60*e),!this._isUTC&&n&&(i=Pe(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,"m"),a!==e&&(!n||this._changeInProgress?Ge(this,He(e-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,t.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?a:Pe(this):null!=e?this:NaN}function Ie(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Ae(t){return this.utcOffset(0,t)}function Oe(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Pe(this),"m")),this}function Re(){if(this._tzm)this.utcOffset(this._tzm);else if("string"==typeof this._i){var t=Ce(qi,this._i);0===t?this.utcOffset(0,!0):this.utcOffset(Ce(qi,this._i))}return this}function We(t){return this.isValid()?(t=t?ye(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function Le(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ve(){if(!p(this._isDSTShifted))return this._isDSTShifted;var t={};if(v(t,this),t=pe(t),t._a){var e=t._isUTC?c(t._a):ye(t._a);this._isDSTShifted=this.isValid()&&S(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Be(){return this.isValid()?!this._isUTC:!1}function Ye(){return this.isValid()?this._isUTC:!1}function ze(){return this.isValid()?this._isUTC&&0===this._offset:!1}function He(t,e){var n,i,a,o=t,r=null;return _e(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(r=La.exec(t))?(n="-"===r[1]?-1:1,o={y:0,d:k(r[ea])*n,h:k(r[na])*n,m:k(r[ia])*n,s:k(r[aa])*n,ms:k(Me(1e3*r[oa]))*n}):(r=Va.exec(t))?(n="-"===r[1]?-1:1,o={y:Ne(r[2],n),M:Ne(r[3],n),w:Ne(r[4],n),d:Ne(r[5],n),h:Ne(r[6],n),m:Ne(r[7],n),s:Ne(r[8],n)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(a=Ue(ye(o.from),ye(o.to)),o={},o.ms=a.milliseconds,o.M=a.months),i=new we(o),_e(t)&&d(t,"_locale")&&(i._locale=t._locale),i}function Ne(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Ee(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function Ue(t,e){var n;return t.isValid()&&e.isValid()?(e=Te(e,t),t.isBefore(e)?n=Ee(t,e):(n=Ee(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function je(t,e){return function(n,i){var a,o;return null===i||isNaN(+i)||(M(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),o=n,n=i,i=o),n="string"==typeof n?+n:n,a=He(n,i),Ge(this,a,t),this}}function Ge(e,n,i,a){var o=n._milliseconds,r=Me(n._days),s=Me(n._months);e.isValid()&&(a=null==a?!0:a,o&&e._d.setTime(e._d.valueOf()+o*i),r&&E(e,"Date",N(e,"Date")+r*i),s&&ut(e,N(e,"Month")+s*i),a&&t.updateOffset(e,r||s))}function qe(t,e){var n=t.diff(e,"days",!0);return-6>n?"sameElse":-1>n?"lastWeek":0>n?"lastDay":1>n?"sameDay":2>n?"nextDay":7>n?"nextWeek":"sameElse"}function Ze(e,n){var i=e||ye(),a=Te(i,this).startOf("day"),o=t.calendarFormat(this,a)||"sameElse",r=n&&(D(n[o])?n[o].call(this,i):n[o]);return this.format(r||this.localeData().calendar(o,this,ye(i)))}function Je(){return new b(this)}function Xe(t,e){var n=y(t)?t:ye(t);return this.isValid()&&n.isValid()?(e=V(p(e)?"millisecond":e),"millisecond"===e?this.valueOf()>n.valueOf():n.valueOf()e-o?(n=t.clone().add(a-1,"months"),i=(e-o)/(o-n)):(n=t.clone().add(a+1,"months"),i=(e-o)/(n-o)),-(a+i)||0}function on(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function rn(){var t=this.clone().utc();return 0o&&(e=o),An.call(this,t,e,n,i,a))}function An(t,e,n,i,a){var o=St(t,e,n,i,a),r=xt(o.year,0,o.dayOfYear);return this.year(r.getUTCFullYear()),this.month(r.getUTCMonth()),this.date(r.getUTCDate()),this}function On(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Rn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function Wn(t,e){e[oa]=k(1e3*("0."+t))}function Ln(){return this._isUTC?"UTC":""}function Vn(){return this._isUTC?"Coordinated Universal Time":""}function Bn(t){return ye(1e3*t)}function Yn(){return ye.apply(null,arguments).parseZone()}function zn(t){return t}function Hn(t,e,n,i){var a=ne(),o=c().set(i,e);return a[n](o,t)}function Nn(t,e,n){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return Hn(t,e,n,"month");var i,a=[];for(i=0;12>i;i++)a[i]=Hn(t,i,n,"month");return a}function En(t,e,n,i){"boolean"==typeof t?("number"==typeof e&&(n=e,e=void 0),e=e||""):(e=t,n=e,t=!1,"number"==typeof e&&(n=e,e=void 0),e=e||"");var a=ne(),o=t?a._week.dow:0;if(null!=n)return Hn(e,(n+o)%7,i,"day");var r,s=[];for(r=0;7>r;r++)s[r]=Hn(e,(r+o)%7,i,"day");return s}function Un(t,e){return Nn(t,e,"months")}function jn(t,e){return Nn(t,e,"monthsShort")}function Gn(t,e,n){return En(t,e,n,"weekdays")}function qn(t,e,n){return En(t,e,n,"weekdaysShort")}function Zn(t,e,n){return En(t,e,n,"weekdaysMin")}function Jn(){var t=this._data;return this._milliseconds=Ja(this._milliseconds),this._days=Ja(this._days),this._months=Ja(this._months),t.milliseconds=Ja(t.milliseconds),t.seconds=Ja(t.seconds),t.minutes=Ja(t.minutes),t.hours=Ja(t.hours),t.months=Ja(t.months),t.years=Ja(t.years),this}function Xn(t,e,n,i){var a=He(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function Kn(t,e){return Xn(this,t,e,1)}function Qn(t,e){return Xn(this,t,e,-1)}function $n(t){return 0>t?Math.floor(t):Math.ceil(t)}function ti(){var t,e,n,i,a,o=this._milliseconds,r=this._days,s=this._months,l=this._data;return o>=0&&r>=0&&s>=0||0>=o&&0>=r&&0>=s||(o+=864e5*$n(ni(s)+r),r=0,s=0),l.milliseconds=o%1e3,t=x(o/1e3),l.seconds=t%60,e=x(t/60),l.minutes=e%60,n=x(e/60),l.hours=n%24,r+=x(n/24),a=x(ei(r)),s+=a,r-=$n(ni(a)),i=x(s/12),s%=12,l.days=r,l.months=s,l.years=i,this}function ei(t){return 4800*t/146097}function ni(t){return 146097*t/4800}function ii(t){var e,n,i=this._milliseconds;if(t=V(t),"month"===t||"year"===t)return e=this._days+i/864e5,n=this._months+ei(e),"month"===t?n:n/12;switch(e=this._days+Math.round(ni(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}}function ai(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*k(this._months/12)}function oi(t){return function(){return this.as(t)}}function ri(t){return t=V(t),this[t+"s"]()}function si(t){return function(){return this._data[t]}}function li(){return x(this.days()/7)}function di(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}function ui(t,e,n){var i=He(t).abs(),a=ho(i.as("s")),o=ho(i.as("m")),r=ho(i.as("h")),s=ho(i.as("d")),l=ho(i.as("M")),d=ho(i.as("y")),u=a=o&&["m"]||o=r&&["h"]||r=s&&["d"]||s=l&&["M"]||l=d&&["y"]||["yy",d];return u[2]=e,u[3]=+t>0,u[4]=n,di.apply(null,u)}function ci(t){return void 0===t?ho:"function"==typeof t?(ho=t,!0):!1}function hi(t,e){return void 0===fo[t]?!1:void 0===e?fo[t]:(fo[t]=e,!0)}function fi(t){var e=this.localeData(),n=ui(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function gi(){var t,e,n,i=go(this._milliseconds)/1e3,a=go(this._days),o=go(this._months);t=x(i/60),e=x(t/60),i%=60,t%=60,n=x(o/12),o%=12;var r=n,s=o,l=a,d=e,u=t,c=i,h=this.asSeconds();return h?(0>h?"-":"")+"P"+(r?r+"Y":"")+(s?s+"M":"")+(l?l+"D":"")+(d||u||c?"T":"")+(d?d+"H":"")+(u?u+"M":"")+(c?c+"S":""):"P0D"}var mi,pi;pi=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,i=0;n>i;i++)if(i in e&&t.call(this,e[i],i,e))return!0;return!1};var vi=t.momentProperties=[],bi=!1,yi={};t.suppressDeprecationWarnings=!1,t.deprecationHandler=null;var xi;xi=Object.keys?Object.keys:function(t){var e,n=[];for(e in t)d(t,e)&&n.push(e);return n};var ki,Si={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},wi={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},_i="Invalid date",Mi="%d",Di=/\d{1,2}/,Ci={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ti={},Pi={},Fi=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Ii=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ai={},Oi={},Ri=/\d/,Wi=/\d\d/,Li=/\d{3}/,Vi=/\d{4}/,Bi=/[+-]?\d{6}/,Yi=/\d\d?/,zi=/\d\d\d\d?/,Hi=/\d\d\d\d\d\d?/,Ni=/\d{1,3}/,Ei=/\d{1,4}/,Ui=/[+-]?\d{1,6}/,ji=/\d+/,Gi=/[+-]?\d+/,qi=/Z|[+-]\d\d:?\d\d/gi,Zi=/Z|[+-]\d\d(?::?\d\d)?/gi,Ji=/[+-]?\d+(\.\d{1,3})?/,Xi=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ki={},Qi={},$i=0,ta=1,ea=2,na=3,ia=4,aa=5,oa=6,ra=7,sa=8;ki=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e=t?""+t:"+"+t}),q(0,["YY",2],0,function(){return this.year()%100}),q(0,["YYYY",4],0,"year"),q(0,["YYYYY",5],0,"year"),q(0,["YYYYYY",6,!0],0,"year"),L("year","y"),Y("year",1),Q("Y",Gi),Q("YY",Yi,Wi),Q("YYYY",Ei,Vi),Q("YYYYY",Ui,Bi),Q("YYYYYY",Ui,Bi),nt(["YYYYY","YYYYYY"],$i),nt("YYYY",function(e,n){n[$i]=2===e.length?t.parseTwoDigitYear(e):k(e)}),nt("YY",function(e,n){n[$i]=t.parseTwoDigitYear(e)}),nt("Y",function(t,e){e[$i]=parseInt(t,10)}),t.parseTwoDigitYear=function(t){return k(t)+(k(t)>68?1900:2e3)};var fa=H("FullYear",!0);q("w",["ww",2],"wo","week"),q("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),Y("week",5),Y("isoWeek",5),Q("w",Yi),Q("ww",Yi,Wi),Q("W",Yi),Q("WW",Yi,Wi),it(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=k(t)});var ga={dow:0,doy:6};q("d",0,"do","day"),q("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),q("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),q("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),q("e",0,0,"weekday"),q("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),Y("day",11),Y("weekday",11),Y("isoWeekday",11),Q("d",Yi),Q("e",Yi),Q("E",Yi),Q("dd",function(t,e){return e.weekdaysMinRegex(t)}),Q("ddd",function(t,e){return e.weekdaysShortRegex(t)}),Q("dddd",function(t,e){return e.weekdaysRegex(t)}),it(["dd","ddd","dddd"],function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:f(n).invalidWeekday=t}),it(["d","e","E"],function(t,e,n,i){e[i]=k(t)});var ma="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),pa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),va="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ba=Xi,ya=Xi,xa=Xi;q("H",["HH",2],0,"hour"),q("h",["hh",2],0,Ut),q("k",["kk",2],0,jt),q("hmm",0,0,function(){return""+Ut.apply(this)+G(this.minutes(),2)}),q("hmmss",0,0,function(){return""+Ut.apply(this)+G(this.minutes(),2)+G(this.seconds(),2)}),q("Hmm",0,0,function(){return""+this.hours()+G(this.minutes(),2)}),q("Hmmss",0,0,function(){return""+this.hours()+G(this.minutes(),2)+G(this.seconds(),2)}),Gt("a",!0),Gt("A",!1),L("hour","h"),Y("hour",13),Q("a",qt),Q("A",qt),Q("H",Yi),Q("h",Yi),Q("HH",Yi,Wi),Q("hh",Yi,Wi),Q("hmm",zi),Q("hmmss",Hi),Q("Hmm",zi),Q("Hmmss",Hi),nt(["H","HH"],na),nt(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),nt(["h","hh"],function(t,e,n){e[na]=k(t),f(n).bigHour=!0}),nt("hmm",function(t,e,n){var i=t.length-2;e[na]=k(t.substr(0,i)),e[ia]=k(t.substr(i)),f(n).bigHour=!0}),nt("hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[na]=k(t.substr(0,i)),e[ia]=k(t.substr(i,2)),e[aa]=k(t.substr(a)),f(n).bigHour=!0}),nt("Hmm",function(t,e,n){var i=t.length-2;e[na]=k(t.substr(0,i)),e[ia]=k(t.substr(i))}),nt("Hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[na]=k(t.substr(0,i)),e[ia]=k(t.substr(i,2)),e[aa]=k(t.substr(a))});var ka,Sa=/[ap]\.?m?\.?/i,wa=H("Hours",!0),_a={calendar:Si,longDateFormat:wi,invalidDate:_i,ordinal:Mi,ordinalParse:Di,relativeTime:Ci,months:da,monthsShort:ua,week:ga,weekdays:ma,weekdaysMin:va,weekdaysShort:pa,meridiemParse:Sa},Ma={},Da=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Ca=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Ta=/Z|[+-]\d\d(?::?\d\d)?/,Pa=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Fa=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ia=/^\/?Date\((\-?\d+)/i; -t.createFromInputFallback=_("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),t.ISO_8601=function(){};var Aa=_("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var t=ye.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:m()}),Oa=_("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var t=ye.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:m()}),Ra=function(){return Date.now?Date.now():+new Date};De("Z",":"),De("ZZ",""),Q("Z",Zi),Q("ZZ",Zi),nt(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Ce(Zi,t)});var Wa=/([\+\-]|\d\d)/gi;t.updateOffset=function(){};var La=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Va=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;He.fn=we.prototype;var Ba=je(1,"add"),Ya=je(-1,"subtract");t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",t.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var za=_("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});q(0,["gg",2],0,function(){return this.weekYear()%100}),q(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Dn("gggg","weekYear"),Dn("ggggg","weekYear"),Dn("GGGG","isoWeekYear"),Dn("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),Y("weekYear",1),Y("isoWeekYear",1),Q("G",Gi),Q("g",Gi),Q("GG",Yi,Wi),Q("gg",Yi,Wi),Q("GGGG",Ei,Vi),Q("gggg",Ei,Vi),Q("GGGGG",Ui,Bi),Q("ggggg",Ui,Bi),it(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,i){e[i.substr(0,2)]=k(t)}),it(["gg","GG"],function(e,n,i,a){n[a]=t.parseTwoDigitYear(e)}),q("Q",0,"Qo","quarter"),L("quarter","Q"),Y("quarter",7),Q("Q",Ri),nt("Q",function(t,e){e[ta]=3*(k(t)-1)}),q("D",["DD",2],"Do","date"),L("date","D"),Y("date",9),Q("D",Yi),Q("DD",Yi,Wi),Q("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),nt(["D","DD"],ea),nt("Do",function(t,e){e[ea]=k(t.match(Yi)[0],10)});var Ha=H("Date",!0);q("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),Y("dayOfYear",4),Q("DDD",Ni),Q("DDDD",Li),nt(["DDD","DDDD"],function(t,e,n){n._dayOfYear=k(t)}),q("m",["mm",2],0,"minute"),L("minute","m"),Y("minute",14),Q("m",Yi),Q("mm",Yi,Wi),nt(["m","mm"],ia);var Na=H("Minutes",!1);q("s",["ss",2],0,"second"),L("second","s"),Y("second",15),Q("s",Yi),Q("ss",Yi,Wi),nt(["s","ss"],aa);var Ea=H("Seconds",!1);q("S",0,0,function(){return~~(this.millisecond()/100)}),q(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),q(0,["SSS",3],0,"millisecond"),q(0,["SSSS",4],0,function(){return 10*this.millisecond()}),q(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),q(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),q(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),q(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),q(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),L("millisecond","ms"),Y("millisecond",16),Q("S",Ni,Ri),Q("SS",Ni,Wi),Q("SSS",Ni,Li);var Ua;for(Ua="SSSS";Ua.length<=9;Ua+="S")Q(Ua,ji);for(Ua="S";Ua.length<=9;Ua+="S")nt(Ua,Wn);var ja=H("Milliseconds",!1);q("z",0,0,"zoneAbbr"),q("zz",0,0,"zoneName");var Ga=b.prototype;Ga.add=Ba,Ga.calendar=Ze,Ga.clone=Je,Ga.diff=nn,Ga.endOf=mn,Ga.format=sn,Ga.from=ln,Ga.fromNow=dn,Ga.to=un,Ga.toNow=cn,Ga.get=U,Ga.invalidAt=_n,Ga.isAfter=Xe,Ga.isBefore=Ke,Ga.isBetween=Qe,Ga.isSame=$e,Ga.isSameOrAfter=tn,Ga.isSameOrBefore=en,Ga.isValid=Sn,Ga.lang=za,Ga.locale=hn,Ga.localeData=fn,Ga.max=Oa,Ga.min=Aa,Ga.parsingFlags=wn,Ga.set=j,Ga.startOf=gn,Ga.subtract=Ya,Ga.toArray=yn,Ga.toObject=xn,Ga.toDate=bn,Ga.toISOString=rn,Ga.toJSON=kn,Ga.toString=on,Ga.unix=vn,Ga.valueOf=pn,Ga.creationData=Mn,Ga.year=fa,Ga.isLeapYear=bt,Ga.weekYear=Cn,Ga.isoWeekYear=Tn,Ga.quarter=Ga.quarters=On,Ga.month=ct,Ga.daysInMonth=ht,Ga.week=Ga.weeks=Tt,Ga.isoWeek=Ga.isoWeeks=Pt,Ga.weeksInYear=Fn,Ga.isoWeeksInYear=Pn,Ga.date=Ha,Ga.day=Ga.days=Vt,Ga.weekday=Bt,Ga.isoWeekday=Yt,Ga.dayOfYear=Rn,Ga.hour=Ga.hours=wa,Ga.minute=Ga.minutes=Na,Ga.second=Ga.seconds=Ea,Ga.millisecond=Ga.milliseconds=ja,Ga.utcOffset=Fe,Ga.utc=Ae,Ga.local=Oe,Ga.parseZone=Re,Ga.hasAlignedHourOffset=We,Ga.isDST=Le,Ga.isLocal=Be,Ga.isUtcOffset=Ye,Ga.isUtc=ze,Ga.isUTC=ze,Ga.zoneAbbr=Ln,Ga.zoneName=Vn,Ga.dates=_("dates accessor is deprecated. Use date instead.",Ha),Ga.months=_("months accessor is deprecated. Use month instead",ct),Ga.years=_("years accessor is deprecated. Use year instead",fa),Ga.zone=_("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Ie),Ga.isDSTShifted=_("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ve);var qa=Ga,Za=P.prototype;Za.calendar=F,Za.longDateFormat=I,Za.invalidDate=A,Za.ordinal=O,Za.preparse=zn,Za.postformat=zn,Za.relativeTime=R,Za.pastFuture=W,Za.set=C,Za.months=rt,Za.monthsShort=st,Za.monthsParse=dt,Za.monthsRegex=gt,Za.monthsShortRegex=ft,Za.week=Mt,Za.firstDayOfYear=Ct,Za.firstDayOfWeek=Dt,Za.weekdays=At,Za.weekdaysMin=Rt,Za.weekdaysShort=Ot,Za.weekdaysParse=Lt,Za.weekdaysRegex=zt,Za.weekdaysShortRegex=Ht,Za.weekdaysMinRegex=Nt,Za.isPM=Zt,Za.meridiem=Jt,$t("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===k(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),t.lang=_("moment.lang is deprecated. Use moment.locale instead.",$t),t.langData=_("moment.langData is deprecated. Use moment.localeData instead.",ne);var Ja=Math.abs,Xa=oi("ms"),Ka=oi("s"),Qa=oi("m"),$a=oi("h"),to=oi("d"),eo=oi("w"),no=oi("M"),io=oi("y"),ao=si("milliseconds"),oo=si("seconds"),ro=si("minutes"),so=si("hours"),lo=si("days"),uo=si("months"),co=si("years"),ho=Math.round,fo={s:45,m:45,h:22,d:26,M:11},go=Math.abs,mo=we.prototype;mo.abs=Jn,mo.add=Kn,mo.subtract=Qn,mo.as=ii,mo.asMilliseconds=Xa,mo.asSeconds=Ka,mo.asMinutes=Qa,mo.asHours=$a,mo.asDays=to,mo.asWeeks=eo,mo.asMonths=no,mo.asYears=io,mo.valueOf=ai,mo._bubble=ti,mo.get=ri,mo.milliseconds=ao,mo.seconds=oo,mo.minutes=ro,mo.hours=so,mo.days=lo,mo.weeks=li,mo.months=uo,mo.years=co,mo.humanize=fi,mo.toISOString=gi,mo.toString=gi,mo.toJSON=gi,mo.locale=hn,mo.localeData=fn,mo.toIsoString=_("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",gi),mo.lang=za,q("X",0,0,"unix"),q("x",0,0,"valueOf"),Q("x",Gi),Q("X",Ji),nt("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),nt("x",function(t,e,n){n._d=new Date(k(t))}),t.version="2.15.1",i(ye),t.fn=qa,t.min=ke,t.max=Se,t.now=Ra,t.utc=c,t.unix=Bn,t.months=Un,t.isDate=s,t.locale=$t,t.invalid=m,t.duration=He,t.isMoment=y,t.weekdays=Gn,t.parseZone=Yn,t.localeData=ne,t.isDuration=_e,t.monthsShort=jn,t.weekdaysMin=Zn,t.defineLocale=te,t.updateLocale=ee,t.locales=ie,t.weekdaysShort=qn,t.normalizeUnits=V,t.relativeTimeRounding=ci,t.relativeTimeThreshold=hi,t.calendarFormat=qe,t.prototype=qa;var po=t;return po})},{}],7:[function(t,e,n){var i=t(27)();t(26)(i),t(22)(i),t(25)(i),t(21)(i),t(23)(i),t(24)(i),t(28)(i),t(32)(i),t(30)(i),t(31)(i),t(33)(i),t(29)(i),t(34)(i),t(35)(i),t(36)(i),t(37)(i),t(38)(i),t(41)(i),t(39)(i),t(40)(i),t(42)(i),t(43)(i),t(44)(i),t(15)(i),t(16)(i),t(17)(i),t(18)(i),t(19)(i),t(20)(i),t(8)(i),t(9)(i),t(10)(i),t(11)(i),t(12)(i),t(13)(i),t(14)(i),window.Chart=e.exports=i},{10:10,11:11,12:12,13:13,14:14,15:15,16:16,17:17,18:18,19:19,20:20,21:21,22:22,23:23,24:24,25:25,26:26,27:27,28:28,29:29,30:30,31:31,32:32,33:33,34:34,35:35,36:36,37:37,38:38,39:39,40:40,41:41,42:42,43:43,44:44,8:8,9:9}],8:[function(t,e,n){"use strict";e.exports=function(t){t.Bar=function(e,n){return n.type="bar",new t(e,n)}}},{}],9:[function(t,e,n){"use strict";e.exports=function(t){t.Bubble=function(e,n){return n.type="bubble",new t(e,n)}}},{}],10:[function(t,e,n){"use strict";e.exports=function(t){t.Doughnut=function(e,n){return n.type="doughnut",new t(e,n)}}},{}],11:[function(t,e,n){"use strict";e.exports=function(t){t.Line=function(e,n){return n.type="line",new t(e,n)}}},{}],12:[function(t,e,n){"use strict";e.exports=function(t){t.PolarArea=function(e,n){return n.type="polarArea",new t(e,n)}}},{}],13:[function(t,e,n){"use strict";e.exports=function(t){t.Radar=function(e,n){return n.options=t.helpers.configMerge({aspectRatio:1},n.options),n.type="radar",new t(e,n)}}},{}],14:[function(t,e,n){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,n){return n.type="scatter",new t(e,n)}}},{}],15:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({dataElementType:t.elements.Rectangle,initialize:function(e,n){t.DatasetController.prototype.initialize.call(this,e,n),this.getMeta().bar=!0},getBarCount:function(){var t=this,n=0;return e.each(t.chart.data.datasets,function(e,i){var a=t.chart.getDatasetMeta(i);a.bar&&t.chart.isDatasetVisible(i)&&++n},t),n},update:function(t){var n=this;e.each(n.getMeta().data,function(e,i){n.updateElement(e,i,t)},n)},updateElement:function(t,n,i){var a=this,o=a.getMeta(),r=a.getScaleForId(o.xAxisID),s=a.getScaleForId(o.yAxisID),l=s.getBasePixel(),d=a.chart.options.elements.rectangle,u=t.custom||{},c=a.getDataset();e.extend(t,{_xScale:r,_yScale:s,_datasetIndex:a.index,_index:n,_model:{x:a.calculateBarX(n,a.index),y:i?l:a.calculateBarY(n,a.index),label:a.chart.data.labels[n],datasetLabel:c.label,base:i?l:a.calculateBarBase(a.index,n),width:a.calculateBarWidth(n),backgroundColor:u.backgroundColor?u.backgroundColor:e.getValueAtIndexOrDefault(c.backgroundColor,n,d.backgroundColor),borderSkipped:u.borderSkipped?u.borderSkipped:d.borderSkipped,borderColor:u.borderColor?u.borderColor:e.getValueAtIndexOrDefault(c.borderColor,n,d.borderColor),borderWidth:u.borderWidth?u.borderWidth:e.getValueAtIndexOrDefault(c.borderWidth,n,d.borderWidth)}}),t.pivot()},calculateBarBase:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),o=0;if(a.options.stacked){for(var r=n.chart,s=r.data.datasets,l=Number(s[t].data[e]),d=0;t>d;d++){var u=s[d],c=r.getDatasetMeta(d);if(c.bar&&c.yAxisID===a.id&&r.isDatasetVisible(d)){var h=Number(u.data[e]);o+=0>l?Math.min(h,0):Math.max(h,0)}}return a.getPixelForValue(o)}return a.getBasePixel()},getRuler:function(t){var e,n=this,i=n.getMeta(),a=n.getScaleForId(i.xAxisID),o=n.getBarCount();e="category"===a.options.type?a.getPixelForTick(t+1)-a.getPixelForTick(t):a.width/a.ticks.length;var r=e*a.options.categoryPercentage,s=(e-e*a.options.categoryPercentage)/2,l=r/o;if(a.ticks.length!==n.chart.data.labels.length){var d=a.ticks.length/n.chart.data.labels.length;l*=d}var u=l*a.options.barPercentage,c=l-l*a.options.barPercentage;return{datasetCount:o,tickWidth:e,categoryWidth:r,categorySpacing:s,fullBarWidth:l,barWidth:u,barSpacing:c}},calculateBarWidth:function(t){var e=this.getScaleForId(this.getMeta().xAxisID);if(e.options.barThickness)return e.options.barThickness;var n=this.getRuler(t);return e.options.stacked?n.categoryWidth:n.barWidth},getBarIndex:function(t){var e,n,i=0;for(n=0;t>n;++n)e=this.chart.getDatasetMeta(n),e.bar&&this.chart.isDatasetVisible(n)&&++i;return i},calculateBarX:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.xAxisID),o=n.getBarIndex(e),r=n.getRuler(t),s=a.getPixelForValue(null,t,e,n.chart.isCombo);return s-=n.chart.isCombo?r.tickWidth/2:0,a.options.stacked?s+r.categoryWidth/2+r.categorySpacing:s+r.barWidth/2+r.categorySpacing+r.barWidth*o+r.barSpacing/2+r.barSpacing*o},calculateBarY:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),o=Number(n.getDataset().data[t]);if(a.options.stacked){for(var r=0,s=0,l=0;e>l;l++){var d=n.chart.data.datasets[l],u=n.chart.getDatasetMeta(l);if(u.bar&&u.yAxisID===a.id&&n.chart.isDatasetVisible(l)){var c=Number(d.data[t]);0>c?s+=c||0:r+=c||0}}return 0>o?a.getPixelForValue(s+o):a.getPixelForValue(r+o)}return a.getPixelForValue(o)},draw:function(t){var n=this,i=t||1;e.each(n.getMeta().data,function(t,e){var a=n.getDataset().data[e];null===a||void 0===a||isNaN(a)||t.transition(i).draw()},n)},setHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t._index,a=t.custom||{},o=t._model;o.backgroundColor=a.hoverBackgroundColor?a.hoverBackgroundColor:e.getValueAtIndexOrDefault(n.hoverBackgroundColor,i,e.getHoverColor(o.backgroundColor)),o.borderColor=a.hoverBorderColor?a.hoverBorderColor:e.getValueAtIndexOrDefault(n.hoverBorderColor,i,e.getHoverColor(o.borderColor)),o.borderWidth=a.hoverBorderWidth?a.hoverBorderWidth:e.getValueAtIndexOrDefault(n.hoverBorderWidth,i,o.borderWidth)},removeHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t._index,a=t.custom||{},o=t._model,r=this.chart.options.elements.rectangle;o.backgroundColor=a.backgroundColor?a.backgroundColor:e.getValueAtIndexOrDefault(n.backgroundColor,i,r.backgroundColor),o.borderColor=a.borderColor?a.borderColor:e.getValueAtIndexOrDefault(n.borderColor,i,r.borderColor),o.borderWidth=a.borderWidth?a.borderWidth:e.getValueAtIndexOrDefault(n.borderWidth,i,r.borderWidth)}}),t.defaults.horizontalBar={hover:{mode:"label"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{position:"left",type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}]},elements:{rectangle:{borderSkipped:"left"}},tooltips:{callbacks:{title:function(t,e){var n="";return t.length>0&&(t[0].yLabel?n=t[0].yLabel:e.labels.length>0&&t[0].indexc;c++)e.lineTo.apply(e,t(c));e.fill(),n.borderWidth&&e.stroke()},inRange:function(t,e){var n=this._view,i=!1;return n&&(i=n.x=n.y-n.height/2&&e<=n.y+n.height/2&&t>=n.x&&t<=n.base:e>=n.y-n.height/2&&e<=n.y+n.height/2&&t>=n.base&&t<=n.x),i}}),t.pivot()},calculateBarBase:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.xAxisID),o=0;if(a.options.stacked){for(var r=n.chart,s=r.data.datasets,l=Number(s[t].data[e]),d=0;t>d;d++){var u=s[d],c=r.getDatasetMeta(d);if(c.bar&&c.xAxisID===a.id&&r.isDatasetVisible(d)){var h=Number(u.data[e]);o+=0>l?Math.min(h,0):Math.max(h,0)}}return a.getPixelForValue(o)}return a.getBasePixel()},getRuler:function(t){var e,n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),o=n.getBarCount();e="category"===a.options.type?a.getPixelForTick(t+1)-a.getPixelForTick(t):a.width/a.ticks.length;var r=e*a.options.categoryPercentage,s=(e-e*a.options.categoryPercentage)/2,l=r/o;if(a.ticks.length!==n.chart.data.labels.length){var d=a.ticks.length/n.chart.data.labels.length;l*=d}var u=l*a.options.barPercentage,c=l-l*a.options.barPercentage;return{datasetCount:o,tickHeight:e,categoryHeight:r,categorySpacing:s,fullBarHeight:l,barHeight:u,barSpacing:c}},calculateBarHeight:function(t){var e=this,n=e.getScaleForId(e.getMeta().yAxisID);if(n.options.barThickness)return n.options.barThickness;var i=e.getRuler(t);return n.options.stacked?i.categoryHeight:i.barHeight},calculateBarX:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.xAxisID),o=Number(n.getDataset().data[t]);if(a.options.stacked){for(var r=0,s=0,l=0;e>l;l++){var d=n.chart.data.datasets[l],u=n.chart.getDatasetMeta(l);if(u.bar&&u.xAxisID===a.id&&n.chart.isDatasetVisible(l)){var c=Number(d.data[t]);0>c?s+=c||0:r+=c||0}}return 0>o?a.getPixelForValue(s+o):a.getPixelForValue(r+o)}return a.getPixelForValue(o)},calculateBarY:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),o=n.getBarIndex(e),r=n.getRuler(t),s=a.getPixelForValue(null,t,e,n.chart.isCombo);return s-=n.chart.isCombo?r.tickHeight/2:0,a.options.stacked?s+r.categoryHeight/2+r.categorySpacing:s+r.barHeight/2+r.categorySpacing+r.barHeight*o+r.barSpacing/2+r.barSpacing*o}})}},{}],16:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.datasets[t.datasetIndex].label||"",i=e.datasets[t.datasetIndex].data[t.index];return n+": ("+i.x+", "+i.y+", "+i.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({dataElementType:t.elements.Point,update:function(t){var n=this,i=n.getMeta(),a=i.data;e.each(a,function(e,i){n.updateElement(e,i,t)})},updateElement:function(n,i,a){var o=this,r=o.getMeta(),s=o.getScaleForId(r.xAxisID),l=o.getScaleForId(r.yAxisID),d=n.custom||{},u=o.getDataset(),c=u.data[i],h=o.chart.options.elements.point,f=o.index;e.extend(n,{_xScale:s,_yScale:l,_datasetIndex:f,_index:i,_model:{x:a?s.getPixelForDecimal(.5):s.getPixelForValue("object"==typeof c?c:NaN,i,f,o.chart.isCombo),y:a?l.getBasePixel():l.getPixelForValue(c,i,f),radius:a?0:d.radius?d.radius:o.getRadius(c),hitRadius:d.hitRadius?d.hitRadius:e.getValueAtIndexOrDefault(u.hitRadius,i,h.hitRadius)}}),t.DatasetController.prototype.removeHoverStyle.call(o,n,h);var g=n._model;g.skip=d.skip?d.skip:isNaN(g.x)||isNaN(g.y),n.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},setHoverStyle:function(n){var i=this;t.DatasetController.prototype.setHoverStyle.call(i,n);var a=i.chart.data.datasets[n._datasetIndex],o=n._index,r=n.custom||{},s=n._model;s.radius=r.hoverRadius?r.hoverRadius:e.getValueAtIndexOrDefault(a.hoverRadius,o,i.chart.options.elements.point.hoverRadius)+i.getRadius(a.data[o])},removeHoverStyle:function(e){var n=this;t.DatasetController.prototype.removeHoverStyle.call(n,e,n.chart.options.elements.point);var i=n.chart.data.datasets[e._datasetIndex].data[e._index],a=e.custom||{},o=e._model;o.radius=a.radius?a.radius:n.getRadius(i)}})}},{}],17:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=t.defaults;n.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];e.push('
    ');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var o=t.getDatasetMeta(0),r=n.datasets[0],s=o.data[a],l=s&&s.custom||{},d=e.getValueAtIndexOrDefault,u=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:d(r.backgroundColor,a,u.backgroundColor),h=l.borderColor?l.borderColor:d(r.borderColor,a,u.borderColor),f=l.borderWidth?l.borderWidth:d(r.borderWidth,a,u.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(r.data[a])||o.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;i>n;++n)a=r.getDatasetMeta(n),a.data[o]&&(a.data[o].hidden=!a.data[o].hidden);r.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},n.pie=e.clone(n.doughnut),e.extend(n.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({dataElementType:t.elements.Arc,linkScales:e.noop,getRingIndex:function(t){for(var e=0,n=0;t>n;++n)this.chart.isDatasetVisible(n)&&++e;return e},update:function(t){var n=this,i=n.chart,a=i.chartArea,o=i.options,r=o.elements.arc,s=a.right-a.left-r.borderWidth,l=a.bottom-a.top-r.borderWidth,d=Math.min(s,l),u={x:0,y:0},c=n.getMeta(),h=o.cutoutPercentage,f=o.circumference;if(f<2*Math.PI){var g=o.rotation%(2*Math.PI);g+=2*Math.PI*(g>=Math.PI?-1:g<-Math.PI?1:0);var m=g+f,p={x:Math.cos(g),y:Math.sin(g)},v={x:Math.cos(m),y:Math.sin(m)},b=0>=g&&m>=0||g<=2*Math.PI&&2*Math.PI<=m,y=g<=.5*Math.PI&&.5*Math.PI<=m||g<=2.5*Math.PI&&2.5*Math.PI<=m,x=g<=-Math.PI&&-Math.PI<=m||g<=Math.PI&&Math.PI<=m,k=g<=.5*-Math.PI&&.5*-Math.PI<=m||g<=1.5*Math.PI&&1.5*Math.PI<=m,S=h/100,w={x:x?-1:Math.min(p.x*(p.x<0?1:S),v.x*(v.x<0?1:S)),y:k?-1:Math.min(p.y*(p.y<0?1:S),v.y*(v.y<0?1:S))},_={x:b?1:Math.max(p.x*(p.x>0?1:S),v.x*(v.x>0?1:S)),y:y?1:Math.max(p.y*(p.y>0?1:S),v.y*(v.y>0?1:S))},M={width:.5*(_.x-w.x),height:.5*(_.y-w.y)};d=Math.min(s/M.width,l/M.height),u={x:(_.x+w.x)*-.5,y:(_.y+w.y)*-.5}}i.borderWidth=n.getMaxBorderWidth(c.data),i.outerRadius=Math.max((d-i.borderWidth)/2,0),i.innerRadius=Math.max(h?i.outerRadius/100*h:1,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),i.offsetX=u.x*i.outerRadius,i.offsetY=u.y*i.outerRadius,c.total=n.calculateTotal(),n.outerRadius=i.outerRadius-i.radiusLength*n.getRingIndex(n.index),n.innerRadius=n.outerRadius-i.radiusLength,e.each(c.data,function(e,i){n.updateElement(e,i,t)})},updateElement:function(t,n,i){var a=this,o=a.chart,r=o.chartArea,s=o.options,l=s.animation,d=(r.left+r.right)/2,u=(r.top+r.bottom)/2,c=s.rotation,h=s.rotation,f=a.getDataset(),g=i&&l.animateRotate?0:t.hidden?0:a.calculateCircumference(f.data[n])*(s.circumference/(2*Math.PI)),m=i&&l.animateScale?0:a.innerRadius,p=i&&l.animateScale?0:a.outerRadius,v=e.getValueAtIndexOrDefault;e.extend(t,{_datasetIndex:a.index,_index:n,_model:{x:d+o.offsetX,y:u+o.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:p,innerRadius:m,label:v(f.label,n,o.data.labels[n])}});var b=t._model;this.removeHoverStyle(t),i&&l.animateRotate||(0===n?b.startAngle=s.rotation:b.startAngle=a.getMeta().data[n-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,n=this.getDataset(),i=this.getMeta(),a=0;return e.each(i.data,function(e,i){t=n.data[i],isNaN(t)||e.hidden||(a+=Math.abs(t))}),a},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0},getMaxBorderWidth:function(t){for(var e,n,i=0,a=this.index,o=t.length,r=0;o>r;r++)e=t[r]._model?t[r]._model.borderWidth:0,n=t[r]._chart?t[r]._chart.config.data.datasets[a].hoverBorderWidth:0,i=e>i?e:i,i=n>i?n:i;return i}})}},{}],18:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){return n.getValueOrDefault(t.showLine,e.showLines)}var n=t.helpers;t.defaults.line={showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,addElementAndReset:function(n){var i=this,a=i.chart.options,o=i.getMeta();t.DatasetController.prototype.addElementAndReset.call(i,n),e(i.getDataset(),a)&&0!==o.dataset._model.tension&&i.updateBezierControlPoints()},update:function(t){var i,a,o,r=this,s=r.getMeta(),l=s.dataset,d=s.data||[],u=r.chart.options,c=u.elements.line,h=r.getScaleForId(s.yAxisID),f=r.getDataset(),g=e(f,u);for(g&&(o=l.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),l._scale=h,l._datasetIndex=r.index,l._children=d,l._model={spanGaps:f.spanGaps?f.spanGaps:u.spanGaps,tension:o.tension?o.tension:n.getValueOrDefault(f.lineTension,c.tension),backgroundColor:o.backgroundColor?o.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:o.borderWidth?o.borderWidth:f.borderWidth||c.borderWidth,borderColor:o.borderColor?o.borderColor:f.borderColor||c.borderColor,borderCapStyle:o.borderCapStyle?o.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:o.borderDash?o.borderDash:f.borderDash||c.borderDash,borderDashOffset:o.borderDashOffset?o.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:o.borderJoinStyle?o.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:o.fill?o.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:o.steppedLine?o.steppedLine:n.getValueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:o.cubicInterpolationMode?o.cubicInterpolationMode:n.getValueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode),scaleTop:h.top,scaleBottom:h.bottom,scaleZero:h.getBasePixel()},l.pivot()),i=0,a=d.length;a>i;++i)r.updateElement(d[i],i,t);for(g&&0!==l._model.tension&&r.updateBezierControlPoints(),i=0,a=d.length;a>i;++i)d[i].pivot()},getPointBackgroundColor:function(t,e){var i=this.chart.options.elements.point.backgroundColor,a=this.getDataset(),o=t.custom||{};return o.backgroundColor?i=o.backgroundColor:a.pointBackgroundColor?i=n.getValueAtIndexOrDefault(a.pointBackgroundColor,e,i):a.backgroundColor&&(i=a.backgroundColor),i},getPointBorderColor:function(t,e){var i=this.chart.options.elements.point.borderColor,a=this.getDataset(),o=t.custom||{};return o.borderColor?i=o.borderColor:a.pointBorderColor?i=n.getValueAtIndexOrDefault(a.pointBorderColor,e,i):a.borderColor&&(i=a.borderColor),i},getPointBorderWidth:function(t,e){var i=this.chart.options.elements.point.borderWidth,a=this.getDataset(),o=t.custom||{};return o.borderWidth?i=o.borderWidth:a.pointBorderWidth?i=n.getValueAtIndexOrDefault(a.pointBorderWidth,e,i):a.borderWidth&&(i=a.borderWidth),i},updateElement:function(t,e,i){var a,o,r=this,s=r.getMeta(),l=t.custom||{},d=r.getDataset(),u=r.index,c=d.data[e],h=r.getScaleForId(s.yAxisID),f=r.getScaleForId(s.xAxisID),g=r.chart.options.elements.point,m=r.chart.data.labels||[],p=1===m.length||1===d.data.length||r.chart.isCombo;void 0!==d.radius&&void 0===d.pointRadius&&(d.pointRadius=d.radius),void 0!==d.hitRadius&&void 0===d.pointHitRadius&&(d.pointHitRadius=d.hitRadius),a=f.getPixelForValue("object"==typeof c?c:NaN,e,u,p),o=i?h.getBasePixel():r.calculatePointY(c,e,u),t._xScale=f,t._yScale=h,t._datasetIndex=u,t._index=e,t._model={x:a,y:o,skip:l.skip||isNaN(a)||isNaN(o),radius:l.radius||n.getValueAtIndexOrDefault(d.pointRadius,e,g.radius),pointStyle:l.pointStyle||n.getValueAtIndexOrDefault(d.pointStyle,e,g.pointStyle),backgroundColor:r.getPointBackgroundColor(t,e),borderColor:r.getPointBorderColor(t,e),borderWidth:r.getPointBorderWidth(t,e),tension:s.dataset._model?s.dataset._model.tension:0,steppedLine:s.dataset._model?s.dataset._model.steppedLine:!1,hitRadius:l.hitRadius||n.getValueAtIndexOrDefault(d.pointHitRadius,e,g.hitRadius)}},calculatePointY:function(t,e,n){var i,a,o,r=this,s=r.chart,l=r.getMeta(),d=r.getScaleForId(l.yAxisID),u=0,c=0;if(d.options.stacked){for(i=0;n>i;i++)if(a=s.data.datasets[i],o=s.getDatasetMeta(i),"line"===o.type&&o.yAxisID===d.id&&s.isDatasetVisible(i)){var h=Number(d.getRightValue(a.data[e]));0>h?c+=h||0:u+=h||0}var f=Number(d.getRightValue(t));return 0>f?d.getPixelForValue(c+f):d.getPixelForValue(u+f)}return d.getPixelForValue(t)},updateBezierControlPoints:function(){function t(t,e,n){return Math.max(Math.min(t,n),e)}var e,i,a,o,r,s=this,l=s.getMeta(),d=s.chart.chartArea,u=l.data||[];if(l.dataset._model.spanGaps&&(u=u.filter(function(t){return!t._model.skip})),"monotone"===l.dataset._model.cubicInterpolationMode)n.splineCurveMonotone(u);else for(e=0,i=u.length;i>e;++e)a=u[e],o=a._model,r=n.splineCurve(n.previousItem(u,e)._model,o,n.nextItem(u,e)._model,l.dataset._model.tension),o.controlPointPreviousX=r.previous.x,o.controlPointPreviousY=r.previous.y,o.controlPointNextX=r.next.x,o.controlPointNextY=r.next.y;if(s.chart.options.elements.line.capBezierPoints)for(e=0,i=u.length;i>e;++e)o=u[e]._model,o.controlPointPreviousX=t(o.controlPointPreviousX,d.left,d.right),o.controlPointPreviousY=t(o.controlPointPreviousY,d.top,d.bottom),o.controlPointNextX=t(o.controlPointNextX,d.left,d.right),o.controlPointNextY=t(o.controlPointNextY,d.top,d.bottom)},draw:function(t){var n,i,a=this,o=a.getMeta(),r=o.data||[],s=t||1;for(n=0,i=r.length;i>n;++n)r[n].transition(s);for(e(a.getDataset(),a.chart.options)&&o.dataset.transition(s).draw(),n=0,i=r.length;i>n;++n)r[n].draw()},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],i=t._index,a=t.custom||{},o=t._model;o.radius=a.hoverRadius||n.getValueAtIndexOrDefault(e.pointHoverRadius,i,this.chart.options.elements.point.hoverRadius),o.backgroundColor=a.hoverBackgroundColor||n.getValueAtIndexOrDefault(e.pointHoverBackgroundColor,i,n.getHoverColor(o.backgroundColor)),o.borderColor=a.hoverBorderColor||n.getValueAtIndexOrDefault(e.pointHoverBorderColor,i,n.getHoverColor(o.borderColor)),o.borderWidth=a.hoverBorderWidth||n.getValueAtIndexOrDefault(e.pointHoverBorderWidth,i,o.borderWidth)},removeHoverStyle:function(t){var e=this,i=e.chart.data.datasets[t._datasetIndex],a=t._index,o=t.custom||{},r=t._model;void 0!==i.radius&&void 0===i.pointRadius&&(i.pointRadius=i.radius),r.radius=o.radius||n.getValueAtIndexOrDefault(i.pointRadius,a,e.chart.options.elements.point.radius),r.backgroundColor=e.getPointBackgroundColor(t,a),r.borderColor=e.getPointBorderColor(t,a),r.borderWidth=e.getPointBorderWidth(t,a)}})}},{}],19:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0,ticks:{beginAtZero:!0}},animation:{animateRotate:!0,animateScale:!0},startAngle:-.5*Math.PI,aspectRatio:1,legendCallback:function(t){var e=[];e.push('
    ');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var o=t.getDatasetMeta(0),r=n.datasets[0],s=o.data[a],l=s.custom||{},d=e.getValueAtIndexOrDefault,u=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:d(r.backgroundColor,a,u.backgroundColor),h=l.borderColor?l.borderColor:d(r.borderColor,a,u.borderColor),f=l.borderWidth?l.borderWidth:d(r.borderWidth,a,u.borderWidth);return{ -text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(r.data[a])||o.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;i>n;++n)a=r.getDatasetMeta(n),a.data[o].hidden=!a.data[o].hidden;r.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({dataElementType:t.elements.Arc,linkScales:e.noop,update:function(t){var n=this,i=n.chart,a=i.chartArea,o=n.getMeta(),r=i.options,s=r.elements.arc,l=Math.min(a.right-a.left,a.bottom-a.top);i.outerRadius=Math.max((l-s.borderWidth/2)/2,0),i.innerRadius=Math.max(r.cutoutPercentage?i.outerRadius/100*r.cutoutPercentage:1,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),n.outerRadius=i.outerRadius-i.radiusLength*n.index,n.innerRadius=n.outerRadius-i.radiusLength,o.count=n.countVisibleElements(),e.each(o.data,function(e,i){n.updateElement(e,i,t)})},updateElement:function(t,n,i){for(var a=this,o=a.chart,r=a.getDataset(),s=o.options,l=s.animation,d=o.scale,u=e.getValueAtIndexOrDefault,c=o.data.labels,h=a.calculateCircumference(r.data[n]),f=d.xCenter,g=d.yCenter,m=0,p=a.getMeta(),v=0;n>v;++v)isNaN(r.data[v])||p.data[v].hidden||++m;var b=s.startAngle,y=t.hidden?0:d.getDistanceFromCenterForValue(r.data[n]),x=b+h*m,k=x+(t.hidden?0:h),S=l.animateScale?0:d.getDistanceFromCenterForValue(r.data[n]);e.extend(t,{_datasetIndex:a.index,_index:n,_scale:d,_model:{x:f,y:g,innerRadius:0,outerRadius:i?S:y,startAngle:i&&l.animateRotate?b:x,endAngle:i&&l.animateRotate?b:k,label:u(c,n,c[n])}}),a.removeHoverStyle(t),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},countVisibleElements:function(){var t=this.getDataset(),n=this.getMeta(),i=0;return e.each(n.data,function(e,n){isNaN(t.data[n])||e.hidden||i++}),i},calculateCircumference:function(t){var e=this.getMeta().count;return e>0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,linkScales:e.noop,addElementAndReset:function(e){t.DatasetController.prototype.addElementAndReset.call(this,e),this.updateBezierControlPoints()},update:function(t){var n=this,i=n.getMeta(),a=i.dataset,o=i.data,r=a.custom||{},s=n.getDataset(),l=n.chart.options.elements.line,d=n.chart.scale;void 0!==s.tension&&void 0===s.lineTension&&(s.lineTension=s.tension),e.extend(i.dataset,{_datasetIndex:n.index,_children:o,_loop:!0,_model:{tension:r.tension?r.tension:e.getValueOrDefault(s.lineTension,l.tension),backgroundColor:r.backgroundColor?r.backgroundColor:s.backgroundColor||l.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:s.borderWidth||l.borderWidth,borderColor:r.borderColor?r.borderColor:s.borderColor||l.borderColor,fill:r.fill?r.fill:void 0!==s.fill?s.fill:l.fill,borderCapStyle:r.borderCapStyle?r.borderCapStyle:s.borderCapStyle||l.borderCapStyle,borderDash:r.borderDash?r.borderDash:s.borderDash||l.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:s.borderDashOffset||l.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:s.borderJoinStyle||l.borderJoinStyle,scaleTop:d.top,scaleBottom:d.bottom,scaleZero:d.getBasePosition()}}),i.dataset.pivot(),e.each(o,function(e,i){n.updateElement(e,i,t)},n),n.updateBezierControlPoints()},updateElement:function(t,n,i){var a=this,o=t.custom||{},r=a.getDataset(),s=a.chart.scale,l=a.chart.options.elements.point,d=s.getPointPositionForValue(n,r.data[n]);e.extend(t,{_datasetIndex:a.index,_index:n,_scale:s,_model:{x:i?s.xCenter:d.x,y:i?s.yCenter:d.y,tension:o.tension?o.tension:e.getValueOrDefault(r.tension,a.chart.options.elements.line.tension),radius:o.radius?o.radius:e.getValueAtIndexOrDefault(r.pointRadius,n,l.radius),backgroundColor:o.backgroundColor?o.backgroundColor:e.getValueAtIndexOrDefault(r.pointBackgroundColor,n,l.backgroundColor),borderColor:o.borderColor?o.borderColor:e.getValueAtIndexOrDefault(r.pointBorderColor,n,l.borderColor),borderWidth:o.borderWidth?o.borderWidth:e.getValueAtIndexOrDefault(r.pointBorderWidth,n,l.borderWidth),pointStyle:o.pointStyle?o.pointStyle:e.getValueAtIndexOrDefault(r.pointStyle,n,l.pointStyle),hitRadius:o.hitRadius?o.hitRadius:e.getValueAtIndexOrDefault(r.hitRadius,n,l.hitRadius)}}),t._model.skip=o.skip?o.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,n=this.getMeta();e.each(n.data,function(i,a){var o=i._model,r=e.splineCurve(e.previousItem(n.data,a,!0)._model,o,e.nextItem(n.data,a,!0)._model,o.tension);o.controlPointPreviousX=Math.max(Math.min(r.previous.x,t.right),t.left),o.controlPointPreviousY=Math.max(Math.min(r.previous.y,t.bottom),t.top),o.controlPointNextX=Math.max(Math.min(r.next.x,t.right),t.left),o.controlPointNextY=Math.max(Math.min(r.next.y,t.bottom),t.top),i.pivot()})},draw:function(t){var n=this.getMeta(),i=t||1;e.each(n.data,function(t){t.transition(i)}),n.dataset.transition(i).draw(),e.each(n.data,function(t){t.draw()})},setHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,o=t._model;o.radius=i.hoverRadius?i.hoverRadius:e.getValueAtIndexOrDefault(n.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),o.backgroundColor=i.hoverBackgroundColor?i.hoverBackgroundColor:e.getValueAtIndexOrDefault(n.pointHoverBackgroundColor,a,e.getHoverColor(o.backgroundColor)),o.borderColor=i.hoverBorderColor?i.hoverBorderColor:e.getValueAtIndexOrDefault(n.pointHoverBorderColor,a,e.getHoverColor(o.borderColor)),o.borderWidth=i.hoverBorderWidth?i.hoverBorderWidth:e.getValueAtIndexOrDefault(n.pointHoverBorderWidth,a,o.borderWidth)},removeHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,o=t._model,r=this.chart.options.elements.point;o.radius=i.radius?i.radius:e.getValueAtIndexOrDefault(n.radius,a,r.radius),o.backgroundColor=i.backgroundColor?i.backgroundColor:e.getValueAtIndexOrDefault(n.pointBackgroundColor,a,r.backgroundColor),o.borderColor=i.borderColor?i.borderColor:e.getValueAtIndexOrDefault(n.pointBorderColor,a,r.borderColor),o.borderWidth=i.borderWidth?i.borderWidth:e.getValueAtIndexOrDefault(n.pointBorderWidth,a,r.borderWidth)}})}},{}],21:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,n,i){var a=this;i||(t.animating=!0);for(var o=0;o1&&(n=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1);for(var i=0;it.animations[i].animationObject.numSteps&&(t.animations[i].animationObject.currentStep=t.animations[i].animationObject.numSteps),t.animations[i].animationObject.render(t.animations[i].chartInstance,t.animations[i].animationObject),t.animations[i].animationObject.onAnimationProgress&&t.animations[i].animationObject.onAnimationProgress.call&&t.animations[i].animationObject.onAnimationProgress.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].animationObject.currentStep===t.animations[i].animationObject.numSteps?(t.animations[i].animationObject.onAnimationComplete&&t.animations[i].animationObject.onAnimationComplete.call&&t.animations[i].animationObject.onAnimationComplete.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].chartInstance.animating=!1,t.animations.splice(i,1)):++i;var a=Date.now(),o=(a-e)/t.frameDuration;t.dropFrames+=o,t.animations.length>0&&t.requestAnimationFrame()}}}},{}],22:[function(t,e,n){"use strict";e.exports=function(t){var e=t.canvasHelpers={};e.drawPoint=function(t,e,n,i,a){var o,r,s,l,d,u;if("object"==typeof e&&(o=e.toString(),"[object HTMLImageElement]"===o||"[object HTMLCanvasElement]"===o))return void t.drawImage(e,i-e.width/2,a-e.height/2);if(!(isNaN(n)||0>=n)){switch(e){default:t.beginPath(),t.arc(i,a,n,0,2*Math.PI),t.closePath(),t.fill();break;case"triangle":t.beginPath(),r=3*n/Math.sqrt(3),d=r*Math.sqrt(3)/2,t.moveTo(i-r/2,a+d/3),t.lineTo(i+r/2,a+d/3),t.lineTo(i,a-2*d/3),t.closePath(),t.fill();break;case"rect":u=1/Math.SQRT2*n,t.beginPath(),t.fillRect(i-u,a-u,2*u,2*u),t.strokeRect(i-u,a-u,2*u,2*u);break;case"rectRot":u=1/Math.SQRT2*n,t.beginPath(),t.moveTo(i-u,a),t.lineTo(i,a+u),t.lineTo(i+u,a),t.lineTo(i,a-u),t.closePath(),t.fill();break;case"cross":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"crossRot":t.beginPath(),s=Math.cos(Math.PI/4)*n,l=Math.sin(Math.PI/4)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i-s,a+l),t.lineTo(i+s,a-l),t.closePath();break;case"star":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),s=Math.cos(Math.PI/4)*n,l=Math.sin(Math.PI/4)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i-s,a+l),t.lineTo(i+s,a-l),t.closePath();break;case"line":t.beginPath(),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"dash":t.beginPath(),t.moveTo(i,a),t.lineTo(i+n,a),t.closePath()}t.stroke()}}}},{}],23:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(n){return this.chart=n,this.config=n.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){var e=this;return t.plugins.notify("beforeInit",[e]),e.bindEvents(),e.ensureScalesHaveIDs(),e.buildOrUpdateControllers(),e.buildScales(),e.updateLayout(),e.resetElements(),e.initToolTip(),e.update(),t.plugins.notify("afterInit",[e]),e},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(n){var i=this,a=i.chart,o=a.canvas,r=e.getMaximumWidth(o),s=a.aspectRatio,l=i.options.maintainAspectRatio&&isNaN(s)===!1&&isFinite(s)&&0!==s?r/s:e.getMaximumHeight(o),d=a.width!==r||a.height!==l;if(!d)return i;o.width=a.width=r,o.height=a.height=l,e.retinaScale(a);var u={width:r,height:l};return t.plugins.notify("resize",[i,u]),i.options.onResize&&i.options.onResize(i,u),n||(i.stop(),i.update(i.options.responsiveAnimationDuration)),i},ensureScalesHaveIDs:function(){var t=this.options,n=t.scales||{},i=t.scale;e.each(n.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),e.each(n.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),i&&(i.id=i.id||"scale")},buildScales:function(){var n=this,i=n.options,a=n.scales={},o=[];i.scales&&(o=o.concat((i.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category"}}),(i.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear"}}))),i.scale&&o.push({options:i.scale,dtype:"radialLinear",isDefault:!0}),e.each(o,function(i){var o=i.options,r=e.getValueOrDefault(o.type,i.dtype),s=t.scaleService.getScaleConstructor(r);if(s){var l=new s({id:o.id,options:o,ctx:n.chart.ctx,chart:n});a[l.id]=l,i.isDefault&&(n.scale=l)}}),t.scaleService.addScalesToLayout(this)},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var n=this,i=[],a=[];if(e.each(n.data.datasets,function(e,o){var r=n.getDatasetMeta(o);r.type||(r.type=e.type||n.config.type),i.push(r.type),r.controller?r.controller.updateIndex(o):(r.controller=new t.controllers[r.type](n,o),a.push(r.controller))},n),i.length>1)for(var o=1;oe;++e)i.getDatasetMeta(e).controller.update();t.plugins.notify("afterDatasetsUpdate",[i])}},render:function(n,i){var a=this;t.plugins.notify("beforeRender",[a]);var o=a.options.animation;if(o&&("undefined"!=typeof n&&0!==n||"undefined"==typeof n&&0!==o.duration)){var r=new t.Animation;r.numSteps=(n||o.duration)/16.66,r.easing=o.easing,r.render=function(t,n){var i=e.easingEffects[n.easing],a=n.currentStep/n.numSteps,o=i(a);t.draw(o,a,n.currentStep)},r.onAnimationProgress=o.onProgress,r.onAnimationComplete=o.onComplete,t.animationService.addAnimation(a,r,n,i)}else a.draw(),o&&o.onComplete&&o.onComplete.call&&o.onComplete.call(a);return a},draw:function(n){var i=this,a=n||1;i.clear(),t.plugins.notify("beforeDraw",[i,a]),e.each(i.boxes,function(t){t.draw(i.chartArea)},i),i.scale&&i.scale.draw(),t.plugins.notify("beforeDatasetsDraw",[i,a]),e.each(i.data.datasets,function(t,e){i.isDatasetVisible(e)&&i.getDatasetMeta(e).controller.draw(n)},i,!0),t.plugins.notify("afterDatasetsDraw",[i,a]),i.tooltip.transition(a).draw(),t.plugins.notify("afterDraw",[i,a])},getElementAtEvent:function(t){var n=this,i=e.getRelativePosition(t,n.chart),a=[];return e.each(n.data.datasets,function(t,o){if(n.isDatasetVisible(o)){var r=n.getDatasetMeta(o);e.each(r.data,function(t){return t.inRange(i.x,i.y)?(a.push(t),a):void 0})}}),a.slice(0,1)},getElementsAtEvent:function(t){var n=this,i=e.getRelativePosition(t,n.chart),a=[],o=function(){if(n.data.datasets)for(var t=0;t0&&(e=this.getDatasetMeta(e[0]._datasetIndex).data),e},getDatasetMeta:function(t){var e=this,n=e.data.datasets[t];n._meta||(n._meta={});var i=n._meta[e.id];return i||(i=n._meta[e.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;n>e;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){var n=this;n.stop(),n.clear(),e.unbindEvents(n,n.events),e.removeResizeListener(n.chart.canvas.parentNode);var i=n.chart.canvas;i.width=n.chart.width,i.height=n.chart.height,void 0!==n.chart.originalDevicePixelRatio&&n.chart.ctx.scale(1/n.chart.originalDevicePixelRatio,1/n.chart.originalDevicePixelRatio),i.style.width=n.chart.originalCanvasStyleWidth,i.style.height=n.chart.originalCanvasStyleHeight,t.plugins.notify("destroy",[n]),delete t.instances[n.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){var e=this;e.tooltip=new t.Tooltip({_chart:e.chart,_chartInstance:e,_data:e.data,_options:e.options.tooltips},e)},bindEvents:function(){var t=this;e.bindEvents(t,t.options.events,function(e){t.eventHandler(e)})},updateHoverStyle:function(t,e,n){var i,a,o,r=n?"setHoverStyle":"removeHoverStyle";switch(e){case"single":t=[t[0]];break;case"label":case"dataset":case"x-axis":break;default:return}for(a=0,o=t.length;o>a;++a)i=t[a],i&&this.getDatasetMeta(i._datasetIndex).controller[r](i)},eventHandler:function(t){var n=this,i=n.tooltip,a=n.options||{},o=a.hover,r=a.tooltips;return n.lastActive=n.lastActive||[],n.lastTooltipActive=n.lastTooltipActive||[],"mouseout"===t.type?(n.active=[],n.tooltipActive=[]):(n.active=n.getElementsAtEventForMode(t,o.mode),n.tooltipActive=n.getElementsAtEventForMode(t,r.mode)),o.onHover&&o.onHover.call(n,n.active),n.legend&&n.legend.handleEvent&&n.legend.handleEvent(t),("mouseup"===t.type||"click"===t.type)&&a.onClick&&a.onClick.call(n,t,n.active),n.lastActive.length&&n.updateHoverStyle(n.lastActive,o.mode,!1),n.active.length&&o.mode&&n.updateHoverStyle(n.active,o.mode,!0),(r.enabled||r.custom)&&(i.initialize(),i._active=n.tooltipActive,i.update(!0)),i.pivot(),n.animating||e.arrayEquals(n.active,n.lastActive)&&e.arrayEquals(n.tooltipActive,n.lastTooltipActive)||(n.stop(),(r.enabled||r.custom)&&i.update(!0),n.render(o.animationDuration,!0)),n.lastActive=n.active,n.lastTooltipActive=n.tooltipActive,n}})}},{}],24:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=e.noop;t.DatasetController=function(t,e){this.initialize(t,e)},e.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),n=t.getDataset();null===e.xAxisID&&(e.xAxisID=n.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=n.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,n=e.dataElementType;return n&&new n({_chart:e.chart.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,n=this,i=n.getMeta(),a=n.getDataset().data||[],o=i.data;for(t=0,e=a.length;e>t;++t)o[t]=o[t]||n.createMetaData(i,t);i.dataset=i.dataset||n.createMetaDataset()},addElementAndReset:function(t){var e=this,n=e.createMetaData(t);e.getMeta().data.splice(t,0,n),e.updateElement(n,t,!0)},buildOrUpdateElements:function(){var t=this.getMeta(),e=t.data,n=this.getDataset().data.length,i=e.length;if(i>n)e.splice(n,i-n);else if(n>i)for(var a=i;n>a;++a)this.addElementAndReset(a)},update:n,draw:function(t){var n=t||1;e.each(this.getMeta().data,function(t){t.transition(n).draw()})},removeHoverStyle:function(t,n){var i=this.chart.data.datasets[t._datasetIndex],a=t._index,o=t.custom||{},r=e.getValueAtIndexOrDefault,s=t._model;s.backgroundColor=o.backgroundColor?o.backgroundColor:r(i.backgroundColor,a,n.backgroundColor),s.borderColor=o.borderColor?o.borderColor:r(i.borderColor,a,n.borderColor),s.borderWidth=o.borderWidth?o.borderWidth:r(i.borderWidth,a,n.borderWidth)},setHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t._index,a=t.custom||{},o=e.getValueAtIndexOrDefault,r=e.getHoverColor,s=t._model;s.backgroundColor=a.hoverBackgroundColor?a.hoverBackgroundColor:o(n.hoverBackgroundColor,i,r(s.backgroundColor)),s.borderColor=a.hoverBorderColor?a.hoverBorderColor:o(n.hoverBorderColor,i,r(s.borderColor)),s.borderWidth=a.hoverBorderWidth?a.hoverBorderWidth:o(n.hoverBorderWidth,i,s.borderWidth)}}),t.DatasetController.extend=e.inherits}},{}],25:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){this.hidden=!1},pivot:function(){var t=this;return t._view||(t._view=e.clone(t._model)),t._start=e.clone(t._view),t},transition:function(t){var n=this;return n._view||(n._view=e.clone(n._model)),1===t?(n._view=n._model,n._start=null,n):(n._start||n.pivot(),e.each(n._model,function(i,a){if("_"===a[0]);else if(n._view.hasOwnProperty(a))if(i===n._view[a]);else if("string"==typeof i)try{var o=e.color(n._model[a]).mix(e.color(n._start[a]),t);n._view[a]=o.rgbString()}catch(r){n._view[a]=i}else if("number"==typeof i){var s=void 0!==n._start[a]&&isNaN(n._start[a])===!1?n._start[a]:0;n._view[a]=(n._model[a]-s)*t+s}else n._view[a]=i;else"number"!=typeof i||isNaN(n._view[a])?n._view[a]=i:n._view[a]=i*t},n),n)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],26:[function(t,e,n){"use strict";var i=t(2);e.exports=function(t){function e(t,e,n){var i;return"string"==typeof t?(i=parseInt(t,10),-1!==t.indexOf("%")&&(i=i/100*e.parentNode[n])):i=t,i}function n(t){return void 0!==t&&null!==t&&"none"!==t}function a(t,i,a){var o=document.defaultView,r=t.parentNode,s=o.getComputedStyle(t)[i],l=o.getComputedStyle(r)[i],d=n(s),u=n(l),c=Number.POSITIVE_INFINITY;return d||u?Math.min(d?e(s,t,a):c,u?e(l,r,a):c):"none"}var o=t.helpers={};o.each=function(t,e,n,i){var a,r;if(o.isArray(t))if(r=t.length,i)for(a=r-1;a>=0;a--)e.call(n,t[a],a);else for(a=0;r>a;a++)e.call(n,t[a],a);else if("object"==typeof t){var s=Object.keys(t);for(r=s.length,a=0;r>a;a++)e.call(n,t[s[a]],s[a])}},o.clone=function(t){var e={};return o.each(t,function(t,n){o.isArray(t)?e[n]=t.slice(0):"object"==typeof t&&null!==t?e[n]=o.clone(t):e[n]=t}),e},o.extend=function(t){for(var e=function(e,n){t[n]=e},n=1,i=arguments.length;i>n;n++)o.each(arguments[n],e);return t},o.configMerge=function(e){var n=o.clone(e);return o.each(Array.prototype.slice.call(arguments,1),function(e){o.each(e,function(e,i){if("scales"===i)n[i]=o.scaleMerge(n.hasOwnProperty(i)?n[i]:{},e);else if("scale"===i)n[i]=o.configMerge(n.hasOwnProperty(i)?n[i]:{},t.scaleService.getScaleDefaults(e.type),e);else if(n.hasOwnProperty(i)&&o.isArray(n[i])&&o.isArray(e)){var a=n[i];o.each(e,function(t,e){e=i[n].length||!i[n][a].type?i[n].push(o.configMerge(s,e)):e.type&&e.type!==i[n][a].type?i[n][a]=o.configMerge(i[n][a],s,e):i[n][a]=o.configMerge(i[n][a],e)}):(i[n]=[],o.each(e,function(e){var a=o.getValueOrDefault(e.type,"xAxes"===n?"category":"linear");i[n].push(o.configMerge(t.scaleService.getScaleDefaults(a),e))})):i.hasOwnProperty(n)&&"object"==typeof i[n]&&null!==i[n]&&"object"==typeof e?i[n]=o.configMerge(i[n],e):i[n]=e}),i},o.getValueAtIndexOrDefault=function(t,e,n){return void 0===t||null===t?n:o.isArray(t)?en;++n)if(t[n]===e)return n;return-1},o.where=function(t,e){if(o.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return o.each(t,function(t){e(t)&&n.push(t)}),n},o.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;a>i;++i)if(e.call(n,t[i],i,t))return i;return-1},o.findNextWhere=function(t,e,n){(void 0===n||null===n)&&(n=-1);for(var i=n+1;i=0;i--){var a=t[i];if(e(a))return a}},o.inherits=function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=o.inherits,t&&o.extend(n.prototype,t),n.__super__=e.prototype,n},o.noop=function(){},o.uid=function(){var t=0;return function(){return t++}}(),o.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.almostEquals=function(t,e,n){return Math.abs(t-e)0?1:-1},o.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},o.toRadians=function(t){return t*(Math.PI/180)},o.toDegrees=function(t){return t*(180/Math.PI)},o.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),o=Math.atan2(i,n);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},o.aliasPixel=function(t){return t%2===0?0:.5},o.splineCurve=function(t,e,n,i){var a=t.skip?e:t,o=e,r=n.skip?e:n,s=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),l=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),d=s/(s+l),u=l/(s+l);d=isNaN(d)?0:d,u=isNaN(u)?0:u;var c=i*d,h=i*u;return{previous:{x:o.x-c*(r.x-a.x),y:o.y-c*(r.y-a.y)},next:{x:o.x+h*(r.x-a.x),y:o.y+h*(r.y-a.y)}}},o.EPSILON=Number.EPSILON||1e-14,o.splineCurveMonotone=function(t){var e,n,i,a,r=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),s=r.length;for(e=0;s>e;++e)i=r[e],i.model.skip||(n=e>0?r[e-1]:null,a=s-1>e?r[e+1]:null,a&&!a.model.skip&&(i.deltaK=(a.model.y-i.model.y)/(a.model.x-i.model.x)),!n||n.model.skip?i.mK=i.deltaK:!a||a.model.skip?i.mK=n.deltaK:this.sign(n.deltaK)!==this.sign(i.deltaK)?i.mK=0:i.mK=(n.deltaK+i.deltaK)/2);var l,d,u,c;for(e=0;s-1>e;++e)i=r[e],a=r[e+1],i.model.skip||a.model.skip||(o.almostEquals(i.deltaK,0,this.EPSILON)?i.mK=a.mK=0:(l=i.mK/i.deltaK,d=a.mK/i.deltaK,c=Math.pow(l,2)+Math.pow(d,2),9>=c||(u=3/Math.sqrt(c),i.mK=l*u*i.deltaK,a.mK=d*u*i.deltaK)));var h;for(e=0;s>e;++e)i=r[e],i.model.skip||(n=e>0?r[e-1]:null,a=s-1>e?r[e+1]:null,n&&!n.model.skip&&(h=(i.model.x-n.model.x)/3,i.model.controlPointPreviousX=i.model.x-h,i.model.controlPointPreviousY=i.model.y-h*i.mK),a&&!a.model.skip&&(h=(a.model.x-i.model.x)/3,i.model.controlPointNextX=i.model.x+h,i.model.controlPointNextY=i.model.y+h*i.mK))},o.nextItem=function(t,e,n){return n?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},o.previousItem=function(t,e,n){return n?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},o.niceNum=function(t,e){var n,i=Math.floor(o.log10(t)),a=t/Math.pow(10,i);return n=e?1.5>a?1:3>a?2:7>a?5:10:1>=a?1:2>=a?2:5>=a?5:10,n*Math.pow(10,i)};var r=o.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===(t/=1)?1:(n||(n=.3),it?-.5*(i*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/n)):i*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-r.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*r.easeInBounce(2*t):.5*r.easeOutBounce(2*t-1)+.5}};o.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),o.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),o.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.currentTarget||t.srcElement,s=r.getBoundingClientRect(),l=a.touches;l&&l.length>0?(n=l[0].clientX,i=l[0].clientY):(n=a.clientX,i=a.clientY);var d=parseFloat(o.getStyle(r,"padding-left")),u=parseFloat(o.getStyle(r,"padding-top")),c=parseFloat(o.getStyle(r,"padding-right")),h=parseFloat(o.getStyle(r,"padding-bottom")),f=s.right-s.left-d-c,g=s.bottom-s.top-u-h; -return n=Math.round((n-s.left-d)/f*r.width/e.currentDevicePixelRatio),i=Math.round((i-s.top-u)/g*r.height/e.currentDevicePixelRatio),{x:n,y:i}},o.addEvent=function(t,e,n){t.addEventListener?t.addEventListener(e,n):t.attachEvent?t.attachEvent("on"+e,n):t["on"+e]=n},o.removeEvent=function(t,e,n){t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent?t.detachEvent("on"+e,n):t["on"+e]=o.noop},o.bindEvents=function(t,e,n){var i=t.events=t.events||{};o.each(e,function(e){i[e]=function(){n.apply(t,arguments)},o.addEvent(t.chart.canvas,e,i[e])})},o.unbindEvents=function(t,e){var n=t.chart.canvas;o.each(e,function(t,e){o.removeEvent(n,e,t)})},o.getConstraintWidth=function(t){return a(t,"max-width","clientWidth")},o.getConstraintHeight=function(t){return a(t,"max-height","clientHeight")},o.getMaximumWidth=function(t){var e=t.parentNode,n=parseInt(o.getStyle(e,"padding-left"),10),i=parseInt(o.getStyle(e,"padding-right"),10),a=e.clientWidth-n-i,r=o.getConstraintWidth(t);return isNaN(r)?a:Math.min(a,r)},o.getMaximumHeight=function(t){var e=t.parentNode,n=parseInt(o.getStyle(e,"padding-top"),10),i=parseInt(o.getStyle(e,"padding-bottom"),10),a=e.clientHeight-n-i,r=o.getConstraintHeight(t);return isNaN(r)?a:Math.min(a,r)},o.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},o.retinaScale=function(t){var e=t.ctx,n=t.canvas,i=n.width,a=n.height,o=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==o&&(n.height=a*o,n.width=i*o,e.scale(o,o),t.originalDevicePixelRatio=t.originalDevicePixelRatio||o),n.style.width=i+"px",n.style.height=a+"px"},o.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},o.fontString=function(t,e,n){return e+" "+t+"px "+n},o.longestText=function(t,e,n,i){i=i||{};var a=i.data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var s=0;o.each(n,function(e){void 0!==e&&null!==e&&o.isArray(e)!==!0?s=o.measureText(t,a,r,s,e):o.isArray(e)&&o.each(e,function(e){void 0===e||null===e||o.isArray(e)||(s=o.measureText(t,a,r,s,e))})});var l=r.length/2;if(l>n.length){for(var d=0;l>d;d++)delete a[r[d]];r.splice(0,l)}return s},o.measureText=function(t,e,n,i,a){var o=e[a];return o||(o=e[a]=t.measureText(a).width,n.push(a)),o>i&&(i=o),i},o.numberOfLabelLines=function(t){var e=1;return o.each(t,function(t){o.isArray(t)&&t.length>e&&(e=t.length)}),e},o.drawRoundedRectangle=function(t,e,n,i,a,o){t.beginPath(),t.moveTo(e+o,n),t.lineTo(e+i-o,n),t.quadraticCurveTo(e+i,n,e+i,n+o),t.lineTo(e+i,n+a-o),t.quadraticCurveTo(e+i,n+a,e+i-o,n+a),t.lineTo(e+o,n+a),t.quadraticCurveTo(e,n+a,e,n+a-o),t.lineTo(e,n+o),t.quadraticCurveTo(e,n,e+o,n),t.closePath()},o.color=function(e){return i?i(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.error("Color.js not found!"),e)},o.addResizeListener=function(t,e){var n=document.createElement("iframe"),i="chartjs-hidden-iframe";n.classlist?n.classlist.add(i):n.setAttribute("class",i),n.tabIndex=-1;var a=n.style;a.width="100%",a.display="block",a.border=0,a.height=0,a.margin=0,a.position="absolute",a.left=0,a.right=0,a.top=0,a.bottom=0,t.insertBefore(n,t.firstChild),(n.contentWindow||n).onresize=function(){return e?e():void 0}},o.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},o.isArray=Array.isArray?function(t){return Array.isArray(t)}:function(t){return"[object Array]"===Object.prototype.toString.call(t)},o.arrayEquals=function(t,e){var n,i,a,r;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;i>n;++n)if(a=t[n],r=e[n],a instanceof Array&&r instanceof Array){if(!o.arrayEquals(a,r))return!1}else if(a!==r)return!1;return!0},o.callCallback=function(t,e,n){t&&"function"==typeof t.call&&t.apply(n,e)},o.getHoverColor=function(t){return t instanceof CanvasPattern?t:o.color(t).saturate(.5).darken(.1).rgbString()}}},{2:2}],27:[function(t,e,n){"use strict";e.exports=function(){var t=function(e,n){var i=this,a=t.helpers;return i.config=n||{data:{datasets:[]}},e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),i.ctx=e,i.canvas=e.canvas,e.canvas.style.display=e.canvas.style.display||"block",i.width=e.canvas.width||parseInt(a.getStyle(e.canvas,"width"),10)||a.getMaximumWidth(e.canvas),i.height=e.canvas.height||parseInt(a.getStyle(e.canvas,"height"),10)||a.getMaximumHeight(e.canvas),i.aspectRatio=i.width/i.height,(isNaN(i.aspectRatio)||isFinite(i.aspectRatio)===!1)&&(i.aspectRatio=void 0!==n.aspectRatio?n.aspectRatio:2),i.originalCanvasStyleWidth=e.canvas.style.width,i.originalCanvasStyleHeight=e.canvas.style.height,a.retinaScale(i),i.controller=new t.Controller(i),a.addResizeListener(e.canvas.parentNode,function(){i.controller&&i.controller.config.options.responsive&&i.controller.resize()}),i.controller?i.controller:i};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
    ');for(var n=0;n'),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push("
"),e.join("")}}},t.Chart=t,t}},{}],28:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,n,i){function a(t){var e,n=t.isHorizontal();n?(e=t.update(t.options.fullWidth?m:k,x),S-=e.height):(e=t.update(y,b),k-=e.width),w.push({horizontal:n,minSize:e,box:t})}function o(t){var n=e.findNextWhere(w,function(e){return e.box===t});if(n)if(t.isHorizontal()){var i={left:_,right:M,top:0,bottom:0};t.update(t.options.fullWidth?m:k,p/2,i)}else t.update(n.minSize.width,S)}function r(t){var n=e.findNextWhere(w,function(e){return e.box===t}),i={left:0,right:0,top:D,bottom:C};n&&t.update(n.minSize.width,S,i)}function s(t){t.isHorizontal()?(t.left=t.options.fullWidth?l:_,t.right=t.options.fullWidth?n-l:_+k,t.top=I,t.bottom=I+t.height,I=t.bottom):(t.left=F,t.right=F+t.width,t.top=D,t.bottom=D+S,F=t.right)}if(t){var l=0,d=0,u=e.where(t.boxes,function(t){return"left"===t.options.position}),c=e.where(t.boxes,function(t){return"right"===t.options.position}),h=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});h.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var m=n-2*l,p=i-2*d,v=m/2,b=p/2,y=(n-v)/(u.length+c.length),x=(i-b)/(h.length+f.length),k=m,S=p,w=[];e.each(u.concat(c,h,f),a);var _=l,M=l,D=d,C=d;e.each(u.concat(c),o),e.each(u,function(t){_+=t.width}),e.each(c,function(t){M+=t.width}),e.each(h.concat(f),o),e.each(h,function(t){D+=t.height}),e.each(f,function(t){C+=t.height}),e.each(u.concat(c),r),_=l,M=l,D=d,C=d,e.each(u,function(t){_+=t.width}),e.each(c,function(t){M+=t.width}),e.each(h,function(t){D+=t.height}),e.each(f,function(t){C+=t.height});var T=i-D-C,P=n-_-M;(P!==k||T!==S)&&(e.each(u,function(t){t.height=T}),e.each(c,function(t){t.height=T}),e.each(h,function(t){t.options.fullWidth||(t.width=P)}),e.each(f,function(t){t.options.fullWidth||(t.width=P)}),S=T,k=P);var F=l,I=d;e.each(u.concat(h),s),F+=k,I+=S,e.each(c,s),e.each(f,s),t.chartArea={left:_,top:D,right:_+k,bottom:D+S},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,S)})}}}}},{}],29:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=e.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var n=t.data;return e.isArray(n.datasets)?n.datasets.map(function(n,i){return{text:n.label,fillStyle:e.isArray(n.backgroundColor)?n.backgroundColor[0]:n.backgroundColor,hidden:!t.isDatasetVisible(i),lineCap:n.borderCapStyle,lineDash:n.borderDash,lineDashOffset:n.borderDashOffset,lineJoin:n.borderJoinStyle,lineWidth:n.borderWidth,strokeStyle:n.borderColor,pointStyle:n.pointStyle,datasetIndex:i}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:n,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:n,beforeSetDimensions:n,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:n,beforeBuildLabels:n,buildLabels:function(){var t=this;t.legendItems=t.options.labels.generateLabels.call(t,t.chart),t.options.reverse&&t.legendItems.reverse()},afterBuildLabels:n,beforeFit:n,fit:function(){var n=this,i=n.options,a=i.labels,o=i.display,r=n.ctx,s=t.defaults.global,l=e.getValueOrDefault,d=l(a.fontSize,s.defaultFontSize),u=l(a.fontStyle,s.defaultFontStyle),c=l(a.fontFamily,s.defaultFontFamily),h=e.fontString(d,u,c),f=n.legendHitBoxes=[],g=n.minSize,m=n.isHorizontal();if(m?(g.width=n.maxWidth,g.height=o?10:0):(g.width=o?10:0,g.height=n.maxHeight),o)if(r.font=h,m){var p=n.lineWidths=[0],v=n.legendItems.length?d+a.padding:0;r.textAlign="left",r.textBaseline="top",e.each(n.legendItems,function(t,e){var i=a.usePointStyle?d*Math.sqrt(2):a.boxWidth,o=i+d/2+r.measureText(t.text).width;p[p.length-1]+o+a.padding>=n.width&&(v+=d+a.padding,p[p.length]=n.left),f[e]={left:0,top:0,width:o,height:d},p[p.length-1]+=o+a.padding}),g.height+=v}else{var b=a.padding,y=n.columnWidths=[],x=a.padding,k=0,S=0,w=d+b;e.each(n.legendItems,function(t,e){var n=a.usePointStyle?2*a.boxWidth:a.boxWidth,i=n+d/2+r.measureText(t.text).width;S+w>g.height&&(x+=k+a.padding,y.push(k),k=0,S=0),k=Math.max(k,i),S+=w,f[e]={left:0,top:0,width:i,height:d}}),x+=k,y.push(k),g.width+=x}n.width=g.width,n.height=g.height},afterFit:n,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var n=this,i=n.options,a=i.labels,o=t.defaults.global,r=o.elements.line,s=n.width,l=n.lineWidths;if(i.display){var d,u=n.ctx,c=e.getValueOrDefault,h=c(a.fontColor,o.defaultFontColor),f=c(a.fontSize,o.defaultFontSize),g=c(a.fontStyle,o.defaultFontStyle),m=c(a.fontFamily,o.defaultFontFamily),p=e.fontString(f,g,m);u.textAlign="left",u.textBaseline="top",u.lineWidth=.5,u.strokeStyle=h,u.fillStyle=h,u.font=p;var v=a.boxWidth,b=n.legendHitBoxes,y=function(e,n,a){if(!(isNaN(v)||0>=v)){u.save(),u.fillStyle=c(a.fillStyle,o.defaultColor),u.lineCap=c(a.lineCap,r.borderCapStyle),u.lineDashOffset=c(a.lineDashOffset,r.borderDashOffset),u.lineJoin=c(a.lineJoin,r.borderJoinStyle),u.lineWidth=c(a.lineWidth,r.borderWidth),u.strokeStyle=c(a.strokeStyle,o.defaultColor);var s=0===c(a.lineWidth,r.borderWidth);if(u.setLineDash&&u.setLineDash(c(a.lineDash,r.borderDash)),i.labels&&i.labels.usePointStyle){var l=f*Math.SQRT2/2,d=l/Math.SQRT2,h=e+d,g=n+d;t.canvasHelpers.drawPoint(u,a.pointStyle,l,h,g)}else s||u.strokeRect(e,n,v,f),u.fillRect(e,n,v,f);u.restore()}},x=function(t,e,n,i){u.fillText(n.text,v+f/2+t,e),n.hidden&&(u.beginPath(),u.lineWidth=2,u.moveTo(v+f/2+t,e+f/2),u.lineTo(v+f/2+t+i,e+f/2),u.stroke())},k=n.isHorizontal();d=k?{x:n.left+(s-l[0])/2,y:n.top+a.padding,line:0}:{x:n.left+a.padding,y:n.top+a.padding,line:0};var S=f+a.padding;e.each(n.legendItems,function(t,e){var i=u.measureText(t.text).width,o=a.usePointStyle?f+f/2+i:v+f/2+i,r=d.x,c=d.y;k?r+o>=s&&(c=d.y+=S,d.line++,r=d.x=n.left+(s-l[d.line])/2):c+S>n.bottom&&(r=d.x=r+n.columnWidths[d.line]+a.padding,c=d.y=n.top,d.line++),y(r,c,t),b[e].left=r,b[e].top=c,x(r,c,t,i),k?d.x+=o+a.padding:d.y+=S})}},handleEvent:function(t){var n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover)return}else{if("click"!==a)return;if(!i.onClick)return}var o=e.getRelativePosition(t,n.chart.chart),r=o.x,s=o.y;if(r>=n.left&&r<=n.right&&s>=n.top&&s<=n.bottom)for(var l=n.legendHitBoxes,d=0;d=u.left&&r<=u.left+u.width&&s>=u.top&&s<=u.top+u.height){if("click"===a){i.onClick.call(n,t,n.legendItems[d]);break}if("mousemove"===a){i.onHover.call(n,t,n.legendItems[d]);break}}}}}),t.plugins.register({beforeInit:function(e){var n=e.options,i=n.legend;i&&(e.legend=new t.Legend({ctx:e.chart.ctx,options:i,chart:e}),t.layoutService.addBox(e,e.legend))}})}},{}],30:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers.noop;t.plugins={_plugins:[],register:function(t){var e=this._plugins;[].concat(t).forEach(function(t){-1===e.indexOf(t)&&e.push(t)})},unregister:function(t){var e=this._plugins;[].concat(t).forEach(function(t){var n=e.indexOf(t);-1!==n&&e.splice(n,1)})},clear:function(){this._plugins=[]},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e){var n,i,a=this._plugins,o=a.length;for(n=0;o>n;++n)if(i=a[n],"function"==typeof i[t]&&i[t].apply(i,e||[])===!1)return!1;return!0}},t.PluginBase=t.Element.extend({beforeInit:e,afterInit:e,beforeUpdate:e,afterUpdate:e,beforeDraw:e,afterDraw:e,destroy:e}),t.pluginService=t.plugins}},{}],31:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,position:"left",gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1,borderDash:[],borderDashOffset:0},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:function(t){return e.isArray(t)?t:""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,n,i){var a=this;return a.beforeUpdate(),a.maxWidth=t,a.maxHeight=n,a.margins=e.extend({left:0,right:0,top:0,bottom:0},i),a.beforeSetDimensions(),a.setDimensions(),a.afterSetDimensions(),a.beforeDataLimits(),a.determineDataLimits(),a.afterDataLimits(),a.beforeBuildTicks(),a.buildTicks(),a.afterBuildTicks(),a.beforeTickToLabelConversion(),a.convertTicksToLabels(),a.afterTickToLabelConversion(),a.beforeCalculateTickRotation(),a.calculateTickRotation(),a.afterCalculateTickRotation(),a.beforeFit(),a.fit(),a.afterFit(),a.afterUpdate(),a.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){var t=this;t.ticks=t.ticks.map(function(e,n,i){return t.options.ticks.userCallback?t.options.ticks.userCallback(e,n,i):t.options.ticks.callback(e,n,i)},t)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var n=this,i=n.ctx,a=t.defaults.global,o=n.options.ticks,r=e.getValueOrDefault(o.fontSize,a.defaultFontSize),s=e.getValueOrDefault(o.fontStyle,a.defaultFontStyle),l=e.getValueOrDefault(o.fontFamily,a.defaultFontFamily),d=e.fontString(r,s,l);i.font=d;var u,c=i.measureText(n.ticks[0]).width,h=i.measureText(n.ticks[n.ticks.length-1]).width;if(n.labelRotation=o.minRotation||0,n.paddingRight=0,n.paddingLeft=0,n.options.display&&n.isHorizontal()){n.paddingRight=h/2+3,n.paddingLeft=c/2+3,n.longestTextCache||(n.longestTextCache={});for(var f,g,m=e.longestText(i,d,n.ticks,n.longestTextCache),p=m,v=n.getPixelForTick(1)-n.getPixelForTick(0)-6;p>v&&n.labelRotationn.yLabelWidth&&(n.paddingLeft=u+r/2),n.paddingRight=r/2,g*m>n.maxHeight){n.labelRotation--;break}n.labelRotation++,p=f*m}}n.margins&&(n.paddingLeft=Math.max(n.paddingLeft-n.margins.left,0),n.paddingRight=Math.max(n.paddingRight-n.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){var n=this,i=n.minSize={width:0,height:0},a=n.options,o=t.defaults.global,r=a.ticks,s=a.scaleLabel,l=a.gridLines,d=a.display,u=n.isHorizontal(),c=e.getValueOrDefault(r.fontSize,o.defaultFontSize),h=e.getValueOrDefault(r.fontStyle,o.defaultFontStyle),f=e.getValueOrDefault(r.fontFamily,o.defaultFontFamily),g=e.fontString(c,h,f),m=e.getValueOrDefault(s.fontSize,o.defaultFontSize),p=a.gridLines.tickMarkLength;if(u?i.width=n.isFullWidth()?n.maxWidth-n.margins.left-n.margins.right:n.maxWidth:i.width=d&&l.drawTicks?p:0,u?i.height=d&&l.drawTicks?p:0:i.height=n.maxHeight,s.display&&d&&(u?i.height+=1.5*m:i.width+=1.5*m),r.display&&d){n.longestTextCache||(n.longestTextCache={});var v=e.longestText(n.ctx,g,n.ticks,n.longestTextCache),b=e.numberOfLabelLines(n.ticks),y=.5*c;if(u){n.longestLabelWidth=v;var x=Math.sin(e.toRadians(n.labelRotation))*n.longestLabelWidth+c*b+y*b;i.height=Math.min(n.maxHeight,i.height+x),n.ctx.font=g;var k=n.ctx.measureText(n.ticks[0]).width,S=n.ctx.measureText(n.ticks[n.ticks.length-1]).width,w=Math.cos(e.toRadians(n.labelRotation)),_=Math.sin(e.toRadians(n.labelRotation));n.paddingLeft=0!==n.labelRotation?w*k+3:k/2+3,n.paddingRight=0!==n.labelRotation?_*(c/2)+3:S/2+3}else{var M=n.maxWidth-i.width,D=r.mirror;D?v=0:v+=n.options.ticks.padding,M>v?i.width+=v:i.width=n.maxWidth,n.paddingTop=c/2,n.paddingBottom=c/2}}n.margins&&(n.paddingLeft=Math.max(n.paddingLeft-n.margins.left,0),n.paddingTop=Math.max(n.paddingTop-n.margins.top,0),n.paddingRight=Math.max(n.paddingRight-n.margins.right,0),n.paddingBottom=Math.max(n.paddingBottom-n.margins.bottom,0)),n.width=i.width,n.height=i.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date||t.isValid?t:this.getRightValue(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getValueForPixel:e.noop,getPixelForTick:function(t,e){var n=this;if(n.isHorizontal()){var i=n.width-(n.paddingLeft+n.paddingRight),a=i/Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),o=a*t+n.paddingLeft;e&&(o+=a/2);var r=n.left+Math.round(o);return r+=n.isFullWidth()?n.margins.left:0}var s=n.height-(n.paddingTop+n.paddingBottom);return n.top+t*(s/(n.ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=e.width-(e.paddingLeft+e.paddingRight),i=n*t+e.paddingLeft,a=e.left+Math.round(i);return a+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){var t=this,e=t.min,n=t.max;return t.getPixelForValue(t.beginAtZero?0:0>e&&0>n?n:e>0&&n>0?e:0)},draw:function(n){var i=this,a=i.options;if(a.display){var o,r,s=i.ctx,l=t.defaults.global,d=a.ticks,u=a.gridLines,c=a.scaleLabel,h=0!==i.labelRotation,f=d.autoSkip,g=i.isHorizontal();d.maxTicksLimit&&(r=d.maxTicksLimit);var m=e.getValueOrDefault(d.fontColor,l.defaultFontColor),p=e.getValueOrDefault(d.fontSize,l.defaultFontSize),v=e.getValueOrDefault(d.fontStyle,l.defaultFontStyle),b=e.getValueOrDefault(d.fontFamily,l.defaultFontFamily),y=e.fontString(p,v,b),x=u.tickMarkLength,k=e.getValueOrDefault(u.borderDash,l.borderDash),S=e.getValueOrDefault(u.borderDashOffset,l.borderDashOffset),w=e.getValueOrDefault(c.fontColor,l.defaultFontColor),_=e.getValueOrDefault(c.fontSize,l.defaultFontSize),M=e.getValueOrDefault(c.fontStyle,l.defaultFontStyle),D=e.getValueOrDefault(c.fontFamily,l.defaultFontFamily),C=e.fontString(_,M,D),T=e.toRadians(i.labelRotation),P=Math.cos(T),F=i.longestLabelWidth*P;s.fillStyle=m;var I=[];if(g){if(o=!1,h&&(F/=2),(F+d.autoSkipPadding)*i.ticks.length>i.width-(i.paddingLeft+i.paddingRight)&&(o=1+Math.floor((F+d.autoSkipPadding)*i.ticks.length/(i.width-(i.paddingLeft+i.paddingRight)))),r&&i.ticks.length>r)for(;!o||i.ticks.length/(o||1)>r;)o||(o=1),o+=1;f||(o=!1)}var A="right"===a.position?i.left:i.right-x,O="right"===a.position?i.left+x:i.right,R="bottom"===a.position?i.top:i.bottom-x,W="bottom"===a.position?i.top+x:i.bottom;if(e.each(i.ticks,function(t,r){if(void 0!==t&&null!==t){var s=i.ticks.length===r+1,l=o>1&&r%o>0||r%o===0&&r+o>=i.ticks.length;if((!l||s)&&void 0!==t&&null!==t){var c,f;r===("undefined"!=typeof i.zeroLineIndex?i.zeroLineIndex:0)?(c=u.zeroLineWidth,f=u.zeroLineColor):(c=e.getValueAtIndexOrDefault(u.lineWidth,r),f=e.getValueAtIndexOrDefault(u.color,r));var m,p,v,b,y,w,_,M,D,C,P="middle",F="middle";if(g){h||(F="top"===a.position?"bottom":"top"),P=h?"right":"center";var L=i.getPixelForTick(r)+e.aliasPixel(c);D=i.getPixelForTick(r,u.offsetGridLines)+d.labelOffset,C=h?i.top+12:"top"===a.position?i.bottom-x:i.top+x,m=v=y=_=L,p=R,b=W,w=n.top,M=n.bottom}else{"left"===a.position?d.mirror?(D=i.right+d.padding,P="left"):(D=i.right-d.padding,P="right"):d.mirror?(D=i.left-d.padding,P="right"):(D=i.left+d.padding,P="left");var V=i.getPixelForTick(r);V+=e.aliasPixel(c),C=i.getPixelForTick(r,u.offsetGridLines),m=A,v=O,y=n.left,_=n.right,p=b=w=M=V}I.push({tx1:m,ty1:p,tx2:v,ty2:b,x1:y,y1:w,x2:_,y2:M,labelX:D,labelY:C,glWidth:c,glColor:f,glBorderDash:k,glBorderDashOffset:S,rotation:-1*T,label:t,textBaseline:F,textAlign:P})}}}),e.each(I,function(t){if(u.display&&(s.save(),s.lineWidth=t.glWidth,s.strokeStyle=t.glColor,s.setLineDash&&(s.setLineDash(t.glBorderDash),s.lineDashOffset=t.glBorderDashOffset),s.beginPath(),u.drawTicks&&(s.moveTo(t.tx1,t.ty1),s.lineTo(t.tx2,t.ty2)),u.drawOnChartArea&&(s.moveTo(t.x1,t.y1),s.lineTo(t.x2,t.y2)),s.stroke(),s.restore()),d.display){s.save(),s.translate(t.labelX,t.labelY),s.rotate(t.rotation),s.font=y,s.textBaseline=t.textBaseline,s.textAlign=t.textAlign;var n=t.label;if(e.isArray(n))for(var i=0,a=-(n.length-1)*p*.75;ie;++e){var o=t[e];if(o&&o.hasValue()){var r=o.tooltipPosition();i.push(r.x),a.push(r.y)}}var s=0,l=0;for(e=0;e0){var o=t[0];o.xLabel?n=o.xLabel:a>0&&o.indexe;++e)g.push(i(d[e]));s.itemSort&&(g=g.sort(function(t,e){return s.itemSort(t,e,u)})),d.length>1&&a.each(g,function(t){h.push(s.callbacks.labelColor.call(r,t,c))}),a.extend(l,{title:r.getTitle(g,u),beforeBody:r.getBeforeBody(g,u),body:r.getBody(g,u),afterBody:r.getAfterBody(g,u),footer:r.getFooter(g,u),x:Math.round(f.x),y:Math.round(f.y),caretPadding:a.getValueOrDefault(f.padding,2),labelColors:h});var m=r.getTooltipSize(l);r.determineAlignment(m),a.extend(l,r.getBackgroundPoint(l,m))}else r._model.opacity=0;return t&&s.custom&&s.custom.call(r,l),r},getTooltipSize:function(t){var e=this._chart.ctx,n={height:2*t.yPadding,width:0},i=t.body,o=i.reduce(function(t,e){ -return t+e.before.length+e.lines.length+e.after.length},0);o+=t.beforeBody.length+t.afterBody.length;var r=t.title.length,s=t.footer.length,l=t.titleFontSize,d=t.bodyFontSize,u=t.footerFontSize;n.height+=r*l,n.height+=(r-1)*t.titleSpacing,n.height+=r?t.titleMarginBottom:0,n.height+=o*d,n.height+=o?(o-1)*t.bodySpacing:0,n.height+=s?t.footerMarginTop:0,n.height+=s*u,n.height+=s?(s-1)*t.footerSpacing:0;var c=0,h=function(t){n.width=Math.max(n.width,e.measureText(t).width+c)};return e.font=a.fontString(l,t._titleFontStyle,t._titleFontFamily),a.each(t.title,h),e.font=a.fontString(d,t._bodyFontStyle,t._bodyFontFamily),a.each(t.beforeBody.concat(t.afterBody),h),c=i.length>1?d+2:0,a.each(i,function(t){a.each(t.before,h),a.each(t.lines,h),a.each(t.after,h)}),c=0,e.font=a.fontString(u,t._footerFontStyle,t._footerFontFamily),a.each(t.footer,h),n.width+=2*t.xPadding,n},determineAlignment:function(t){var e=this,n=e._model,i=e._chart,a=e._chartInstance.chartArea;n.yi.height-t.height&&(n.yAlign="bottom");var o,r,s,l,d,u=(a.left+a.right)/2,c=(a.top+a.bottom)/2;"center"===n.yAlign?(o=function(t){return u>=t},r=function(t){return t>u}):(o=function(e){return e<=t.width/2},r=function(e){return e>=i.width-t.width/2}),s=function(e){return e+t.width>i.width},l=function(e){return e-t.width<0},d=function(t){return c>=t?"top":"bottom"},o(n.x)?(n.xAlign="left",s(n.x)&&(n.xAlign="center",n.yAlign=d(n.y))):r(n.x)&&(n.xAlign="right",l(n.x)&&(n.xAlign="center",n.yAlign=d(n.y)))},getBackgroundPoint:function(t,e){var n={x:t.x,y:t.y},i=t.caretSize,a=t.caretPadding,o=t.cornerRadius,r=t.xAlign,s=t.yAlign,l=i+a,d=o+a;return"right"===r?n.x-=e.width:"center"===r&&(n.x-=e.width/2),"top"===s?n.y+=l:"bottom"===s?n.y-=e.height+l:n.y-=e.height/2,"center"===s?"left"===r?n.x+=l:"right"===r&&(n.x-=l):"left"===r?n.x-=d:"right"===r&&(n.x+=d),n},drawCaret:function(t,e,n){var i,o,r,s,l,d,u=this._view,c=this._chart.ctx,h=u.caretSize,f=u.cornerRadius,g=u.xAlign,m=u.yAlign,p=t.x,v=t.y,b=e.width,y=e.height;"center"===m?("left"===g?(i=p,o=i-h,r=i):(i=p+b,o=i+h,r=i),l=v+y/2,s=l-h,d=l+h):("left"===g?(i=p+f,o=i+h,r=o+h):"right"===g?(i=p+b-f,o=i-h,r=o-h):(o=p+b/2,i=o-h,r=o+h),"top"===m?(s=v,l=s-h,d=s):(s=v+y,l=s+h,d=s));var x=a.color(u.backgroundColor);c.fillStyle=x.alpha(n*x.alpha()).rgbString(),c.beginPath(),c.moveTo(i,s),c.lineTo(o,l),c.lineTo(r,d),c.closePath(),c.fill()},drawTitle:function(t,e,n,i){var o=e.title;if(o.length){n.textAlign=e._titleAlign,n.textBaseline="top";var r=e.titleFontSize,s=e.titleSpacing,l=a.color(e.titleFontColor);n.fillStyle=l.alpha(i*l.alpha()).rgbString(),n.font=a.fontString(r,e._titleFontStyle,e._titleFontFamily);var d,u;for(d=0,u=o.length;u>d;++d)n.fillText(o[d],t.x,t.y),t.y+=r+s,d+1===o.length&&(t.y+=e.titleMarginBottom-s)}},drawBody:function(t,e,n,i){var o=e.bodyFontSize,r=e.bodySpacing,s=e.body;n.textAlign=e._bodyAlign,n.textBaseline="top";var l=a.color(e.bodyFontColor),d=l.alpha(i*l.alpha()).rgbString();n.fillStyle=d,n.font=a.fontString(o,e._bodyFontStyle,e._bodyFontFamily);var u=0,c=function(e){n.fillText(e,t.x+u,t.y),t.y+=o+r};a.each(e.beforeBody,c);var h=s.length>1;u=h?o+2:0,a.each(s,function(r,s){a.each(r.before,c),a.each(r.lines,function(r){h&&(n.fillStyle=a.color(e.legendColorBackground).alpha(i).rgbaString(),n.fillRect(t.x,t.y,o,o),n.strokeStyle=a.color(e.labelColors[s].borderColor).alpha(i).rgbaString(),n.strokeRect(t.x,t.y,o,o),n.fillStyle=a.color(e.labelColors[s].backgroundColor).alpha(i).rgbaString(),n.fillRect(t.x+1,t.y+1,o-2,o-2),n.fillStyle=d),c(r)}),a.each(r.after,c)}),u=0,a.each(e.afterBody,c),t.y-=r},drawFooter:function(t,e,n,i){var o=e.footer;if(o.length){t.y+=e.footerMarginTop,n.textAlign=e._footerAlign,n.textBaseline="top";var r=a.color(e.footerFontColor);n.fillStyle=r.alpha(i*r.alpha()).rgbString(),n.font=a.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),a.each(o,function(i){n.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n=this.getTooltipSize(e),i={x:e.x,y:e.y},o=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.enabled){var r=a.color(e.backgroundColor);t.fillStyle=r.alpha(o*r.alpha()).rgbString(),a.drawRoundedRectangle(t,i.x,i.y,n.width,n.height,e.cornerRadius),t.fill(),this.drawCaret(i,n,o),i.x+=e.xPadding,i.y+=e.yPadding,this.drawTitle(i,e,t,o),this.drawBody(i,e,t,o),this.drawFooter(i,e,t,o)}}}})}},{}],35:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=t.defaults.global;n.elements.arc={backgroundColor:n.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)l;)l+=2*Math.PI;for(;o>l;)o-=2*Math.PI;for(;s>o;)o+=2*Math.PI;var d=o>=s&&l>=o,u=r>=i.innerRadius&&r<=i.outerRadius;return d&&u}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],36:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=t.defaults.global;t.defaults.global.elements.line={tension:.4,backgroundColor:n.defaultColor,borderWidth:3,borderColor:n.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0},t.elements.Line=t.Element.extend({draw:function(){function t(t,e){var n=e._view;e._view.steppedLine===!0?(l.lineTo(n.x,t._view.y),l.lineTo(n.x,n.y)):0===e._view.tension?l.lineTo(n.x,n.y):l.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,n.controlPointPreviousX,n.controlPointPreviousY,n.x,n.y)}var i=this,a=i._view,o=a.spanGaps,r=a.scaleZero,s=i._loop,l=i._chart.ctx;l.save();var d=i._children.slice(),u=-1;s&&d.length&&d.push(d[0]);var c,h,f,g;if(d.length&&a.fill){for(l.beginPath(),c=0;cc;c++)e.lineTo.apply(e,t(c));e.fill(),n.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var n=this._view;return n?n.y=n.x-n.width/2&&t<=n.x+n.width/2&&e>=n.y&&e<=n.base:t>=n.x-n.width/2&&t<=n.x+n.width/2&&e>=n.base&&e<=n.y:!1},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],39:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"bottom"},i=t.Scale.extend({getLabels:function(){var t=this.chart.data;return(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels},determineDataLimits:function(){var t=this,n=t.getLabels();t.minIndex=0,t.maxIndex=n.length-1;var i;void 0!==t.options.ticks.min&&(i=e.indexOf(n,t.options.ticks.min),t.minIndex=-1!==i?i:t.minIndex),void 0!==t.options.ticks.max&&(i=e.indexOf(n,t.options.ticks.max),t.maxIndex=-1!==i?i:t.maxIndex),t.min=n[t.minIndex],t.max=n[t.maxIndex]},buildTicks:function(){var t=this,e=t.getLabels();t.ticks=0===t.minIndex&&t.maxIndex===e.length-1?e:e.slice(t.minIndex,t.maxIndex+1)},getLabelForIndex:function(t,e){var n=this,i=n.chart.data,a=n.isHorizontal();return i.xLabels&&a||i.yLabels&&!a?n.getRightValue(i.datasets[e].data[t]):n.ticks[t]},getPixelForValue:function(t,e,n,i){var a=this,o=Math.max(a.maxIndex+1-a.minIndex-(a.options.gridLines.offsetGridLines?0:1),1);if(void 0!==t&&isNaN(e)){var r=a.getLabels(),s=r.indexOf(t);e=-1!==s?s:e}if(a.isHorizontal()){var l=a.width-(a.paddingLeft+a.paddingRight),d=l/o,u=d*(e-a.minIndex)+a.paddingLeft;return(a.options.gridLines.offsetGridLines&&i||a.maxIndex===a.minIndex&&i)&&(u+=d/2),a.left+Math.round(u)}var c=a.height-(a.paddingTop+a.paddingBottom),h=c/o,f=h*(e-a.minIndex)+a.paddingTop;return a.options.gridLines.offsetGridLines&&i&&(f+=h/2),a.top+Math.round(f)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,n=this,i=Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),a=n.isHorizontal(),o=a?n.width-(n.paddingLeft+n.paddingRight):n.height-(n.paddingTop+n.paddingBottom),r=o/i;return t-=a?n.left:n.top,n.options.gridLines.offsetGridLines&&(t-=r/2),t-=a?n.paddingLeft:n.paddingTop,e=0>=t?0:Math.round(t/r)},getBasePixel:function(){return this.bottom}});t.scaleService.registerScaleType("category",i,n)}},{}],40:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:function(t,n,i){var a=i.length>3?i[2]-i[1]:i[1]-i[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=e.log10(Math.abs(a)),r="";if(0!==t){var s=-1*Math.floor(o);s=Math.max(Math.min(s,20),0),r=t.toFixed(s)}else r="0";return r}}},i=t.LinearScaleBase.extend({determineDataLimits:function(){function t(t){return s?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=n.chart,o=a.data,r=o.datasets,s=n.isHorizontal();if(n.min=null,n.max=null,i.stacked){var l={};e.each(r,function(o,r){var s=a.getDatasetMeta(r);void 0===l[s.type]&&(l[s.type]={positiveValues:[],negativeValues:[]});var d=l[s.type].positiveValues,u=l[s.type].negativeValues;a.isDatasetVisible(r)&&t(s)&&e.each(o.data,function(t,e){var a=+n.getRightValue(t);isNaN(a)||s.data[e].hidden||(d[e]=d[e]||0,u[e]=u[e]||0,i.relativePoints?d[e]=100:0>a?u[e]+=a:d[e]+=a)})}),e.each(l,function(t){var i=t.positiveValues.concat(t.negativeValues),a=e.min(i),o=e.max(i);n.min=null===n.min?a:Math.min(n.min,a),n.max=null===n.max?o:Math.max(n.max,o)})}else e.each(r,function(i,o){var r=a.getDatasetMeta(o);a.isDatasetVisible(o)&&t(r)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||r.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i))})});this.handleTickRangeOptions()},getTickLimit:function(){var n,i=this,a=i.options.ticks;if(i.isHorizontal())n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.width/50));else{var o=e.getValueOrDefault(a.fontSize,t.defaults.global.defaultFontSize);n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.height/(2*o)))}return n},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e,n,i=this,a=i.paddingLeft,o=i.paddingBottom,r=i.start,s=+i.getRightValue(t),l=i.end-r;return i.isHorizontal()?(n=i.width-(a+i.paddingRight),e=i.left+n/l*(s-r),Math.round(e+a)):(n=i.height-(i.paddingTop+o),e=i.bottom-o-n/l*(s-r),Math.round(e))},getValueForPixel:function(t){var e=this,n=e.isHorizontal(),i=e.paddingLeft,a=e.paddingBottom,o=n?e.width-(i+e.paddingRight):e.height-(e.paddingTop+a),r=(n?t-e.left-i:e.bottom-a-t)/o;return e.start+(e.end-e.start)*r},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",i,n)}},{}],41:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=e.noop;t.LinearScaleBase=t.Scale.extend({handleTickRangeOptions:function(){var t=this,n=t.options,i=n.ticks;if(i.beginAtZero){var a=e.sign(t.min),o=e.sign(t.max);0>a&&0>o?t.max=0:a>0&&o>0&&(t.min=0)}void 0!==i.min?t.min=i.min:void 0!==i.suggestedMin&&(t.min=Math.min(t.min,i.suggestedMin)),void 0!==i.max?t.max=i.max:void 0!==i.suggestedMax&&(t.max=Math.max(t.max,i.suggestedMax)),t.min===t.max&&(t.max++,i.beginAtZero||t.min--)},getTickLimit:n,handleDirectionalChanges:n,buildTicks:function(){var t=this,n=t.options,i=t.ticks=[],a=n.ticks,o=e.getValueOrDefault,r=t.getTickLimit();r=Math.max(2,r);var s,l=a.fixedStepSize&&a.fixedStepSize>0||a.stepSize&&a.stepSize>0;if(l)s=o(a.fixedStepSize,a.stepSize);else{var d=e.niceNum(t.max-t.min,!1);s=e.niceNum(d/(r-1),!0)}var u=Math.floor(t.min/s)*s,c=Math.ceil(t.max/s)*s,h=(c-u)/s;h=e.almostEquals(h,Math.round(h),s/1e3)?Math.round(h):Math.ceil(h),i.push(void 0!==a.min?a.min:u);for(var f=1;h>f;++f)i.push(u+f*s);i.push(void 0!==a.max?a.max:c),t.handleDirectionalChanges(),t.max=e.max(i),t.min=e.min(i),a.reverse?(i.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{}],42:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:function(t,n,i){var a=t/Math.pow(10,Math.floor(e.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===n||n===i.length-1?t.toExponential():""}}},i=t.Scale.extend({determineDataLimits:function(){function t(t){return d?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=i.ticks,o=n.chart,r=o.data,s=r.datasets,l=e.getValueOrDefault,d=n.isHorizontal();if(n.min=null,n.max=null,n.minNotZero=null,i.stacked){var u={};e.each(s,function(a,r){var s=o.getDatasetMeta(r);o.isDatasetVisible(r)&&t(s)&&(void 0===u[s.type]&&(u[s.type]=[]),e.each(a.data,function(t,e){var a=u[s.type],o=+n.getRightValue(t);isNaN(o)||s.data[e].hidden||(a[e]=a[e]||0,i.relativePoints?a[e]=100:a[e]+=o)}))}),e.each(u,function(t){var i=e.min(t),a=e.max(t);n.min=null===n.min?i:Math.min(n.min,i),n.max=null===n.max?a:Math.max(n.max,a)})}else e.each(s,function(i,a){var r=o.getDatasetMeta(a);o.isDatasetVisible(a)&&t(r)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||r.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i),0!==i&&(null===n.minNotZero||it.max&&(t.max=i))})}}),t.handleTickRangeOptions()},getTickLimit:function(){var t=this.options.ticks,i=e.getValueOrDefault(t.fontSize,n.defaultFontSize);return Math.min(t.maxTicksLimit?t.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)))},convertTicksToLabels:function(){var e=this;t.LinearScaleBase.prototype.convertTicksToLabels.call(e),e.pointLabels=e.chart.data.labels.map(e.options.pointLabels.callback,e)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t,i,a,o,r,s,l,d,u,c,h,f,g=this.options.pointLabels,m=e.getValueOrDefault(g.fontSize,n.defaultFontSize),p=e.getValueOrDefault(g.fontStyle,n.defaultFontStyle),v=e.getValueOrDefault(g.fontFamily,n.defaultFontFamily),b=e.fontString(m,p,v),y=e.min([this.height/2-m-5,this.width/2]),x=this.width,k=0;for(this.ctx.font=b,i=0;ix&&(x=t.x+o,r=i),t.x-ow?t.x+a>x&&(x=t.x+a,r=i):t.x-ae&&0>n?n:e>0&&n>0?e:0)},draw:function(){var t=this,i=t.options,a=i.gridLines,o=i.ticks,r=i.angleLines,s=i.pointLabels,l=e.getValueOrDefault;if(i.display){var d=t.ctx,u=l(o.fontSize,n.defaultFontSize),c=l(o.fontStyle,n.defaultFontStyle),h=l(o.fontFamily,n.defaultFontFamily),f=e.fontString(u,c,h);if(e.each(t.ticks,function(r,s){if(s>0||i.reverse){var c=t.getDistanceFromCenterForValue(t.ticksAsNumbers[s]),h=t.yCenter-c;if(a.display&&0!==s)if(d.strokeStyle=e.getValueAtIndexOrDefault(a.color,s-1),d.lineWidth=e.getValueAtIndexOrDefault(a.lineWidth,s-1),i.lineArc)d.beginPath(),d.arc(t.xCenter,t.yCenter,c,0,2*Math.PI),d.closePath(),d.stroke();else{d.beginPath();for(var g=0;g=0;y--){if(r.display){var x=t.getPointPosition(y,g);d.beginPath(),d.moveTo(t.xCenter,t.yCenter),d.lineTo(x.x,x.y),d.stroke(),d.closePath()}var k=t.getPointPosition(y,g+5),S=l(s.fontColor,n.defaultFontColor);d.font=b,d.fillStyle=S;var w=t.pointLabels,_=this.getIndexAngle(y)+Math.PI/2,M=360*_/(2*Math.PI)%360;0===M||180===M?d.textAlign="center":180>M?d.textAlign="left":d.textAlign="right",90===M||270===M?d.textBaseline="middle":M>270||90>M?d.textBaseline="bottom":d.textBaseline="top",d.fillText(w[y]?w[y]:"",k.x,k.y)}}}}});t.scaleService.registerScaleType("radialLinear",a,i)}},{}],44:[function(t,e,n){"use strict";var i=t(6);i="function"==typeof i?i:window.moment,e.exports=function(t){var e=t.helpers,n={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},a={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!i)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return null===t||null===e?null:"undefined"!=typeof this.labelMoments[t]?this.labelMoments[t][e]:null},getLabelDiff:function(t,e){var n=this;return null===t||null===e?null:(void 0===n.labelDiffs&&n.buildLabelDiffs(),"undefined"!=typeof n.labelDiffs[t]?n.labelDiffs[t][e]:null)},getMomentStartOf:function(t){var e=this;return"week"===e.options.time.unit&&e.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(e.options.time.isoWeekday):t.clone().startOf(e.tickUnit)},determineDataLimits:function(){var t=this;t.labelMoments=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0?(e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i))},t),t.firstTick=i.min.call(t,n),t.lastTick=i.max.call(t,n)):(t.firstTick=null,t.lastTick=null),e.each(t.chart.data.datasets,function(a,o){var r=[],s=t.chart.isDatasetVisible(o);"object"==typeof a.data[0]&&null!==a.data[0]?e.each(a.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),r.push(n),s&&(t.firstTick=null!==t.firstTick?i.min(t.firstTick,n):n,t.lastTick=null!==t.lastTick?i.max(t.lastTick,n):n))},t):r=n,t.labelMoments.push(r)},t),t.options.time.min&&(t.firstTick=t.parseTime(t.options.time.min)),t.options.time.max&&(t.lastTick=t.parseTime(t.options.time.max)),t.firstTick=(t.firstTick||i()).clone(),t.lastTick=(t.lastTick||i()).clone()},buildLabelDiffs:function(){var t=this;t.labelDiffs=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0&&e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i.diff(t.firstTick,t.tickUnit,!0)))},t),e.each(t.chart.data.datasets,function(i){var a=[];"object"==typeof i.data[0]&&null!==i.data[0]?e.each(i.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),a.push(n.diff(t.firstTick,t.tickUnit,!0)))},t):a=n,t.labelDiffs.push(a)},t)},buildTicks:function(){var i=this;i.ctx.save();var a=e.getValueOrDefault(i.options.ticks.fontSize,t.defaults.global.defaultFontSize),o=e.getValueOrDefault(i.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),r=e.getValueOrDefault(i.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),s=e.fontString(a,o,r);if(i.ctx.font=s,i.ticks=[],i.unitScale=1,i.scaleSizeInUnits=0,i.options.time.unit)i.tickUnit=i.options.time.unit||"day",i.displayFormat=i.options.time.displayFormats[i.tickUnit],i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,1);else{var l=i.isHorizontal()?i.width-(i.paddingLeft+i.paddingRight):i.height-(i.paddingTop+i.paddingBottom),d=i.tickFormatFunction(i.firstTick,0,[]),u=i.ctx.measureText(d).width,c=Math.cos(e.toRadians(i.options.ticks.maxRotation)),h=Math.sin(e.toRadians(i.options.ticks.maxRotation));u=u*c+a*h;var f=l/u;i.tickUnit=i.options.time.minUnit,i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.displayFormat=i.options.time.displayFormats[i.tickUnit];for(var g=0,m=n.units[g];g=Math.ceil(i.scaleSizeInUnits/f)){i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(i.scaleSizeInUnits/f)k?i.lastTick=i.getMomentStartOf(i.lastTick.add(1,i.tickUnit)):k>=0&&(i.lastTick=x),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0)}i.options.time.displayFormat&&(i.displayFormat=i.options.time.displayFormat),i.ticks.push(i.firstTick.clone());for(var S=1;S<=i.scaleSizeInUnits;++S){var w=y.clone().add(S,i.tickUnit);if(i.options.time.max&&w.diff(i.lastTick,i.tickUnit,!0)>=0)break;S%i.unitScale===0&&i.ticks.push(w)}var _=i.ticks[i.ticks.length-1].diff(i.lastTick,i.tickUnit);(0!==_||0===i.scaleSizeInUnits)&&(i.options.time.max?(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.ticks[0],i.tickUnit,!0)):(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0))),i.ctx.restore(),i.labelDiffs=void 0},getLabelForIndex:function(t,e){var n=this,i=n.chart.data.labels&&tn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return e<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=this,i=t,a=void 0===e?.5:e,r=2*a-1,o=n.alpha()-i.alpha(),s=((r*o===-1?r:(r+o)/(1+r*o))+1)/2,l=1-s;return this.rgb(s*n.red()+l*i.red(),s*n.green()+l*i.green(),s*n.blue()+l*i.blue()).alpha(n.alpha()*a+i.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new r,i=this.values,a=n.values;for(var o in i)i.hasOwnProperty(o)&&(t=i[o],e={}.toString.call(t),"[object Array]"===e?a[o]=t.slice(0):"[object Number]"===e?a[o]=t:console.error("unexpected color value:",t));return n}},r.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},r.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},r.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92;var a=.4124*e+.3576*n+.1805*i,r=.2126*e+.7152*n+.0722*i,o=.0193*e+.1192*n+.9505*i;return[100*a,100*r,100*o]}function d(t){var e,n,i,a=u(t),r=a[0],o=a[1],s=a[2];return r/=95.047,o/=100,s/=108.883,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,e=116*o-16,n=500*(r-o),i=200*(o-s),[e,n,i]}function c(t){return Y(d(t))}function h(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0==s)return r=255*l,[r,r,r];n=l<.5?l*(1+s):l+s-l*s,e=2*l-n,a=[0,0,0];for(var u=0;u<3;u++)i=o+1/3*-(u-1),i<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a}function f(t){var e,n,i=t[0],a=t[1]/100,r=t[2]/100;return 0===r?[0,0,0]:(r*=2,a*=r<=1?r:2-r,n=(r+a)/2,e=2*a/(r+a),[i,100*e,100*n])}function m(t){return o(h(t))}function p(t){return s(h(t))}function v(t){return l(h(t))}function y(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r)),i=255*i;switch(a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}}function x(t){var e,n,i=t[0],a=t[1]/100,r=t[2]/100;return n=(2-a)*r,e=a*r,e/=n<=1?n:2-n,e=e||0,n/=2,[i,100*e,100*n]}function k(t){return o(y(t))}function _(t){return s(y(t))}function w(t){return l(y(t))}function S(t){var e,n,i,a,o=t[0]/360,s=t[1]/100,l=t[2]/100,u=s+l;switch(u>1&&(s/=u,l/=u),e=Math.floor(6*o),n=1-l,i=6*o-e,0!=(1&e)&&(i=1-i),a=s+i*(n-s),e){default:case 6:case 0:r=n,g=a,b=s;break;case 1:r=a,g=n,b=s;break;case 2:r=s,g=n,b=a;break;case 3:r=s,g=a,b=n;break;case 4:r=a,g=s,b=n;break;case 5:r=n,g=s,b=a}return[255*r,255*g,255*b]}function M(t){return i(S(t))}function D(t){return a(S(t))}function C(t){return s(S(t))}function T(t){return l(S(t))}function P(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100,s=t[3]/100;return e=1-Math.min(1,a*(1-s)+s),n=1-Math.min(1,r*(1-s)+s),i=1-Math.min(1,o*(1-s)+s),[255*e,255*n,255*i]}function I(t){return i(P(t))}function A(t){return a(P(t))}function F(t){return o(P(t))}function O(t){return l(P(t))}function R(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return e=3.2406*a+r*-1.5372+o*-.4986,n=a*-.9689+1.8758*r+.0415*o,i=.0557*a+r*-.204+1.057*o,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,e=Math.min(Math.max(0,e),1),n=Math.min(Math.max(0,n),1),i=Math.min(Math.max(0,i),1),[255*e,255*n,255*i]}function L(t){var e,n,i,a=t[0],r=t[1],o=t[2];return a/=95.047,r/=100,o/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,e=116*r-16,n=500*(a-r),i=200*(r-o),[e,n,i]}function V(t){return Y(L(t))}function W(t){var e,n,i,a,r=t[0],o=t[1],s=t[2];return r<=8?(n=100*r/903.3,a=7.787*(n/100)+16/116):(n=100*Math.pow((r+16)/116,3),a=Math.pow(n/100,1/3)),e=e/95.047<=.008856?e=95.047*(o/500+a-16/116)/7.787:95.047*Math.pow(o/500+a,3),i=i/108.883<=.008859?i=108.883*(a-s/200-16/116)/7.787:108.883*Math.pow(a-s/200,3),[e,n,i]}function Y(t){var e,n,i,a=t[0],r=t[1],o=t[2];return e=Math.atan2(o,r),n=360*e/2/Math.PI,n<0&&(n+=360),i=Math.sqrt(r*r+o*o),[a,i,n]}function B(t){return R(W(t))}function z(t){var e,n,i,a=t[0],r=t[1],o=t[2];return i=o/360*2*Math.PI,e=r*Math.cos(i),n=r*Math.sin(i),[a,e,n]}function N(t){return W(z(t))}function H(t){return B(z(t))}function E(t){return J[t]}function U(t){return i(E(t))}function j(t){return a(E(t))}function G(t){return o(E(t))}function q(t){return s(E(t))}function Z(t){return d(E(t))}function X(t){return u(E(t))}e.exports={rgb2hsl:i,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:s,rgb2keyword:l,rgb2xyz:u,rgb2lab:d,rgb2lch:c,hsl2rgb:h,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:y,hsv2hsl:x,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:w,hwb2rgb:S,hwb2hsl:M,hwb2hsv:D,hwb2cmyk:C,hwb2keyword:T,cmyk2rgb:P,cmyk2hsl:I,cmyk2hsv:A,cmyk2hwb:F,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:G,keyword2cmyk:q,keyword2lab:Z,keyword2xyz:X,xyz2rgb:R,xyz2lab:L,xyz2lch:V,lab2xyz:W,lab2rgb:B,lab2lch:Y,lch2lab:z,lch2xyz:N,lch2rgb:H};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},K={};for(var Q in J)K[JSON.stringify(J[Q])]=Q},{}],4:[function(t,e,n){var i=t(3),a=function(){return new u};for(var r in i){a[r+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(r);var o=/(\w+)2(\w+)/.exec(r),s=o[1],l=o[2];a[s]=a[s]||{},a[s][l]=a[r]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var n=i[t](e);if("string"==typeof n||void 0===n)return n;for(var a=0;a0)for(n in xi)i=xi[n],a=e[i],v(a)||(t[i]=a);return t}function y(e){b(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),ki===!1&&(ki=!0,t.updateOffset(this),ki=!1)}function x(t){return t instanceof y||null!=t&&null!=t._isAMomentObject}function k(t){return t<0?Math.ceil(t)||0:Math.floor(t)}function _(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=k(e)),n}function w(t,e,n){var i,a=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),o=0;for(i=0;i0?"future":"past"];return C(n)?n(e):n.replace(/%s/i,e)}function W(t,e){var n=t.toLowerCase();Fi[n]=Fi[n+"s"]=Fi[e]=t}function Y(t){return"string"==typeof t?Fi[t]||Fi[t.toLowerCase()]:void 0}function B(t){var e,n,i={};for(n in t)d(t,n)&&(e=Y(n),e&&(i[e]=t[n]));return i}function z(t,e){Oi[t]=e}function N(t){var e=[];for(var n in t)e.push({unit:n,priority:Oi[n]});return e.sort(function(t,e){return t.priority-e.priority}),e}function H(e,n){return function(i){return null!=i?(U(this,e,i),t.updateOffset(this,n),this):E(this,e)}}function E(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function U(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function j(t){return t=Y(t),C(this[t])?this[t]():this}function G(t,e){if("object"==typeof t){t=B(t);for(var n=N(t),i=0;i=0;return(r?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}function Z(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(Wi[t]=a),e&&(Wi[e[0]]=function(){return q(a.apply(this,arguments),e[1],e[2])}),n&&(Wi[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function X(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function J(t){var e,n,i=t.match(Ri);for(e=0,n=i.length;e=0&&Li.test(t);)t=t.replace(Li,n),Li.lastIndex=0,i-=1;return t}function $(t,e,n){ea[t]=C(e)?e:function(t,i){return t&&n?n:e}}function tt(t,e){return d(ea,t)?ea[t](e._strict,e._locale):new RegExp(et(t))}function et(t){return nt(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,i,a){return e||n||i||a}))}function nt(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function it(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),s(e)&&(i=function(t,n){n[e]=_(t)}),n=0;n=0&&isFinite(s.getFullYear())&&s.setFullYear(t),s}function kt(t){var e=new Date(Date.UTC.apply(null,arguments));return t<100&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function _t(t,e,n){var i=7+e-n,a=(7+kt(t,0,i).getUTCDay()-e)%7;return-a+i-1}function wt(t,e,n,i,a){var r,o,s=(7+n-i)%7,l=_t(t,i,a),u=1+7*(e-1)+s+l;return u<=0?(r=t-1,o=vt(r)+u):u>vt(t)?(r=t+1,o=u-vt(t)):(r=t,o=u),{year:r,dayOfYear:o}}function St(t,e,n){var i,a,r=_t(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?(a=t.year()-1,i=o+Mt(a,e,n)):o>Mt(t.year(),e,n)?(i=o-Mt(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Mt(t,e,n){var i=_t(t,e,n),a=_t(t+1,e,n);return(vt(t)-i+a)/7}function Dt(t){return St(t,this._week.dow,this._week.doy).week}function Ct(){return this._week.dow}function Tt(){return this._week.doy}function Pt(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function It(t){var e=St(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function At(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Ft(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}function Ot(t,e){return t?a(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]:this._weekdays}function Rt(t){return t?this._weekdaysShort[t.day()]:this._weekdaysShort}function Lt(t){return t?this._weekdaysMin[t.day()]:this._weekdaysMin}function Vt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;i<7;++i)r=h([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===e?(a=ha.call(this._weekdaysParse,o),a!==-1?a:null):"ddd"===e?(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:null):(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null):"dddd"===e?(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null))):"ddd"===e?(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:null))):(a=ha.call(this._minWeekdaysParse,o),a!==-1?a:(a=ha.call(this._weekdaysParse,o),a!==-1?a:(a=ha.call(this._shortWeekdaysParse,o),a!==-1?a:null)))}function Wt(t,e,n){var i,a,r;if(this._weekdaysParseExact)return Vt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;i<7;i++){if(a=h([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".",".?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}}function Yt(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=At(t,this.localeData()),this.add(t-e,"d")):e}function Bt(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function zt(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=Ft(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7}function Nt(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(d(this,"_weekdaysRegex")||(this._weekdaysRegex=wa),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)}function Ht(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(d(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Sa),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Et(t){return this._weekdaysParseExact?(d(this,"_weekdaysRegex")||Ut.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(d(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ma),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Ut(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=h([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=nt(s[e]),l[e]=nt(l[e]),u[e]=nt(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function jt(){return this.hours()%12||12}function Gt(){return this.hours()||24}function qt(t,e){Z(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function Zt(t,e){return e._meridiemParse}function Xt(t){return"p"===(t+"").toLowerCase().charAt(0)}function Jt(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function Kt(t){return t?t.toLowerCase().replace("_","-"):t}function Qt(t){for(var e,n,i,a,r=0;r0;){if(i=$t(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&w(a,n,!0)>=e-1)break;e--}r++}return null}function $t(t){var i=null;if(!Ia[t]&&"undefined"!=typeof n&&n&&n.exports)try{i=Da._abbr,e("./locale/"+t),te(i)}catch(t){}return Ia[t]}function te(t,e){var n;return t&&(n=v(e)?ie(t):ee(t,e),n&&(Da=n)),Da._abbr}function ee(t,e){if(null!==e){var n=Pa;if(e.abbr=t,null!=Ia[t])D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),n=Ia[t]._config;else if(null!=e.parentLocale){if(null==Ia[e.parentLocale])return Aa[e.parentLocale]||(Aa[e.parentLocale]=[]),Aa[e.parentLocale].push({name:t,config:e}),null;n=Ia[e.parentLocale]._config}return Ia[t]=new I(P(n,e)),Aa[t]&&Aa[t].forEach(function(t){ee(t.name,t.config)}),te(t),Ia[t]}return delete Ia[t],null}function ne(t,e){if(null!=e){var n,i=Pa;null!=Ia[t]&&(i=Ia[t]._config),e=P(i,e),n=new I(e),n.parentLocale=Ia[t],Ia[t]=n,te(t)}else null!=Ia[t]&&(null!=Ia[t].parentLocale?Ia[t]=Ia[t].parentLocale:null!=Ia[t]&&delete Ia[t]);return Ia[t]}function ie(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Da;if(!a(t)){if(e=$t(t))return e;t=[t]}return Qt(t)}function ae(){return Mi(Ia)}function re(t){var e,n=t._a;return n&&g(t).overflow===-2&&(e=n[aa]<0||n[aa]>11?aa:n[ra]<1||n[ra]>ot(n[ia],n[aa])?ra:n[oa]<0||n[oa]>24||24===n[oa]&&(0!==n[sa]||0!==n[la]||0!==n[ua])?oa:n[sa]<0||n[sa]>59?sa:n[la]<0||n[la]>59?la:n[ua]<0||n[ua]>999?ua:-1,g(t)._overflowDayOfYear&&(era)&&(e=ra),g(t)._overflowWeeks&&e===-1&&(e=da),g(t)._overflowWeekday&&e===-1&&(e=ca),g(t).overflow=e),t}function oe(t){var e,n,i,a,r,o,s=t._i,l=Fa.exec(s)||Oa.exec(s);if(l){for(g(t).iso=!0,e=0,n=La.length;evt(a)&&(g(t)._overflowDayOfYear=!0),n=kt(a,0,t._dayOfYear),t._a[aa]=n.getUTCMonth(),t._a[ra]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=r[e]=i[e];for(;e<7;e++)t._a[e]=r[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[oa]&&0===t._a[sa]&&0===t._a[la]&&0===t._a[ua]&&(t._nextDay=!0,t._a[oa]=0),t._d=(t._useUTC?kt:xt).apply(null,r),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[oa]=24)}}function ce(t){var e,n,i,a,r,o,s,l;if(e=t._w,null!=e.GG||null!=e.W||null!=e.E)r=1,o=4,n=le(e.GG,t._a[ia],St(xe(),1,4).year),i=le(e.W,1),a=le(e.E,1),(a<1||a>7)&&(l=!0);else{r=t._locale._week.dow,o=t._locale._week.doy;var u=St(xe(),r,o);n=le(e.gg,t._a[ia],u.year),i=le(e.w,u.week),null!=e.d?(a=e.d,(a<0||a>6)&&(l=!0)):null!=e.e?(a=e.e+r,(e.e<0||e.e>6)&&(l=!0)):a=r}i<1||i>Mt(n,r,o)?g(t)._overflowWeeks=!0:null!=l?g(t)._overflowWeekday=!0:(s=wt(n,i,a,r,o),t._a[ia]=s.year,t._dayOfYear=s.dayOfYear)}function he(e){if(e._f===t.ISO_8601)return void oe(e);e._a=[],g(e).empty=!0;var n,i,a,r,o,s=""+e._i,l=s.length,u=0;for(a=Q(e._f,e._locale).match(Ri)||[],n=0;n0&&g(e).unusedInput.push(o),s=s.slice(s.indexOf(i)+i.length),u+=i.length),Wi[r]?(i?g(e).empty=!1:g(e).unusedTokens.push(r),rt(r,i,e)):e._strict&&!i&&g(e).unusedTokens.push(r);g(e).charsLeftOver=l-u,s.length>0&&g(e).unusedInput.push(s),e._a[oa]<=12&&g(e).bigHour===!0&&e._a[oa]>0&&(g(e).bigHour=void 0),g(e).parsedDateParts=e._a.slice(0),g(e).meridiem=e._meridiem,e._a[oa]=fe(e._locale,e._a[oa],e._meridiem),de(e),re(e)}function fe(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(i=t.isPM(n),i&&e<12&&(e+=12),i||12!==e||(e=0),e):e}function ge(t){var e,n,i,a,r;if(0===t._f.length)return g(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;athis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ye(){if(!v(this._isDSTShifted))return this._isDSTShifted;var t={};if(b(t,this),t=ve(t),t._a){var e=t._isUTC?h(t._a):xe(t._a);this._isDSTShifted=this.isValid()&&w(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Be(){return!!this.isValid()&&!this._isUTC}function ze(){return!!this.isValid()&&this._isUTC}function Ne(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function He(t,e){var n,i,a,r=t,o=null;return Me(t)?r={ms:t._milliseconds,d:t._days,M:t._months}:s(t)?(r={},e?r[e]=t:r.milliseconds=t):(o=Ha.exec(t))?(n="-"===o[1]?-1:1,r={y:0,d:_(o[ra])*n,h:_(o[oa])*n,m:_(o[sa])*n,s:_(o[la])*n,ms:_(De(1e3*o[ua]))*n}):(o=Ea.exec(t))?(n="-"===o[1]?-1:1,r={y:Ee(o[2],n),M:Ee(o[3],n),w:Ee(o[4],n),d:Ee(o[5],n),h:Ee(o[6],n),m:Ee(o[7],n),s:Ee(o[8],n)}):null==r?r={}:"object"==typeof r&&("from"in r||"to"in r)&&(a=je(xe(r.from),xe(r.to)),r={},r.ms=a.milliseconds,r.M=a.months),i=new Se(r),Me(t)&&d(t,"_locale")&&(i._locale=t._locale),i}function Ee(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Ue(t,e){var n={milliseconds:0,months:0};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function je(t,e){var n;return t.isValid()&&e.isValid()?(e=Pe(e,t),t.isBefore(e)?n=Ue(t,e):(n=Ue(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function Ge(t,e){return function(n,i){var a,r;return null===i||isNaN(+i)||(D(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),r=n,n=i,i=r),n="string"==typeof n?+n:n,a=He(n,i),qe(this,a,t),this}}function qe(e,n,i,a){var r=n._milliseconds,o=De(n._days),s=De(n._months);e.isValid()&&(a=null==a||a,r&&e._d.setTime(e._d.valueOf()+r*i),o&&U(e,"Date",E(e,"Date")+o*i),s&&ct(e,E(e,"Month")+s*i),a&&t.updateOffset(e,o||s))}function Ze(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"}function Xe(e,n){var i=e||xe(),a=Pe(i,this).startOf("day"),r=t.calendarFormat(this,a)||"sameElse",o=n&&(C(n[r])?n[r].call(this,i):n[r]);return this.format(o||this.localeData().calendar(r,this,xe(i)))}function Je(){return new y(this)}function Ke(t,e){var n=x(t)?t:xe(t);return!(!this.isValid()||!n.isValid())&&(e=Y(v(e)?"millisecond":e),"millisecond"===e?this.valueOf()>n.valueOf():n.valueOf()r&&(e=r),Rn.call(this,t,e,n,i,a))}function Rn(t,e,n,i,a){var r=wt(t,e,n,i,a),o=kt(r.year,0,r.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}function Ln(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Vn(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function Wn(t,e){e[ua]=_(1e3*("0."+t))}function Yn(){return this._isUTC?"UTC":""}function Bn(){return this._isUTC?"Coordinated Universal Time":""}function zn(t){return xe(1e3*t)}function Nn(){return xe.apply(null,arguments).parseZone()}function Hn(t){return t}function En(t,e,n,i){var a=ie(),r=h().set(i,e);return a[n](r,t)}function Un(t,e,n){if(s(t)&&(e=t,t=void 0),t=t||"",null!=e)return En(t,e,n,"month");var i,a=[];for(i=0;i<12;i++)a[i]=En(t,i,n,"month");return a}function jn(t,e,n,i){"boolean"==typeof t?(s(e)&&(n=e,e=void 0),e=e||""):(e=t,n=e,t=!1,s(e)&&(n=e,e=void 0),e=e||"");var a=ie(),r=t?a._week.dow:0;if(null!=n)return En(e,(n+r)%7,i,"day");var o,l=[];for(o=0;o<7;o++)l[o]=En(e,(o+r)%7,i,"day");return l}function Gn(t,e){return Un(t,e,"months")}function qn(t,e){return Un(t,e,"monthsShort")}function Zn(t,e,n){return jn(t,e,n,"weekdays")}function Xn(t,e,n){return jn(t,e,n,"weekdaysShort")}function Jn(t,e,n){return jn(t,e,n,"weekdaysMin")}function Kn(){var t=this._data;return this._milliseconds=tr(this._milliseconds),this._days=tr(this._days),this._months=tr(this._months),t.milliseconds=tr(t.milliseconds),t.seconds=tr(t.seconds),t.minutes=tr(t.minutes),t.hours=tr(t.hours),t.months=tr(t.months),t.years=tr(t.years),this}function Qn(t,e,n,i){var a=He(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function $n(t,e){return Qn(this,t,e,1)}function ti(t,e){return Qn(this,t,e,-1)}function ei(t){return t<0?Math.floor(t):Math.ceil(t)}function ni(){var t,e,n,i,a,r=this._milliseconds,o=this._days,s=this._months,l=this._data;return r>=0&&o>=0&&s>=0||r<=0&&o<=0&&s<=0||(r+=864e5*ei(ai(s)+o),o=0,s=0),l.milliseconds=r%1e3,t=k(r/1e3),l.seconds=t%60,e=k(t/60),l.minutes=e%60,n=k(e/60),l.hours=n%24,o+=k(n/24),a=k(ii(o)),s+=a,o-=ei(ai(a)),i=k(s/12),s%=12,l.days=o,l.months=s,l.years=i,this}function ii(t){return 4800*t/146097}function ai(t){return 146097*t/4800}function ri(t){var e,n,i=this._milliseconds;if(t=Y(t),"month"===t||"year"===t)return e=this._days+i/864e5,n=this._months+ii(e),"month"===t?n:n/12;switch(e=this._days+Math.round(ai(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}}function oi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*_(this._months/12)}function si(t){return function(){return this.as(t)}}function li(t){return t=Y(t),this[t+"s"]()}function ui(t){return function(){return this._data[t]}}function di(){return k(this.days()/7)}function ci(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}function hi(t,e,n){var i=He(t).abs(),a=pr(i.as("s")),r=pr(i.as("m")),o=pr(i.as("h")),s=pr(i.as("d")),l=pr(i.as("M")),u=pr(i.as("y")),d=a0,d[4]=n,ci.apply(null,d)}function fi(t){return void 0===t?pr:"function"==typeof t&&(pr=t,!0)}function gi(t,e){return void 0!==vr[t]&&(void 0===e?vr[t]:(vr[t]=e,!0))}function mi(t){var e=this.localeData(),n=hi(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function pi(){var t,e,n,i=br(this._milliseconds)/1e3,a=br(this._days),r=br(this._months);t=k(i/60),e=k(t/60),i%=60,t%=60,n=k(r/12),r%=12;var o=n,s=r,l=a,u=e,d=t,c=i,h=this.asSeconds();return h?(h<0?"-":"")+"P"+(o?o+"Y":"")+(s?s+"M":"")+(l?l+"D":"")+(u||d||c?"T":"")+(u?u+"H":"")+(d?d+"M":"")+(c?c+"S":""):"P0D"}var vi,bi;bi=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,i=0;i68?1900:2e3)};var ba=H("FullYear",!0);Z("w",["ww",2],"wo","week"),Z("W",["WW",2],"Wo","isoWeek"),W("week","w"),W("isoWeek","W"),z("week",5),z("isoWeek",5),$("w",Ei),$("ww",Ei,Bi),$("W",Ei),$("WW",Ei,Bi),at(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=_(t)});var ya={dow:0,doy:6};Z("d",0,"do","day"),Z("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),Z("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),Z("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),Z("e",0,0,"weekday"),Z("E",0,0,"isoWeekday"),W("day","d"),W("weekday","e"),W("isoWeekday","E"),z("day",11),z("weekday",11),z("isoWeekday",11),$("d",Ei),$("e",Ei),$("E",Ei),$("dd",function(t,e){return e.weekdaysMinRegex(t)}),$("ddd",function(t,e){return e.weekdaysShortRegex(t)}),$("dddd",function(t,e){return e.weekdaysRegex(t)}),at(["dd","ddd","dddd"],function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:g(n).invalidWeekday=t}),at(["d","e","E"],function(t,e,n,i){e[i]=_(t)});var xa="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ka="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),_a="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),wa=ta,Sa=ta,Ma=ta;Z("H",["HH",2],0,"hour"),Z("h",["hh",2],0,jt),Z("k",["kk",2],0,Gt),Z("hmm",0,0,function(){return""+jt.apply(this)+q(this.minutes(),2)}),Z("hmmss",0,0,function(){return""+jt.apply(this)+q(this.minutes(),2)+q(this.seconds(),2)}),Z("Hmm",0,0,function(){return""+this.hours()+q(this.minutes(),2)}),Z("Hmmss",0,0,function(){return""+this.hours()+q(this.minutes(),2)+q(this.seconds(),2)}),qt("a",!0),qt("A",!1),W("hour","h"),z("hour",13),$("a",Zt),$("A",Zt),$("H",Ei),$("h",Ei),$("HH",Ei,Bi),$("hh",Ei,Bi),$("hmm",Ui),$("hmmss",ji),$("Hmm",Ui),$("Hmmss",ji),it(["H","HH"],oa),it(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),it(["h","hh"],function(t,e,n){e[oa]=_(t),g(n).bigHour=!0}),it("hmm",function(t,e,n){var i=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i)),g(n).bigHour=!0}),it("hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i,2)),e[la]=_(t.substr(a)),g(n).bigHour=!0}),it("Hmm",function(t,e,n){var i=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i))}),it("Hmmss",function(t,e,n){var i=t.length-4,a=t.length-2;e[oa]=_(t.substr(0,i)),e[sa]=_(t.substr(i,2)),e[la]=_(t.substr(a))});var Da,Ca=/[ap]\.?m?\.?/i,Ta=H("Hours",!0),Pa={calendar:Di,longDateFormat:Ci,invalidDate:Ti,ordinal:Pi,ordinalParse:Ii,relativeTime:Ai,months:ga,monthsShort:ma,week:ya,weekdays:xa,weekdaysMin:_a,weekdaysShort:ka,meridiemParse:Ca},Ia={},Aa={},Fa=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Oa=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ra=/Z|[+-]\d\d(?::?\d\d)?/,La=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Va=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Wa=/^\/?Date\((\-?\d+)/i; +t.createFromInputFallback=M("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),t.ISO_8601=function(){};var Ya=M("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var t=xe.apply(null,arguments);return this.isValid()&&t.isValid()?tthis?this:t:p()}),za=function(){return Date.now?Date.now():+new Date};Ce("Z",":"),Ce("ZZ",""),$("Z",Qi),$("ZZ",Qi),it(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=Te(Qi,t)});var Na=/([\+\-]|\d\d)/gi;t.updateOffset=function(){};var Ha=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Ea=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;He.fn=Se.prototype;var Ua=Ge(1,"add"),ja=Ge(-1,"subtract");t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",t.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Ga=M("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});Z(0,["gg",2],0,function(){return this.weekYear()%100}),Z(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Tn("gggg","weekYear"),Tn("ggggg","weekYear"),Tn("GGGG","isoWeekYear"),Tn("GGGGG","isoWeekYear"),W("weekYear","gg"),W("isoWeekYear","GG"),z("weekYear",1),z("isoWeekYear",1),$("G",Ji),$("g",Ji),$("GG",Ei,Bi),$("gg",Ei,Bi),$("GGGG",qi,Ni),$("gggg",qi,Ni),$("GGGGG",Zi,Hi),$("ggggg",Zi,Hi),at(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,i){e[i.substr(0,2)]=_(t)}),at(["gg","GG"],function(e,n,i,a){n[a]=t.parseTwoDigitYear(e)}),Z("Q",0,"Qo","quarter"),W("quarter","Q"),z("quarter",7),$("Q",Yi),it("Q",function(t,e){e[aa]=3*(_(t)-1)}),Z("D",["DD",2],"Do","date"),W("date","D"),z("date",9),$("D",Ei),$("DD",Ei,Bi),$("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),it(["D","DD"],ra),it("Do",function(t,e){e[ra]=_(t.match(Ei)[0],10)});var qa=H("Date",!0);Z("DDD",["DDDD",3],"DDDo","dayOfYear"),W("dayOfYear","DDD"),z("dayOfYear",4),$("DDD",Gi),$("DDDD",zi),it(["DDD","DDDD"],function(t,e,n){n._dayOfYear=_(t)}),Z("m",["mm",2],0,"minute"),W("minute","m"),z("minute",14),$("m",Ei),$("mm",Ei,Bi),it(["m","mm"],sa);var Za=H("Minutes",!1);Z("s",["ss",2],0,"second"),W("second","s"),z("second",15),$("s",Ei),$("ss",Ei,Bi),it(["s","ss"],la);var Xa=H("Seconds",!1);Z("S",0,0,function(){return~~(this.millisecond()/100)}),Z(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Z(0,["SSS",3],0,"millisecond"),Z(0,["SSSS",4],0,function(){return 10*this.millisecond()}),Z(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),Z(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),Z(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),Z(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),Z(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),W("millisecond","ms"),z("millisecond",16),$("S",Gi,Yi),$("SS",Gi,Bi),$("SSS",Gi,zi);var Ja;for(Ja="SSSS";Ja.length<=9;Ja+="S")$(Ja,Xi);for(Ja="S";Ja.length<=9;Ja+="S")it(Ja,Wn);var Ka=H("Milliseconds",!1);Z("z",0,0,"zoneAbbr"),Z("zz",0,0,"zoneName");var Qa=y.prototype;Qa.add=Ua,Qa.calendar=Xe,Qa.clone=Je,Qa.diff=an,Qa.endOf=vn,Qa.format=un,Qa.from=dn,Qa.fromNow=cn,Qa.to=hn,Qa.toNow=fn,Qa.get=j,Qa.invalidAt=Dn,Qa.isAfter=Ke,Qa.isBefore=Qe,Qa.isBetween=$e,Qa.isSame=tn,Qa.isSameOrAfter=en,Qa.isSameOrBefore=nn,Qa.isValid=Sn,Qa.lang=Ga,Qa.locale=gn,Qa.localeData=mn,Qa.max=Ba,Qa.min=Ya,Qa.parsingFlags=Mn,Qa.set=G,Qa.startOf=pn,Qa.subtract=ja,Qa.toArray=kn,Qa.toObject=_n,Qa.toDate=xn,Qa.toISOString=sn,Qa.inspect=ln,Qa.toJSON=wn,Qa.toString=on,Qa.unix=yn,Qa.valueOf=bn,Qa.creationData=Cn,Qa.year=ba,Qa.isLeapYear=yt,Qa.weekYear=Pn,Qa.isoWeekYear=In,Qa.quarter=Qa.quarters=Ln,Qa.month=ht,Qa.daysInMonth=ft,Qa.week=Qa.weeks=Pt,Qa.isoWeek=Qa.isoWeeks=It,Qa.weeksInYear=Fn,Qa.isoWeeksInYear=An,Qa.date=qa,Qa.day=Qa.days=Yt,Qa.weekday=Bt,Qa.isoWeekday=zt,Qa.dayOfYear=Vn,Qa.hour=Qa.hours=Ta,Qa.minute=Qa.minutes=Za,Qa.second=Qa.seconds=Xa,Qa.millisecond=Qa.milliseconds=Ka,Qa.utcOffset=Ae,Qa.utc=Oe,Qa.local=Re,Qa.parseZone=Le,Qa.hasAlignedHourOffset=Ve,Qa.isDST=We,Qa.isLocal=Be,Qa.isUtcOffset=ze,Qa.isUtc=Ne,Qa.isUTC=Ne,Qa.zoneAbbr=Yn,Qa.zoneName=Bn,Qa.dates=M("dates accessor is deprecated. Use date instead.",qa),Qa.months=M("months accessor is deprecated. Use month instead",ht),Qa.years=M("years accessor is deprecated. Use year instead",ba),Qa.zone=M("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Fe),Qa.isDSTShifted=M("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ye);var $a=I.prototype;$a.calendar=A,$a.longDateFormat=F,$a.invalidDate=O,$a.ordinal=R,$a.preparse=Hn,$a.postformat=Hn,$a.relativeTime=L,$a.pastFuture=V,$a.set=T,$a.months=st,$a.monthsShort=lt,$a.monthsParse=dt,$a.monthsRegex=mt,$a.monthsShortRegex=gt,$a.week=Dt,$a.firstDayOfYear=Tt,$a.firstDayOfWeek=Ct,$a.weekdays=Ot,$a.weekdaysMin=Lt,$a.weekdaysShort=Rt,$a.weekdaysParse=Wt,$a.weekdaysRegex=Nt,$a.weekdaysShortRegex=Ht,$a.weekdaysMinRegex=Et,$a.isPM=Xt,$a.meridiem=Jt,te("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,n=1===_(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),t.lang=M("moment.lang is deprecated. Use moment.locale instead.",te),t.langData=M("moment.langData is deprecated. Use moment.localeData instead.",ie);var tr=Math.abs,er=si("ms"),nr=si("s"),ir=si("m"),ar=si("h"),rr=si("d"),or=si("w"),sr=si("M"),lr=si("y"),ur=ui("milliseconds"),dr=ui("seconds"),cr=ui("minutes"),hr=ui("hours"),fr=ui("days"),gr=ui("months"),mr=ui("years"),pr=Math.round,vr={s:45,m:45,h:22,d:26,M:11},br=Math.abs,yr=Se.prototype;return yr.abs=Kn,yr.add=$n,yr.subtract=ti,yr.as=ri,yr.asMilliseconds=er,yr.asSeconds=nr,yr.asMinutes=ir,yr.asHours=ar,yr.asDays=rr,yr.asWeeks=or,yr.asMonths=sr,yr.asYears=lr,yr.valueOf=oi,yr._bubble=ni,yr.get=li,yr.milliseconds=ur,yr.seconds=dr,yr.minutes=cr,yr.hours=hr,yr.days=fr,yr.weeks=di,yr.months=gr,yr.years=mr,yr.humanize=mi,yr.toISOString=pi,yr.toString=pi,yr.toJSON=pi,yr.locale=gn,yr.localeData=mn,yr.toIsoString=M("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",pi),yr.lang=Ga,Z("X",0,0,"unix"),Z("x",0,0,"valueOf"),$("x",Ji),$("X",$i),it("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),it("x",function(t,e,n){n._d=new Date(_(t))}),t.version="2.17.1",i(xe),t.fn=Qa,t.min=_e,t.max=we,t.now=za,t.utc=h,t.unix=zn,t.months=Gn,t.isDate=l,t.locale=te,t.invalid=p,t.duration=He,t.isMoment=x,t.weekdays=Zn,t.parseZone=Nn,t.localeData=ie,t.isDuration=Me,t.monthsShort=qn,t.weekdaysMin=Jn,t.defineLocale=ee,t.updateLocale=ne,t.locales=ae,t.weekdaysShort=Xn,t.normalizeUnits=Y,t.relativeTimeRounding=fi,t.relativeTimeThreshold=gi,t.calendarFormat=Ze,t.prototype=Qa,t})},{}],7:[function(t,e,n){var i=t(28)();t(26)(i),t(42)(i),t(22)(i),t(31)(i),t(25)(i),t(21)(i),t(23)(i),t(24)(i),t(29)(i),t(33)(i),t(34)(i),t(32)(i),t(35)(i),t(30)(i),t(27)(i),t(36)(i),t(37)(i),t(38)(i),t(39)(i),t(40)(i),t(45)(i),t(43)(i),t(44)(i),t(46)(i),t(47)(i),t(48)(i),t(15)(i),t(16)(i),t(17)(i),t(18)(i),t(19)(i),t(20)(i),t(8)(i),t(9)(i),t(10)(i),t(11)(i),t(12)(i),t(13)(i),t(14)(i),window.Chart=e.exports=i},{10:10,11:11,12:12,13:13,14:14,15:15,16:16,17:17,18:18,19:19,20:20,21:21,22:22,23:23,24:24,25:25,26:26,27:27,28:28,29:29,30:30,31:31,32:32,33:33,34:34,35:35,36:36,37:37,38:38,39:39,40:40,42:42,43:43,44:44,45:45,46:46,47:47,48:48,8:8,9:9}],8:[function(t,e,n){"use strict";e.exports=function(t){t.Bar=function(e,n){return n.type="bar",new t(e,n)}}},{}],9:[function(t,e,n){"use strict";e.exports=function(t){t.Bubble=function(e,n){return n.type="bubble",new t(e,n)}}},{}],10:[function(t,e,n){"use strict";e.exports=function(t){t.Doughnut=function(e,n){return n.type="doughnut",new t(e,n)}}},{}],11:[function(t,e,n){"use strict";e.exports=function(t){t.Line=function(e,n){return n.type="line",new t(e,n)}}},{}],12:[function(t,e,n){"use strict";e.exports=function(t){t.PolarArea=function(e,n){return n.type="polarArea",new t(e,n)}}},{}],13:[function(t,e,n){"use strict";e.exports=function(t){t.Radar=function(e,n){return n.type="radar",new t(e,n)}}},{}],14:[function(t,e,n){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,n){return n.type="scatter",new t(e,n)}}},{}],15:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({dataElementType:t.elements.Rectangle,initialize:function(e,n){t.DatasetController.prototype.initialize.call(this,e,n);var i=this,a=i.getMeta(),r=i.getDataset();a.stack=r.stack,a.bar=!0},getStackCount:function(){var t=this,n=t.getMeta(),i=t.getScaleForId(n.yAxisID),a=[];return e.each(t.chart.data.datasets,function(e,n){var r=t.chart.getDatasetMeta(n);r.bar&&t.chart.isDatasetVisible(n)&&(i.options.stacked===!1||i.options.stacked===!0&&a.indexOf(r.stack)===-1||void 0===i.options.stacked&&(void 0===r.stack||a.indexOf(r.stack)===-1))&&a.push(r.stack)},t),a.length},update:function(t){var n=this;e.each(n.getMeta().data,function(e,i){n.updateElement(e,i,t)},n)},updateElement:function(t,n,i){var a=this,r=a.getMeta(),o=a.getScaleForId(r.xAxisID),s=a.getScaleForId(r.yAxisID),l=s.getBasePixel(),u=a.chart.options.elements.rectangle,d=t.custom||{},c=a.getDataset();t._xScale=o,t._yScale=s,t._datasetIndex=a.index,t._index=n;var h=a.getRuler(n);t._model={x:a.calculateBarX(n,a.index,h),y:i?l:a.calculateBarY(n,a.index),label:a.chart.data.labels[n],datasetLabel:c.label,horizontal:!1,base:i?l:a.calculateBarBase(a.index,n),width:a.calculateBarWidth(h),backgroundColor:d.backgroundColor?d.backgroundColor:e.getValueAtIndexOrDefault(c.backgroundColor,n,u.backgroundColor),borderSkipped:d.borderSkipped?d.borderSkipped:u.borderSkipped,borderColor:d.borderColor?d.borderColor:e.getValueAtIndexOrDefault(c.borderColor,n,u.borderColor),borderWidth:d.borderWidth?d.borderWidth:e.getValueAtIndexOrDefault(c.borderWidth,n,u.borderWidth)},t.pivot()},calculateBarBase:function(t,e){var n=this,i=n.getMeta(),a=n.getScaleForId(i.yAxisID),r=a.getBaseValue(),o=r;if(a.options.stacked===!0||void 0===a.options.stacked&&void 0!==i.stack){for(var s=n.chart,l=s.data.datasets,u=Number(l[t].data[e]),d=0;d0&&(t[0].yLabel?n=t[0].yLabel:e.labels.length>0&&t[0].index');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var r=t.getDatasetMeta(0),o=n.datasets[0],s=r.data[a],l=s&&s.custom||{},u=e.getValueAtIndexOrDefault,d=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:u(o.backgroundColor,a,d.backgroundColor),h=l.borderColor?l.borderColor:u(o.borderColor,a,d.borderColor),f=l.borderWidth?l.borderWidth:u(o.borderWidth,a,d.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(o.data[a])||r.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n=Math.PI?-1:g<-Math.PI?1:0);var m=g+f,p={x:Math.cos(g),y:Math.sin(g)},v={x:Math.cos(m),y:Math.sin(m)},b=g<=0&&0<=m||g<=2*Math.PI&&2*Math.PI<=m,y=g<=.5*Math.PI&&.5*Math.PI<=m||g<=2.5*Math.PI&&2.5*Math.PI<=m,x=g<=-Math.PI&&-Math.PI<=m||g<=Math.PI&&Math.PI<=m,k=g<=.5*-Math.PI&&.5*-Math.PI<=m||g<=1.5*Math.PI&&1.5*Math.PI<=m,_=h/100,w={x:x?-1:Math.min(p.x*(p.x<0?1:_),v.x*(v.x<0?1:_)),y:k?-1:Math.min(p.y*(p.y<0?1:_),v.y*(v.y<0?1:_))},S={x:b?1:Math.max(p.x*(p.x>0?1:_),v.x*(v.x>0?1:_)),y:y?1:Math.max(p.y*(p.y>0?1:_),v.y*(v.y>0?1:_))},M={width:.5*(S.x-w.x),height:.5*(S.y-w.y)};u=Math.min(s/M.width,l/M.height),d={x:(S.x+w.x)*-.5,y:(S.y+w.y)*-.5}}i.borderWidth=n.getMaxBorderWidth(c.data),i.outerRadius=Math.max((u-i.borderWidth)/2,0),i.innerRadius=Math.max(h?i.outerRadius/100*h:0,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),i.offsetX=d.x*i.outerRadius,i.offsetY=d.y*i.outerRadius,c.total=n.calculateTotal(),n.outerRadius=i.outerRadius-i.radiusLength*n.getRingIndex(n.index),n.innerRadius=Math.max(n.outerRadius-i.radiusLength,0),e.each(c.data,function(e,i){n.updateElement(e,i,t)})},updateElement:function(t,n,i){var a=this,r=a.chart,o=r.chartArea,s=r.options,l=s.animation,u=(o.left+o.right)/2,d=(o.top+o.bottom)/2,c=s.rotation,h=s.rotation,f=a.getDataset(),g=i&&l.animateRotate?0:t.hidden?0:a.calculateCircumference(f.data[n])*(s.circumference/(2*Math.PI)),m=i&&l.animateScale?0:a.innerRadius,p=i&&l.animateScale?0:a.outerRadius,v=e.getValueAtIndexOrDefault;e.extend(t,{_datasetIndex:a.index,_index:n,_model:{x:u+r.offsetX,y:d+r.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:p,innerRadius:m,label:v(f.label,n,r.data.labels[n])}});var b=t._model;this.removeHoverStyle(t),i&&l.animateRotate||(0===n?b.startAngle=s.rotation:b.startAngle=a.getMeta().data[n-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,n=this.getDataset(),i=this.getMeta(),a=0;return e.each(i.data,function(e,i){t=n.data[i],isNaN(t)||e.hidden||(a+=Math.abs(t))}),a},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0},getMaxBorderWidth:function(t){for(var e,n,i=0,a=this.index,r=t.length,o=0;oi?e:i,i=n>i?n:i;return i}})}},{}],18:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){return n.getValueOrDefault(t.showLine,e.showLines)}var n=t.helpers;t.defaults.line={showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,update:function(t){var i,a,r,o=this,s=o.getMeta(),l=s.dataset,u=s.data||[],d=o.chart.options,c=d.elements.line,h=o.getScaleForId(s.yAxisID),f=o.getDataset(),g=e(f,d);for(g&&(r=l.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),l._scale=h,l._datasetIndex=o.index,l._children=u,l._model={spanGaps:f.spanGaps?f.spanGaps:d.spanGaps,tension:r.tension?r.tension:n.getValueOrDefault(f.lineTension,c.tension),backgroundColor:r.backgroundColor?r.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:f.borderWidth||c.borderWidth,borderColor:r.borderColor?r.borderColor:f.borderColor||c.borderColor,borderCapStyle:r.borderCapStyle?r.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:r.borderDash?r.borderDash:f.borderDash||c.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:r.fill?r.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:r.steppedLine?r.steppedLine:n.getValueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:r.cubicInterpolationMode?r.cubicInterpolationMode:n.getValueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode),scaleTop:h.top,scaleBottom:h.bottom,scaleZero:h.getBasePixel()},l.pivot()),i=0,a=u.length;i');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var r=0;r'),a[r]&&e.push(a[r]),e.push("");return e.push(""),e.join(""); +},legend:{labels:{generateLabels:function(t){var n=t.data;return n.labels.length&&n.datasets.length?n.labels.map(function(i,a){var r=t.getDatasetMeta(0),o=n.datasets[0],s=r.data[a],l=s.custom||{},u=e.getValueAtIndexOrDefault,d=t.options.elements.arc,c=l.backgroundColor?l.backgroundColor:u(o.backgroundColor,a,d.backgroundColor),h=l.borderColor?l.borderColor:u(o.borderColor,a,d.borderColor),f=l.borderWidth?l.borderWidth:u(o.borderWidth,a,d.borderWidth);return{text:i,fillStyle:c,strokeStyle:h,lineWidth:f,hidden:isNaN(o.data[a])||r.data[a].hidden,index:a}}):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={aspectRatio:1,scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({datasetElementType:t.elements.Line,dataElementType:t.elements.Point,linkScales:e.noop,update:function(t){var n=this,i=n.getMeta(),a=i.dataset,r=i.data,o=a.custom||{},s=n.getDataset(),l=n.chart.options.elements.line,u=n.chart.scale;void 0!==s.tension&&void 0===s.lineTension&&(s.lineTension=s.tension),e.extend(i.dataset,{_datasetIndex:n.index,_children:r,_loop:!0,_model:{tension:o.tension?o.tension:e.getValueOrDefault(s.lineTension,l.tension),backgroundColor:o.backgroundColor?o.backgroundColor:s.backgroundColor||l.backgroundColor,borderWidth:o.borderWidth?o.borderWidth:s.borderWidth||l.borderWidth,borderColor:o.borderColor?o.borderColor:s.borderColor||l.borderColor,fill:o.fill?o.fill:void 0!==s.fill?s.fill:l.fill,borderCapStyle:o.borderCapStyle?o.borderCapStyle:s.borderCapStyle||l.borderCapStyle,borderDash:o.borderDash?o.borderDash:s.borderDash||l.borderDash,borderDashOffset:o.borderDashOffset?o.borderDashOffset:s.borderDashOffset||l.borderDashOffset,borderJoinStyle:o.borderJoinStyle?o.borderJoinStyle:s.borderJoinStyle||l.borderJoinStyle,scaleTop:u.top,scaleBottom:u.bottom,scaleZero:u.getBasePosition()}}),i.dataset.pivot(),e.each(r,function(e,i){n.updateElement(e,i,t)},n),n.updateBezierControlPoints()},updateElement:function(t,n,i){var a=this,r=t.custom||{},o=a.getDataset(),s=a.chart.scale,l=a.chart.options.elements.point,u=s.getPointPositionForValue(n,o.data[n]);e.extend(t,{_datasetIndex:a.index,_index:n,_scale:s,_model:{x:i?s.xCenter:u.x,y:i?s.yCenter:u.y,tension:r.tension?r.tension:e.getValueOrDefault(o.lineTension,a.chart.options.elements.line.tension),radius:r.radius?r.radius:e.getValueAtIndexOrDefault(o.pointRadius,n,l.radius),backgroundColor:r.backgroundColor?r.backgroundColor:e.getValueAtIndexOrDefault(o.pointBackgroundColor,n,l.backgroundColor),borderColor:r.borderColor?r.borderColor:e.getValueAtIndexOrDefault(o.pointBorderColor,n,l.borderColor),borderWidth:r.borderWidth?r.borderWidth:e.getValueAtIndexOrDefault(o.pointBorderWidth,n,l.borderWidth),pointStyle:r.pointStyle?r.pointStyle:e.getValueAtIndexOrDefault(o.pointStyle,n,l.pointStyle),hitRadius:r.hitRadius?r.hitRadius:e.getValueAtIndexOrDefault(o.hitRadius,n,l.hitRadius)}}),t._model.skip=r.skip?r.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,n=this.getMeta();e.each(n.data,function(i,a){var r=i._model,o=e.splineCurve(e.previousItem(n.data,a,!0)._model,r,e.nextItem(n.data,a,!0)._model,r.tension);r.controlPointPreviousX=Math.max(Math.min(o.previous.x,t.right),t.left),r.controlPointPreviousY=Math.max(Math.min(o.previous.y,t.bottom),t.top),r.controlPointNextX=Math.max(Math.min(o.next.x,t.right),t.left),r.controlPointNextY=Math.max(Math.min(o.next.y,t.bottom),t.top),i.pivot()})},draw:function(t){var n=this.getMeta(),i=t||1;e.each(n.data,function(t){t.transition(i)}),n.dataset.transition(i).draw(),e.each(n.data,function(t){t.draw()})},setHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,r=t._model;r.radius=i.hoverRadius?i.hoverRadius:e.getValueAtIndexOrDefault(n.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),r.backgroundColor=i.hoverBackgroundColor?i.hoverBackgroundColor:e.getValueAtIndexOrDefault(n.pointHoverBackgroundColor,a,e.getHoverColor(r.backgroundColor)),r.borderColor=i.hoverBorderColor?i.hoverBorderColor:e.getValueAtIndexOrDefault(n.pointHoverBorderColor,a,e.getHoverColor(r.borderColor)),r.borderWidth=i.hoverBorderWidth?i.hoverBorderWidth:e.getValueAtIndexOrDefault(n.pointHoverBorderWidth,a,r.borderWidth)},removeHoverStyle:function(t){var n=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},a=t._index,r=t._model,o=this.chart.options.elements.point;r.radius=i.radius?i.radius:e.getValueAtIndexOrDefault(n.radius,a,o.radius),r.backgroundColor=i.backgroundColor?i.backgroundColor:e.getValueAtIndexOrDefault(n.pointBackgroundColor,a,o.backgroundColor),r.borderColor=i.borderColor?i.borderColor:e.getValueAtIndexOrDefault(n.pointBorderColor,a,o.borderColor),r.borderWidth=i.borderWidth?i.borderWidth:e.getValueAtIndexOrDefault(n.pointBorderWidth,a,o.borderWidth)}})}},{}],21:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,n,i){var a=this;i||(t.animating=!0);for(var r=0;r1&&(n=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1);for(var i=0;it.animations[i].animationObject.numSteps&&(t.animations[i].animationObject.currentStep=t.animations[i].animationObject.numSteps),t.animations[i].animationObject.render(t.animations[i].chartInstance,t.animations[i].animationObject),t.animations[i].animationObject.onAnimationProgress&&t.animations[i].animationObject.onAnimationProgress.call&&t.animations[i].animationObject.onAnimationProgress.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].animationObject.currentStep===t.animations[i].animationObject.numSteps?(t.animations[i].animationObject.onAnimationComplete&&t.animations[i].animationObject.onAnimationComplete.call&&t.animations[i].animationObject.onAnimationComplete.call(t.animations[i].chartInstance,t.animations[i]),t.animations[i].chartInstance.animating=!1,t.animations.splice(i,1)):++i;var a=Date.now(),r=(a-e)/t.frameDuration;t.dropFrames+=r,t.animations.length>0&&t.requestAnimationFrame()}}}},{}],22:[function(t,e,n){"use strict";e.exports=function(t){var e=t.canvasHelpers={};e.drawPoint=function(e,n,i,a,r){var o,s,l,u,d,c;if("object"==typeof n&&(o=n.toString(),"[object HTMLImageElement]"===o||"[object HTMLCanvasElement]"===o))return void e.drawImage(n,a-n.width/2,r-n.height/2);if(!(isNaN(i)||i<=0)){switch(n){default:e.beginPath(),e.arc(a,r,i,0,2*Math.PI),e.closePath(),e.fill();break;case"triangle":e.beginPath(),s=3*i/Math.sqrt(3),d=s*Math.sqrt(3)/2,e.moveTo(a-s/2,r+d/3),e.lineTo(a+s/2,r+d/3),e.lineTo(a,r-2*d/3),e.closePath(),e.fill();break;case"rect":c=1/Math.SQRT2*i,e.beginPath(),e.fillRect(a-c,r-c,2*c,2*c),e.strokeRect(a-c,r-c,2*c,2*c);break;case"rectRounded":var h=i/Math.SQRT2,f=a-h,g=r-h,m=Math.SQRT2*i;t.helpers.drawRoundedRectangle(e,f,g,m,m,i/2),e.fill();break;case"rectRot":c=1/Math.SQRT2*i,e.beginPath(),e.moveTo(a-c,r),e.lineTo(a,r+c),e.lineTo(a+c,r),e.lineTo(a,r-c),e.closePath(),e.fill();break;case"cross":e.beginPath(),e.moveTo(a,r+i),e.lineTo(a,r-i),e.moveTo(a-i,r),e.lineTo(a+i,r),e.closePath();break;case"crossRot":e.beginPath(),l=Math.cos(Math.PI/4)*i,u=Math.sin(Math.PI/4)*i,e.moveTo(a-l,r-u),e.lineTo(a+l,r+u),e.moveTo(a-l,r+u),e.lineTo(a+l,r-u),e.closePath();break;case"star":e.beginPath(),e.moveTo(a,r+i),e.lineTo(a,r-i),e.moveTo(a-i,r),e.lineTo(a+i,r),l=Math.cos(Math.PI/4)*i,u=Math.sin(Math.PI/4)*i,e.moveTo(a-l,r-u),e.lineTo(a+l,r+u),e.moveTo(a-l,r+u),e.lineTo(a+l,r-u),e.closePath();break;case"line":e.beginPath(),e.moveTo(a-i,r),e.lineTo(a+i,r),e.closePath();break;case"dash":e.beginPath(),e.moveTo(a,r),e.lineTo(a+i,r),e.closePath()}e.stroke()}},e.clipArea=function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},e.unclipArea=function(t){t.restore()}}},{}],23:[function(t,e,n){"use strict";e.exports=function(t){function e(e){e=e||{};var n=e.data=e.data||{};return n.datasets=n.datasets||[],n.labels=n.labels||[],e.options=i.configMerge(t.defaults.global,t.defaults[e.type],e.options||{}),e}function n(t){var e=t.options;e.scale?t.scale.options=e.scale:e.scales&&e.scales.xAxes.concat(e.scales.yAxes).forEach(function(e){t.scales[e.id].options=e}),t.tooltip._options=e.tooltips}var i=t.helpers,a=t.plugins,r=t.platform;t.types={},t.instances={},t.controllers={},t.Controller=function(n,a,o){var s=this;a=e(a);var l=r.acquireContext(n,a),u=l&&l.canvas,d=u&&u.height,c=u&&u.width;return o.ctx=l,o.canvas=u,o.config=a,o.width=c,o.height=d,o.aspectRatio=d?c/d:null,s.id=i.uid(),s.chart=o,s.config=a,s.options=a.options,s._bufferedRender=!1,t.instances[s.id]=s,Object.defineProperty(s,"data",{get:function(){return s.config.data}}),l&&u?(s.initialize(),s.update(),s):(console.error("Failed to create chart: can't acquire context from the given item"),s)},i.extend(t.Controller.prototype,{initialize:function(){var t=this;return a.notify(t,"beforeInit"),i.retinaScale(t.chart),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildScales(),t.initToolTip(),a.notify(t,"afterInit"),t},clear:function(){return i.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var e=this,n=e.chart,r=e.options,o=n.canvas,s=r.maintainAspectRatio&&n.aspectRatio||null,l=Math.floor(i.getMaximumWidth(o)),u=Math.floor(s?l/s:i.getMaximumHeight(o));if((n.width!==l||n.height!==u)&&(o.width=n.width=l,o.height=n.height=u,o.style.width=l+"px",o.style.height=u+"px",i.retinaScale(n),!t)){var d={width:l,height:u};a.notify(e,"resize",[d]),e.options.onResize&&e.options.onResize(e,d),e.stop(),e.update(e.options.responsiveAnimationDuration)}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;i.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),i.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),n&&(n.id=n.id||"scale")},buildScales:function(){var e=this,n=e.options,a=e.scales={},r=[];n.scales&&(r=r.concat((n.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category"}}),(n.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear"}}))),n.scale&&r.push({options:n.scale,dtype:"radialLinear",isDefault:!0}),i.each(r,function(n){var r=n.options,o=i.getValueOrDefault(r.type,n.dtype),s=t.scaleService.getScaleConstructor(o);if(s){var l=new s({id:r.id,options:r,ctx:e.chart.ctx,chart:e});a[l.id]=l,n.isDefault&&(e.scale=l)}}),t.scaleService.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e=this,n=[],a=[];if(i.each(e.data.datasets,function(i,r){var o=e.getDatasetMeta(r);o.type||(o.type=i.type||e.config.type),n.push(o.type),o.controller?o.controller.updateIndex(r):(o.controller=new t.controllers[o.type](e,r),a.push(o.controller))},e),n.length>1)for(var r=1;r0||(a.forEach(function(e){delete t[e]}),delete t._chartjs)}}var i=t.helpers,a=["push","pop","shift","splice","unshift"];t.DatasetController=function(t,e){this.initialize(t,e)},i.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),n=t.getDataset();null===e.xAxisID&&(e.xAxisID=n.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=n.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},destroy:function(){this._data&&n(this._data,this)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,n=e.dataElementType;return n&&new n({_chart:e.chart.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,n=this,i=n.getMeta(),a=n.getDataset().data||[],r=i.data;for(t=0,e=a.length;ti&&t.insertElements(i,a-i)},insertElements:function(t,e){for(var n=0;n=0;a--)e.call(n,t[a],a);else for(a=0;a=i[n].length||!i[n][a].type?i[n].push(r.configMerge(s,e)):e.type&&e.type!==i[n][a].type?i[n][a]=r.configMerge(i[n][a],s,e):i[n][a]=r.configMerge(i[n][a],e)}):(i[n]=[],r.each(e,function(e){var a=r.getValueOrDefault(e.type,"xAxes"===n?"category":"linear");i[n].push(r.configMerge(t.scaleService.getScaleDefaults(a),e))})):i.hasOwnProperty(n)&&"object"==typeof i[n]&&null!==i[n]&&"object"==typeof e?i[n]=r.configMerge(i[n],e):i[n]=e}),i},r.getValueAtIndexOrDefault=function(t,e,n){return void 0===t||null===t?n:r.isArray(t)?e=0;i--){var a=t[i];if(e(a))return a}},r.inherits=function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=r.inherits,t&&r.extend(n.prototype,t),n.__super__=e.prototype,n},r.noop=function(){},r.uid=function(){var t=0;return function(){return t++}}(),r.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},r.almostEquals=function(t,e,n){return Math.abs(t-e)t},r.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},r.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},r.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return t=+t,0===t||isNaN(t)?t:t>0?1:-1},r.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},r.toRadians=function(t){return t*(Math.PI/180)},r.toDegrees=function(t){return t*(180/Math.PI)},r.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},r.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},r.aliasPixel=function(t){return t%2===0?0:.5},r.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l);u=isNaN(u)?0:u,d=isNaN(d)?0:d;var c=i*u,h=i*d;return{previous:{x:r.x-c*(o.x-a.x),y:r.y-c*(o.y-a.y)},next:{x:r.x+h*(o.x-a.x),y:r.y+h*(o.y-a.y)}}},r.EPSILON=Number.EPSILON||1e-14,r.splineCurveMonotone=function(t){var e,n,i,a,o=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),s=o.length;for(e=0;e0?o[e-1]:null,a=e0?o[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},r.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},r.niceNum=function(t,e){var n,i=Math.floor(r.log10(t)),a=t/Math.pow(10,i);return n=e?a<1.5?1:a<3?2:a<7?5:10:a<=1?1:a<=2?2:a<=5?5:10,n*Math.pow(10,i)};var o=r.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===(t/=1)?1:(n||(n=.3),i0?(n=l[0].clientX,i=l[0].clientY):(n=a.clientX,i=a.clientY);var u=parseFloat(r.getStyle(o,"padding-left")),d=parseFloat(r.getStyle(o,"padding-top")),c=parseFloat(r.getStyle(o,"padding-right")),h=parseFloat(r.getStyle(o,"padding-bottom")),f=s.right-s.left-u-c,g=s.bottom-s.top-d-h;return n=Math.round((n-s.left-u)/f*o.width/e.currentDevicePixelRatio),i=Math.round((i-s.top-d)/g*o.height/e.currentDevicePixelRatio),{x:n,y:i}},r.addEvent=function(t,e,n){t.addEventListener?t.addEventListener(e,n):t.attachEvent?t.attachEvent("on"+e,n):t["on"+e]=n},r.removeEvent=function(t,e,n){t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent?t.detachEvent("on"+e,n):t["on"+e]=r.noop},r.getConstraintWidth=function(t){return a(t,"max-width","clientWidth")},r.getConstraintHeight=function(t){return a(t,"max-height","clientHeight")},r.getMaximumWidth=function(t){var e=t.parentNode,n=parseInt(r.getStyle(e,"padding-left"),10),i=parseInt(r.getStyle(e,"padding-right"),10),a=e.clientWidth-n-i,o=r.getConstraintWidth(t);return isNaN(o)?a:Math.min(a,o)},r.getMaximumHeight=function(t){var e=t.parentNode,n=parseInt(r.getStyle(e,"padding-top"),10),i=parseInt(r.getStyle(e,"padding-bottom"),10),a=e.clientHeight-n-i,o=r.getConstraintHeight(t);return isNaN(o)?a:Math.min(a,o)},r.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},r.retinaScale=function(t){var e=t.currentDevicePixelRatio=window.devicePixelRatio||1;if(1!==e){var n=t.canvas,i=t.height,a=t.width;n.height=i*e,n.width=a*e,t.ctx.scale(e,e),n.style.height=i+"px",n.style.width=a+"px"}},r.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},r.fontString=function(t,e,n){return e+" "+t+"px "+n},r.longestText=function(t,e,n,i){i=i||{};var a=i.data=i.data||{},o=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},o=i.garbageCollect=[],i.font=e),t.font=e;var s=0;r.each(n,function(e){void 0!==e&&null!==e&&r.isArray(e)!==!0?s=r.measureText(t,a,o,s,e):r.isArray(e)&&r.each(e,function(e){void 0===e||null===e||r.isArray(e)||(s=r.measureText(t,a,o,s,e))})});var l=o.length/2;if(l>n.length){for(var u=0;ui&&(i=r),i},r.numberOfLabelLines=function(t){var e=1;return r.each(t,function(t){r.isArray(t)&&t.length>e&&(e=t.length)}),e},r.drawRoundedRectangle=function(t,e,n,i,a,r){t.beginPath(),t.moveTo(e+r,n),t.lineTo(e+i-r,n),t.quadraticCurveTo(e+i,n,e+i,n+r),t.lineTo(e+i,n+a-r),t.quadraticCurveTo(e+i,n+a,e+i-r,n+a),t.lineTo(e+r,n+a),t.quadraticCurveTo(e,n+a,e,n+a-r),t.lineTo(e,n+r),t.quadraticCurveTo(e,n,e+r,n),t.closePath()},r.color=function(e){return i?i(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.error("Color.js not found!"),e)},r.isArray=Array.isArray?function(t){return Array.isArray(t)}:function(t){return"[object Array]"===Object.prototype.toString.call(t)},r.arrayEquals=function(t,e){var n,i,a,o;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;n0&&(s=t.getDatasetMeta(s[0]._datasetIndex).data),s},"x-axis":function(t,e){return r(t,e,!0)},point:function(t,n){var a=e(n,t.chart);return i(t,a)},nearest:function(t,n,i){var r=e(n,t.chart),o=a(t,r,i.intersect);return o.length>1&&o.sort(function(t,e){var n=t.getArea(),i=e.getArea(),a=n-i;return 0===a&&(a=t._datasetIndex-e._datasetIndex),a}),o.slice(0,1)},x:function(t,i,a){var r=e(i,t.chart),o=[],s=!1;return n(t,function(t){t.inXRange(r.x)&&o.push(t),t.inRange(r.x,r.y)&&(s=!0)}),a.intersect&&!s&&(o=[]),o},y:function(t,i,a){var r=e(i,t.chart),o=[],s=!1;return n(t,function(t){t.inYRange(r.y)&&o.push(t),t.inRange(r.x,r.y)&&(s=!0)}),a.intersect&&!s&&(o=[]),o}}}}},{}],28:[function(t,e,n){"use strict";e.exports=function(){var t=function(e,n){return this.controller=new t.Controller(e,n,this),this.controller};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
    ');for(var n=0;n'),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push("
"),e.join("")}}},t.Chart=t,t}},{}],29:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,n,i){function a(t){var e,n=t.isHorizontal();n?(e=t.update(t.options.fullWidth?y:M,S),D-=e.height):(e=t.update(w,_),M-=e.width),C.push({horizontal:n,minSize:e,box:t})}function r(t){var n=e.findNextWhere(C,function(e){return e.box===t});if(n)if(t.isHorizontal()){var i={left:Math.max(F,T),right:Math.max(O,P),top:0,bottom:0};t.update(t.options.fullWidth?y:M,x/2,i)}else t.update(n.minSize.width,D)}function o(t){var n=e.findNextWhere(C,function(e){return e.box===t}),i={left:0,right:0,top:R,bottom:L};n&&t.update(n.minSize.width,D,i)}function s(t){t.isHorizontal()?(t.left=t.options.fullWidth?d:F,t.right=t.options.fullWidth?n-c:F+M,t.top=N,t.bottom=N+t.height,N=t.bottom):(t.left=z,t.right=z+t.width,t.top=R,t.bottom=R+D,z=t.right)}if(t){var l=t.options.layout,u=l?l.padding:null,d=0,c=0,h=0,f=0;isNaN(u)?(d=u.left||0,c=u.right||0,h=u.top||0,f=u.bottom||0):(d=u,c=u,h=u,f=u);var g=e.where(t.boxes,function(t){return"left"===t.options.position}),m=e.where(t.boxes,function(t){return"right"===t.options.position}),p=e.where(t.boxes,function(t){return"top"===t.options.position}),v=e.where(t.boxes,function(t){return"bottom"===t.options.position}),b=e.where(t.boxes,function(t){return"chartArea"===t.options.position});p.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),v.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var y=n-d-c,x=i-h-f,k=y/2,_=x/2,w=(n-k)/(g.length+m.length),S=(i-_)/(p.length+v.length),M=y,D=x,C=[];e.each(g.concat(m,p,v),a);var T=0,P=0,I=0,A=0;e.each(p.concat(v),function(t){if(t.getPadding){var e=t.getPadding();T=Math.max(T,e.left),P=Math.max(P,e.right)}}),e.each(g.concat(m),function(t){if(t.getPadding){var e=t.getPadding();I=Math.max(I,e.top),A=Math.max(A,e.bottom)}});var F=d,O=c,R=h,L=f;e.each(g.concat(m),r),e.each(g,function(t){F+=t.width}),e.each(m,function(t){O+=t.width}),e.each(p.concat(v),r),e.each(p,function(t){R+=t.height}),e.each(v,function(t){L+=t.height}),e.each(g.concat(m),o),F=d,O=c,R=h,L=f,e.each(g,function(t){F+=t.width}),e.each(m,function(t){O+=t.width}),e.each(p,function(t){R+=t.height}),e.each(v,function(t){L+=t.height});var V=Math.max(T-F,0);F+=V,O+=Math.max(P-O,0);var W=Math.max(I-R,0);R+=W,L+=Math.max(A-L,0);var Y=i-R-L,B=n-F-O;B===M&&Y===D||(e.each(g,function(t){t.height=Y}),e.each(m,function(t){t.height=Y}),e.each(p,function(t){t.options.fullWidth||(t.width=B)}),e.each(v,function(t){t.options.fullWidth||(t.width=B)}),D=Y,M=B);var z=d+V,N=h+W;e.each(g.concat(p),s),z+=M,N+=D,e.each(m,s),e.each(v,s),t.chartArea={left:F,top:R,right:F+M,bottom:R+D},e.each(b,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(M,D)})}}}}},{}],30:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){return t.usePointStyle?e*Math.SQRT2:t.boxWidth}function n(e,n){var i=new t.Legend({ctx:e.chart.ctx,options:n,chart:e});e.legend=i,t.layoutService.addBox(e,i)}var i=t.helpers,a=i.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data;return i.isArray(e.datasets)?e.datasets.map(function(e,n){return{text:e.label,fillStyle:i.isArray(e.backgroundColor)?e.backgroundColor[0]:e.backgroundColor,hidden:!t.isDatasetVisible(n),lineCap:e.borderCapStyle,lineDash:e.borderDash,lineDashOffset:e.borderDashOffset,lineJoin:e.borderJoinStyle,lineWidth:e.borderWidth,strokeStyle:e.borderColor,pointStyle:e.pointStyle,datasetIndex:n}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){i.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:a,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:a,beforeSetDimensions:a,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:a,beforeBuildLabels:a,buildLabels:function(){var t=this,e=t.options.labels,n=e.generateLabels.call(t,t.chart);e.filter&&(n=n.filter(function(n){return e.filter(n,t.chart.data)})),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:a,beforeFit:a,fit:function(){var n=this,a=n.options,r=a.labels,o=a.display,s=n.ctx,l=t.defaults.global,u=i.getValueOrDefault,d=u(r.fontSize,l.defaultFontSize),c=u(r.fontStyle,l.defaultFontStyle),h=u(r.fontFamily,l.defaultFontFamily),f=i.fontString(d,c,h),g=n.legendHitBoxes=[],m=n.minSize,p=n.isHorizontal();if(p?(m.width=n.maxWidth,m.height=o?10:0):(m.width=o?10:0,m.height=n.maxHeight),o)if(s.font=f,p){var v=n.lineWidths=[0],b=n.legendItems.length?d+r.padding:0;s.textAlign="left",s.textBaseline="top",i.each(n.legendItems,function(t,i){var a=e(r,d),o=a+d/2+s.measureText(t.text).width;v[v.length-1]+o+r.padding>=n.width&&(b+=d+r.padding,v[v.length]=n.left),g[i]={left:0,top:0,width:o,height:d},v[v.length-1]+=o+r.padding}),m.height+=b}else{var y=r.padding,x=n.columnWidths=[],k=r.padding,_=0,w=0,S=d+y;i.each(n.legendItems,function(t,n){var i=e(r,d),a=i+d/2+s.measureText(t.text).width;w+S>m.height&&(k+=_+r.padding,x.push(_),_=0,w=0),_=Math.max(_,a),w+=S,g[n]={left:0,top:0,width:a,height:d}}),k+=_,x.push(_),m.width+=k}n.width=m.width,n.height=m.height},afterFit:a,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var n=this,a=n.options,r=a.labels,o=t.defaults.global,s=o.elements.line,l=n.width,u=n.lineWidths;if(a.display){var d,c=n.ctx,h=i.getValueOrDefault,f=h(r.fontColor,o.defaultFontColor),g=h(r.fontSize,o.defaultFontSize),m=h(r.fontStyle,o.defaultFontStyle),p=h(r.fontFamily,o.defaultFontFamily),v=i.fontString(g,m,p);c.textAlign="left",c.textBaseline="top",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=v;var b=e(r,g),y=n.legendHitBoxes,x=function(e,n,i){if(!(isNaN(b)||b<=0)){c.save(),c.fillStyle=h(i.fillStyle,o.defaultColor),c.lineCap=h(i.lineCap,s.borderCapStyle),c.lineDashOffset=h(i.lineDashOffset,s.borderDashOffset),c.lineJoin=h(i.lineJoin,s.borderJoinStyle),c.lineWidth=h(i.lineWidth,s.borderWidth),c.strokeStyle=h(i.strokeStyle,o.defaultColor);var r=0===h(i.lineWidth,s.borderWidth);if(c.setLineDash&&c.setLineDash(h(i.lineDash,s.borderDash)),a.labels&&a.labels.usePointStyle){var l=g*Math.SQRT2/2,u=l/Math.SQRT2,d=e+u,f=n+u;t.canvasHelpers.drawPoint(c,i.pointStyle,l,d,f)}else r||c.strokeRect(e,n,b,g),c.fillRect(e,n,b,g);c.restore()}},k=function(t,e,n,i){c.fillText(n.text,b+g/2+t,e),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(b+g/2+t,e+g/2),c.lineTo(b+g/2+t+i,e+g/2),c.stroke())},_=n.isHorizontal();d=_?{x:n.left+(l-u[0])/2,y:n.top+r.padding,line:0}:{x:n.left+r.padding,y:n.top+r.padding,line:0};var w=g+r.padding;i.each(n.legendItems,function(t,e){var i=c.measureText(t.text).width,a=b+g/2+i,o=d.x,s=d.y;_?o+a>=l&&(s=d.y+=w,d.line++,o=d.x=n.left+(l-u[d.line])/2):s+w>n.bottom&&(o=d.x=o+n.columnWidths[d.line]+r.padding,s=d.y=n.top+r.padding,d.line++),x(o,s,t),y[e].left=o,y[e].top=s,k(o,s,t,i),_?d.x+=a+r.padding:d.y+=w})}},handleEvent:function(t){var e=this,n=e.options,i="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===i){if(!n.onHover)return}else{if("click"!==i)return;if(!n.onClick)return}var r=t.x,o=t.y;if(r>=e.left&&r<=e.right&&o>=e.top&&o<=e.bottom)for(var s=e.legendHitBoxes,l=0;l=u.left&&r<=u.left+u.width&&o>=u.top&&o<=u.top+u.height){if("click"===i){n.onClick.call(e,t.native,e.legendItems[l]),a=!0;break}if("mousemove"===i){n.onHover.call(e,t.native,e.legendItems[l]),a=!0;break}}}return a}}),t.plugins.register({beforeInit:function(t){var e=t.options.legend;e&&n(t,e)},beforeUpdate:function(e){var a=e.options.legend;a?(a=i.configMerge(t.defaults.global.legend,a),e.legend?e.legend.options=a:n(e,a)):(t.layoutService.removeBox(e,e.legend),delete e.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}})}},{}],31:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.plugins={},t.plugins={_plugins:[],_cacheId:0,register:function(t){var e=this._plugins;[].concat(t).forEach(function(t){e.indexOf(t)===-1&&e.push(t)}),this._cacheId++},unregister:function(t){var e=this._plugins;[].concat(t).forEach(function(t){var n=e.indexOf(t);n!==-1&&e.splice(n,1)}),this._cacheId++},clear:function(){this._plugins=[],this._cacheId++},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e,n){var i,a,r,o,s,l=this.descriptors(t),u=l.length;for(i=0;ic&&ot.maxHeight){o--;break}o++,d=s*u}t.labelRotation=o},afterCalculateTickRotation:function(){i.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){i.callCallback(this.options.beforeFit,[this])},fit:function(){var t=this,a=t.minSize={width:0,height:0},r=t.options,o=r.ticks,s=r.scaleLabel,l=r.gridLines,u=r.display,d=t.isHorizontal(),c=n(o),h=1.5*n(s).size,f=r.gridLines.tickMarkLength;if(d?a.width=t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:a.width=u&&l.drawTicks?f:0,d?a.height=u&&l.drawTicks?f:0:a.height=t.maxHeight,s.display&&u&&(d?a.height+=h:a.width+=h),o.display&&u){var g=i.longestText(t.ctx,c.font,t.ticks,t.longestTextCache),m=i.numberOfLabelLines(t.ticks),p=.5*c.size;if(d){t.longestLabelWidth=g;var v=i.toRadians(t.labelRotation),b=Math.cos(v),y=Math.sin(v),x=y*g+c.size*m+p*m;a.height=Math.min(t.maxHeight,a.height+x),t.ctx.font=c.font;var k=t.ticks[0],_=e(t.ctx,k,c.font),w=t.ticks[t.ticks.length-1],S=e(t.ctx,w,c.font);0!==t.labelRotation?(t.paddingLeft="bottom"===r.position?b*_+3:b*p+3,t.paddingRight="bottom"===r.position?b*p+3:b*S+3):(t.paddingLeft=_/2+3,t.paddingRight=S/2+3)}else o.mirror?g=0:g+=t.options.ticks.padding,a.width+=g,t.paddingTop=c.size/2,t.paddingBottom=c.size/2}t.handleMargins(),t.width=a.width,t.height=a.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){i.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){return null===t||"undefined"==typeof t?NaN:"number"!=typeof t||isFinite(t)?"object"==typeof t?t instanceof Date||t.isValid?t:this.getRightValue(this.isHorizontal()?t.x:t.y):t:NaN},getLabelForIndex:i.noop,getPixelForValue:i.noop,getValueForPixel:i.noop,getPixelForTick:function(t,e){var n=this;if(n.isHorizontal()){var i=n.width-(n.paddingLeft+n.paddingRight),a=i/Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),r=a*t+n.paddingLeft;e&&(r+=a/2);var o=n.left+Math.round(r);return o+=n.isFullWidth()?n.margins.left:0}var s=n.height-(n.paddingTop+n.paddingBottom);return n.top+t*(s/(n.ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=e.width-(e.paddingLeft+e.paddingRight),i=n*t+e.paddingLeft,a=e.left+Math.round(i);return a+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this,e=t.min,n=t.max;return t.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0},draw:function(e){var a=this,r=a.options;if(r.display){var o,s,l=a.ctx,u=t.defaults.global,d=r.ticks,c=r.gridLines,h=r.scaleLabel,f=0!==a.labelRotation,g=d.autoSkip,m=a.isHorizontal();d.maxTicksLimit&&(s=d.maxTicksLimit);var p=i.getValueOrDefault(d.fontColor,u.defaultFontColor),v=n(d),b=c.drawTicks?c.tickMarkLength:0,y=i.getValueOrDefault(c.borderDash,u.borderDash),x=i.getValueOrDefault(c.borderDashOffset,u.borderDashOffset),k=i.getValueOrDefault(h.fontColor,u.defaultFontColor),_=n(h),w=i.toRadians(a.labelRotation),S=Math.cos(w),M=a.longestLabelWidth*S;l.fillStyle=p;var D=[];if(m){if(o=!1,f&&(M/=2),(M+d.autoSkipPadding)*a.ticks.length>a.width-(a.paddingLeft+a.paddingRight)&&(o=1+Math.floor((M+d.autoSkipPadding)*a.ticks.length/(a.width-(a.paddingLeft+a.paddingRight)))),s&&a.ticks.length>s)for(;!o||a.ticks.length/(o||1)>s;)o||(o=1),o+=1;g||(o=!1)}var C="right"===r.position?a.left:a.right-b,T="right"===r.position?a.left+b:a.right,P="bottom"===r.position?a.top:a.bottom-b,I="bottom"===r.position?a.top+b:a.bottom;if(i.each(a.ticks,function(t,n){if(void 0!==t&&null!==t){var s=a.ticks.length===n+1,l=o>1&&n%o>0||n%o===0&&n+o>=a.ticks.length;if((!l||s)&&void 0!==t&&null!==t){var u,h;n===("undefined"!=typeof a.zeroLineIndex?a.zeroLineIndex:0)?(u=c.zeroLineWidth,h=c.zeroLineColor):(u=i.getValueAtIndexOrDefault(c.lineWidth,n),h=i.getValueAtIndexOrDefault(c.color,n));var g,p,v,k,_,S,M,A,F,O,R="middle",L="middle";if(m){"bottom"===r.position?(L=f?"middle":"top",R=f?"right":"center",O=a.top+b):(L=f?"middle":"bottom",R=f?"left":"center",O=a.bottom-b);var V=a.getPixelForTick(n)+i.aliasPixel(u);F=a.getPixelForTick(n,c.offsetGridLines)+d.labelOffset,g=v=_=M=V,p=P,k=I,S=e.top,A=e.bottom}else{var W,Y="left"===r.position,B=d.padding;d.mirror?(R=Y?"left":"right",W=B):(R=Y?"right":"left",W=b+B),F=Y?a.right-W:a.left+W;var z=a.getPixelForTick(n);z+=i.aliasPixel(u),O=a.getPixelForTick(n,c.offsetGridLines),g=C,v=T,_=e.left,M=e.right,p=k=S=A=z}D.push({tx1:g,ty1:p,tx2:v,ty2:k,x1:_,y1:S,x2:M,y2:A,labelX:F,labelY:O,glWidth:u,glColor:h,glBorderDash:y,glBorderDashOffset:x,rotation:-1*w,label:t,textBaseline:L,textAlign:R})}}}),i.each(D,function(t){if(c.display&&(l.save(),l.lineWidth=t.glWidth,l.strokeStyle=t.glColor,l.setLineDash&&(l.setLineDash(t.glBorderDash),l.lineDashOffset=t.glBorderDashOffset),l.beginPath(),c.drawTicks&&(l.moveTo(t.tx1,t.ty1),l.lineTo(t.tx2,t.ty2)),c.drawOnChartArea&&(l.moveTo(t.x1,t.y1),l.lineTo(t.x2,t.y2)),l.stroke(),l.restore()),d.display){l.save(),l.translate(t.labelX,t.labelY),l.rotate(t.rotation),l.font=v.font,l.textBaseline=t.textBaseline,l.textAlign=t.textAlign;var e=t.label;if(i.isArray(e))for(var n=0,a=0;n0)i=t.stepSize;else{var r=e.niceNum(n.max-n.min,!1);i=e.niceNum(r/(t.maxTicks-1),!0)}var o=Math.floor(n.min/i)*i,s=Math.ceil(n.max/i)*i;t.min&&t.max&&t.stepSize&&e.almostWhole((t.max-t.min)/t.stepSize,i/1e3)&&(o=t.min,s=t.max);var l=(s-o)/i;l=e.almostEquals(l,Math.round(l),i/1e3)?Math.round(l):Math.ceil(l),a.push(void 0!==t.min?t.min:o);for(var u=1;u3?i[2]-i[1]:i[1]-i[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var r=e.log10(Math.abs(a)),o="";if(0!==t){var s=-1*Math.floor(r);s=Math.max(Math.min(s,20),0),o=t.toFixed(s)}else o="0";return o},logarithmic:function(t,n,i){var a=t/Math.pow(10,Math.floor(e.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===n||n===i.length-1?t.toExponential():""}}}}},{}],35:[function(t,e,n){"use strict";e.exports=function(t){function e(e,n){var i=new t.Title({ctx:e.chart.ctx,options:n,chart:e});e.titleBlock=i,t.layoutService.addBox(e,i)}var n=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""};var i=n.noop;t.Title=t.Element.extend({initialize:function(t){var e=this;n.extend(e,t),e.legendHitBoxes=[]},beforeUpdate:i,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:i,afterBuildLabels:i,beforeFit:i,fit:function(){var e=this,i=n.getValueOrDefault,a=e.options,r=t.defaults.global,o=a.display,s=i(a.fontSize,r.defaultFontSize),l=e.minSize;e.isHorizontal()?(l.width=e.maxWidth,l.height=o?s+2*a.padding:0):(l.width=o?s+2*a.padding:0,l.height=e.maxHeight),e.width=l.width,e.height=l.height},afterFit:i,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var e=this,i=e.ctx,a=n.getValueOrDefault,r=e.options,o=t.defaults.global;if(r.display){var s,l,u,d=a(r.fontSize,o.defaultFontSize),c=a(r.fontStyle,o.defaultFontStyle),h=a(r.fontFamily,o.defaultFontFamily),f=n.fontString(d,c,h),g=0,m=e.top,p=e.left,v=e.bottom,b=e.right;i.fillStyle=a(r.fontColor,o.defaultFontColor),i.font=f,e.isHorizontal()?(s=p+(b-p)/2,l=m+(v-m)/2,u=b-p):(s="left"===r.position?p+d/2:b-d/2,l=m+(v-m)/2,u=v-m,g=Math.PI*("left"===r.position?-.5:.5)),i.save(),i.translate(s,l),i.rotate(g),i.textAlign="center",i.textBaseline="middle",i.fillText(r.text,0,0,u),i.restore()}}}),t.plugins.register({beforeInit:function(t){var n=t.options.title;n&&e(t,n)},beforeUpdate:function(i){var a=i.options.title;a?(a=n.configMerge(t.defaults.global.title,a),i.titleBlock?i.titleBlock.options=a:e(i,a)):(t.layoutService.removeBox(i,i.titleBlock),delete i.titleBlock)}})}},{}],36:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){var n=l.color(t);return n.alpha(e*n.alpha()).rgbaString()}function n(t,e){return e&&(l.isArray(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function i(t){var e=t._xScale,n=t._yScale||t._scale,i=t._index,a=t._datasetIndex;return{xLabel:e?e.getLabelForIndex(i,a):"",yLabel:n?n.getLabelForIndex(i,a):"",index:i,datasetIndex:a,x:t._model.x,y:t._model.y}}function a(e){var n=t.defaults.global,i=l.getValueOrDefault;return{xPadding:e.xPadding,yPadding:e.yPadding,xAlign:e.xAlign,yAlign:e.yAlign,bodyFontColor:e.bodyFontColor,_bodyFontFamily:i(e.bodyFontFamily,n.defaultFontFamily),_bodyFontStyle:i(e.bodyFontStyle,n.defaultFontStyle),_bodyAlign:e.bodyAlign,bodyFontSize:i(e.bodyFontSize,n.defaultFontSize),bodySpacing:e.bodySpacing,titleFontColor:e.titleFontColor,_titleFontFamily:i(e.titleFontFamily,n.defaultFontFamily),_titleFontStyle:i(e.titleFontStyle,n.defaultFontStyle),titleFontSize:i(e.titleFontSize,n.defaultFontSize),_titleAlign:e.titleAlign,titleSpacing:e.titleSpacing, +titleMarginBottom:e.titleMarginBottom,footerFontColor:e.footerFontColor,_footerFontFamily:i(e.footerFontFamily,n.defaultFontFamily),_footerFontStyle:i(e.footerFontStyle,n.defaultFontStyle),footerFontSize:i(e.footerFontSize,n.defaultFontSize),_footerAlign:e.footerAlign,footerSpacing:e.footerSpacing,footerMarginTop:e.footerMarginTop,caretSize:e.caretSize,cornerRadius:e.cornerRadius,backgroundColor:e.backgroundColor,opacity:0,legendColorBackground:e.multiKeyBackground,displayColors:e.displayColors}}function r(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce(function(t,e){return t+e.before.length+e.lines.length+e.after.length},0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,u=e.footer.length,d=e.titleFontSize,c=e.bodyFontSize,h=e.footerFontSize;i+=s*d,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*c,i+=o?(o-1)*e.bodySpacing:0,i+=u?e.footerMarginTop:0,i+=u*h,i+=u?(u-1)*e.footerSpacing:0;var f=0,g=function(t){a=Math.max(a,n.measureText(t).width+f)};return n.font=l.fontString(d,e._titleFontStyle,e._titleFontFamily),l.each(e.title,g),n.font=l.fontString(c,e._bodyFontStyle,e._bodyFontFamily),l.each(e.beforeBody.concat(e.afterBody),g),f=e.displayColors?c+2:0,l.each(r,function(t){l.each(t.before,g),l.each(t.lines,g),l.each(t.after,g)}),f=0,n.font=l.fontString(h,e._footerFontStyle,e._footerFontFamily),l.each(e.footer,g),a+=2*e.xPadding,{width:a,height:i}}function o(t,e){var n=t._model,i=t._chart,a=t._chartInstance.chartArea,r="center",o="center";n.yi.height-e.height&&(o="bottom");var s,l,u,d,c,h=(a.left+a.right)/2,f=(a.top+a.bottom)/2;"center"===o?(s=function(t){return t<=h},l=function(t){return t>h}):(s=function(t){return t<=e.width/2},l=function(t){return t>=i.width-e.width/2}),u=function(t){return t+e.width>i.width},d=function(t){return t-e.width<0},c=function(t){return t<=f?"top":"bottom"},s(n.x)?(r="left",u(n.x)&&(r="center",o=c(n.y))):l(n.x)&&(r="right",d(n.x)&&(r="center",o=c(n.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:r,yAlign:g.yAlign?g.yAlign:o}}function s(t,e,n){var i=t.x,a=t.y,r=t.caretSize,o=t.caretPadding,s=t.cornerRadius,l=n.xAlign,u=n.yAlign,d=r+o,c=s+o;return"right"===l?i-=e.width:"center"===l&&(i-=e.width/2),"top"===u?a+=d:a-="bottom"===u?e.height+d:e.height/2,"center"===u?"left"===l?i+=d:"right"===l&&(i-=d):"left"===l?i-=c:"right"===l&&(i+=c),{x:i,y:a}}var l=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,callbacks:{beforeTitle:l.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var r=t[0];r.xLabel?n=r.xLabel:a>0&&r.indexl;)r-=2*Math.PI;for(;r=s&&r<=l,d=o>=i.innerRadius&&o<=i.outerRadius;return u&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],38:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=t.defaults.global;t.defaults.global.elements.line={tension:.4,backgroundColor:n.defaultColor,borderWidth:3,borderColor:n.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0},t.elements.Line=t.Element.extend({draw:function(){function t(t,e){var n=e._view;e._view.steppedLine===!0?(l.lineTo(n.x,t._view.y),l.lineTo(n.x,n.y)):0===e._view.tension?l.lineTo(n.x,n.y):l.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,n.controlPointPreviousX,n.controlPointPreviousY,n.x,n.y)}var i=this,a=i._view,r=a.spanGaps,o=a.scaleZero,s=i._loop;s||("top"===a.fill?o=a.scaleTop:"bottom"===a.fill&&(o=a.scaleBottom));var l=i._chart.ctx;l.save();var u=i._children.slice(),d=-1;s&&u.length&&u.push(u[0]);var c,h,f,g;if(u.length&&a.fill){for(l.beginPath(),c=0;ce?1:-1,o=1,s=u.borderSkipped||"left"):(e=u.x-u.width/2,n=u.x+u.width/2,i=u.y,a=u.base,r=1,o=a>i?1:-1,s=u.borderSkipped||"bottom"),d){var c=Math.min(Math.abs(e-n),Math.abs(i-a));d=d>c?c:d;var h=d/2,f=e+("left"!==s?h*r:0),g=n+("right"!==s?-h*r:0),m=i+("top"!==s?h*o:0),p=a+("bottom"!==s?-h*o:0);f!==g&&(i=m,a=p),m!==p&&(e=f,n=g)}l.beginPath(),l.fillStyle=u.backgroundColor,l.strokeStyle=u.borderColor,l.lineWidth=d;var v=[[e,a],[e,i],[n,i],[n,a]],b=["bottom","left","top","right"],y=b.indexOf(s,0);y===-1&&(y=0);var x=t(0);l.moveTo(x[0],x[1]);for(var k=1;k<4;k++)x=t(k),l.lineTo(x[0],x[1]);l.fill(),d&&l.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=!1;if(this._view){var a=n(this);i=t>=a.left&&t<=a.right&&e>=a.top&&e<=a.bottom}return i},inLabelRange:function(t,i){var a=this;if(!a._view)return!1;var r=!1,o=n(a);return r=e(a)?t>=o.left&&t<=o.right:i>=o.top&&i<=o.bottom},inXRange:function(t){var e=n(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=n(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,n,i=this._view;return e(this)?(t=i.x,n=(i.y+i.base)/2):(t=(i.x+i.base)/2,n=i.y),{x:t,y:n}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],41:[function(t,e,n){"use strict";e.exports=function(t){function e(t,e){var n=l.getStyle(t,e),i=n&&n.match(/(\d+)px/);return i?Number(i[1]):void 0}function n(t,n){var i=t.style,a=t.getAttribute("height"),r=t.getAttribute("width");if(t._chartjs={initial:{height:a,width:r,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",null===r||""===r){var o=e(t,"width");void 0!==o&&(t.width=o)}if(null===a||""===a)if(""===t.style.height)t.height=t.width/(n.options.aspectRatio||2);else{var s=e(t,"height");void 0!==o&&(t.height=s)}return t}function i(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function a(t,e){var n=u[t.type]||t.type,a=l.getRelativePosition(t,e);return i(n,e,a.x,a.y,t)}function r(t){var e=document.createElement("iframe");return e.className="chartjs-hidden-iframe",e.style.cssText="display:block;overflow:hidden;border:0;margin:0;top:0;left:0;bottom:0;right:0;height:100%;width:100%;position:absolute;pointer-events:none;z-index:-1;",e.tabIndex=-1,l.addEvent(e,"load",function(){l.addEvent(e.contentWindow||e,"resize",t),t()}),e}function o(t,e,n){var a=t._chartjs={ticking:!1},o=function(){a.ticking||(a.ticking=!0,l.requestAnimFrame.call(window,function(){if(a.resizer)return a.ticking=!1,e(i("resize",n))}))};a.resizer=r(o),t.insertBefore(a.resizer,t.firstChild)}function s(t){if(t&&t._chartjs){var e=t._chartjs.resizer;e&&(e.parentNode.removeChild(e),t._chartjs.resizer=null),delete t._chartjs}}var l=t.helpers,u={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};return{acquireContext:function(t,e){if("string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t instanceof HTMLCanvasElement){var i=t.getContext&&t.getContext("2d");if(i instanceof CanvasRenderingContext2D)return n(t,e),i}return null},releaseContext:function(t){var e=t.canvas;if(e._chartjs){var n=e._chartjs.initial;["height","width"].forEach(function(t){var i=n[t];void 0===i||null===i?e.removeAttribute(t):e.setAttribute(t,i)}),l.each(n.style||{},function(t,n){e.style[n]=t}),e.width=e.width,delete e._chartjs}},addEventListener:function(t,e,n){var i=t.chart.canvas;if("resize"===e)return void o(i.parentNode,n,t.chart);var r=n._chartjs||(n._chartjs={}),s=r.proxies||(r.proxies={}),u=s[t.id+"_"+e]=function(e){n(a(e,t.chart))};l.addEvent(i,e,u)},removeEventListener:function(t,e,n){var i=t.chart.canvas;if("resize"===e)return void s(i.parentNode,n);var a=n._chartjs||{},r=a.proxies||{},o=r[t.id+"_"+e];o&&l.removeEvent(i,e,o)}}}},{}],42:[function(t,e,n){"use strict";var i=t(41);e.exports=function(t){t.platform={acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},t.helpers.extend(t.platform,i(t))}},{41:41}],43:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"bottom"},i=t.Scale.extend({getLabels:function(){var t=this.chart.data;return(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels},determineDataLimits:function(){var t=this,n=t.getLabels();t.minIndex=0,t.maxIndex=n.length-1;var i;void 0!==t.options.ticks.min&&(i=e.indexOf(n,t.options.ticks.min),t.minIndex=i!==-1?i:t.minIndex),void 0!==t.options.ticks.max&&(i=e.indexOf(n,t.options.ticks.max),t.maxIndex=i!==-1?i:t.maxIndex),t.min=n[t.minIndex],t.max=n[t.maxIndex]},buildTicks:function(){var t=this,e=t.getLabels();t.ticks=0===t.minIndex&&t.maxIndex===e.length-1?e:e.slice(t.minIndex,t.maxIndex+1)},getLabelForIndex:function(t,e){var n=this,i=n.chart.data,a=n.isHorizontal();return i.yLabels&&!a?n.getRightValue(i.datasets[e].data[t]):n.ticks[t-n.minIndex]},getPixelForValue:function(t,e,n,i){var a=this,r=Math.max(a.maxIndex+1-a.minIndex-(a.options.gridLines.offsetGridLines?0:1),1);if(void 0!==t&&isNaN(e)){var o=a.getLabels(),s=o.indexOf(t);e=s!==-1?s:e}if(a.isHorizontal()){var l=a.width/r,u=l*(e-a.minIndex);return(a.options.gridLines.offsetGridLines&&i||a.maxIndex===a.minIndex&&i)&&(u+=l/2),a.left+Math.round(u)}var d=a.height/r,c=d*(e-a.minIndex);return a.options.gridLines.offsetGridLines&&i&&(c+=d/2),a.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,n=this,i=Math.max(n.ticks.length-(n.options.gridLines.offsetGridLines?0:1),1),a=n.isHorizontal(),r=(a?n.width:n.height)/i;return t-=a?n.left:n.top,n.options.gridLines.offsetGridLines&&(t-=r/2),e=t<=0?0:Math.round(t/r)},getBasePixel:function(){return this.bottom}});t.scaleService.registerScaleType("category",i,n)}},{}],44:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:t.Ticks.formatters.linear}},i=t.LinearScaleBase.extend({determineDataLimits:function(){function t(t){return s?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=n.chart,r=a.data,o=r.datasets,s=n.isHorizontal();n.min=null,n.max=null;var l=i.stacked;if(void 0===l&&e.each(o,function(e,n){if(!l){var i=a.getDatasetMeta(n);a.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(l=!0)}}),i.stacked||l){var u={};e.each(o,function(r,o){var s=a.getDatasetMeta(o),l=[s.type,void 0===i.stacked&&void 0===s.stack?o:"",s.stack].join(".");void 0===u[l]&&(u[l]={positiveValues:[],negativeValues:[]});var d=u[l].positiveValues,c=u[l].negativeValues;a.isDatasetVisible(o)&&t(s)&&e.each(r.data,function(t,e){var a=+n.getRightValue(t);isNaN(a)||s.data[e].hidden||(d[e]=d[e]||0,c[e]=c[e]||0,i.relativePoints?d[e]=100:a<0?c[e]+=a:d[e]+=a)})}),e.each(u,function(t){var i=t.positiveValues.concat(t.negativeValues),a=e.min(i),r=e.max(i);n.min=null===n.min?a:Math.min(n.min,a),n.max=null===n.max?r:Math.max(n.max,r)})}else e.each(o,function(i,r){var o=a.getDatasetMeta(r);a.isDatasetVisible(r)&&t(o)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||o.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i))})});this.handleTickRangeOptions()},getTickLimit:function(){var n,i=this,a=i.options.ticks;if(i.isHorizontal())n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.width/50));else{var r=e.getValueOrDefault(a.fontSize,t.defaults.global.defaultFontSize);n=Math.min(a.maxTicksLimit?a.maxTicksLimit:11,Math.ceil(i.height/(2*r)))}return n},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e,n=this,i=n.start,a=+n.getRightValue(t),r=n.end-i;return n.isHorizontal()?(e=n.left+n.width/r*(a-i),Math.round(e)):(e=n.bottom-n.height/r*(a-i),Math.round(e))},getValueForPixel:function(t){var e=this,n=e.isHorizontal(),i=n?e.width:e.height,a=(n?t-e.left:e.bottom-t)/i;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",i,n)}},{}],45:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n=e.noop;t.LinearScaleBase=t.Scale.extend({handleTickRangeOptions:function(){var t=this,n=t.options,i=n.ticks;if(i.beginAtZero){var a=e.sign(t.min),r=e.sign(t.max);a<0&&r<0?t.max=0:a>0&&r>0&&(t.min=0)}void 0!==i.min?t.min=i.min:void 0!==i.suggestedMin&&(t.min=Math.min(t.min,i.suggestedMin)),void 0!==i.max?t.max=i.max:void 0!==i.suggestedMax&&(t.max=Math.max(t.max,i.suggestedMax)),t.min===t.max&&(t.max++,i.beginAtZero||t.min--)},getTickLimit:n,handleDirectionalChanges:n,buildTicks:function(){var n=this,i=n.options,a=i.ticks,r=n.getTickLimit();r=Math.max(2,r);var o={maxTicks:r,min:a.min,max:a.max,stepSize:e.getValueOrDefault(a.fixedStepSize,a.stepSize)},s=n.ticks=t.Ticks.generators.linear(o,n);n.handleDirectionalChanges(),n.max=e.max(s),n.min=e.min(s),a.reverse?(s.reverse(),n.start=n.max,n.end=n.min):(n.start=n.min,n.end=n.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{}],46:[function(t,e,n){"use strict";e.exports=function(t){var e=t.helpers,n={position:"left",ticks:{callback:t.Ticks.formatters.logarithmic}},i=t.Scale.extend({determineDataLimits:function(){function t(t){return u?t.xAxisID===n.id:t.yAxisID===n.id}var n=this,i=n.options,a=i.ticks,r=n.chart,o=r.data,s=o.datasets,l=e.getValueOrDefault,u=n.isHorizontal();n.min=null,n.max=null,n.minNotZero=null;var d=i.stacked;if(void 0===d&&e.each(s,function(e,n){if(!d){var i=r.getDatasetMeta(n);r.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(d=!0)}}),i.stacked||d){var c={};e.each(s,function(a,o){var s=r.getDatasetMeta(o),l=[s.type,void 0===i.stacked&&void 0===s.stack?o:"",s.stack].join(".");r.isDatasetVisible(o)&&t(s)&&(void 0===c[l]&&(c[l]=[]),e.each(a.data,function(t,e){var a=c[l],r=+n.getRightValue(t);isNaN(r)||s.data[e].hidden||(a[e]=a[e]||0,i.relativePoints?a[e]=100:a[e]+=r)}))}),e.each(c,function(t){var i=e.min(t),a=e.max(t);n.min=null===n.min?i:Math.min(n.min,i),n.max=null===n.max?a:Math.max(n.max,a)})}else e.each(s,function(i,a){var o=r.getDatasetMeta(a);r.isDatasetVisible(a)&&t(o)&&e.each(i.data,function(t,e){var i=+n.getRightValue(t);isNaN(i)||o.data[e].hidden||(null===n.min?n.min=i:in.max&&(n.max=i),0!==i&&(null===n.minNotZero||ia?{start:e-n-5,end:e}:{start:e,end:e+n+5}}function r(t){var r,o,s,l=n(t),u=Math.min(t.height/2,t.width/2),d={l:t.width,r:0,t:t.height,b:0},c={};t.ctx.font=l.font,t._pointLabelSizes=[];var h=e(t);for(r=0;rd.r&&(d.r=p.end,c.r=g),v.startd.b&&(d.b=v.end,c.b=g)}t.setReductions(u,d,c)}function o(t){var e=Math.min(t.height/2,t.width/2);t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0)}function s(t){return 0===t||180===t?"center":t<180?"left":"right"}function l(t,e,n,i){if(f.isArray(e))for(var a=n.y,r=1.5*i,o=0;o270||t<90)&&(n.y-=e.h)}function d(t){var i=t.ctx,a=f.getValueOrDefault,r=t.options,o=r.angleLines,d=r.pointLabels;i.lineWidth=o.lineWidth,i.strokeStyle=o.color;var c=t.getDistanceFromCenterForValue(r.reverse?t.min:t.max),h=n(t);i.textBaseline="top";for(var m=e(t)-1;m>=0;m--){if(o.display){var p=t.getPointPosition(m,c);i.beginPath(),i.moveTo(t.xCenter,t.yCenter),i.lineTo(p.x,p.y),i.stroke(),i.closePath()}var v=t.getPointPosition(m,c+5),b=a(d.fontColor,g.defaultFontColor);i.font=h.font,i.fillStyle=b;var y=t.getIndexAngle(m),x=f.toDegrees(y);i.textAlign=s(x),u(x,t._pointLabelSizes[m],v),l(i,t.pointLabels[m]||"",v,h.size)}}function c(t,n,i,a){var r=t.ctx;if(r.strokeStyle=f.getValueAtIndexOrDefault(n.color,a-1),r.lineWidth=f.getValueAtIndexOrDefault(n.lineWidth,a-1),t.options.lineArc)r.beginPath(),r.arc(t.xCenter,t.yCenter,i,0,2*Math.PI),r.closePath(),r.stroke();else{var o=e(t);if(0===o)return;r.beginPath();var s=t.getPointPosition(0,i);r.moveTo(s.x,s.y);for(var l=1;l0&&n>0?e:0)},draw:function(){var t=this,e=t.options,n=e.gridLines,i=e.ticks,a=f.getValueOrDefault; +if(e.display){var r=t.ctx,o=a(i.fontSize,g.defaultFontSize),s=a(i.fontStyle,g.defaultFontStyle),l=a(i.fontFamily,g.defaultFontFamily),u=f.fontString(o,s,l);f.each(t.ticks,function(s,l){if(l>0||e.reverse){var d=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),h=t.yCenter-d;if(n.display&&0!==l&&c(t,n,d,l),i.display){var f=a(i.fontColor,g.defaultFontColor);if(r.font=u,i.showLabelBackdrop){var m=r.measureText(s).width;r.fillStyle=i.backdropColor,r.fillRect(t.xCenter-m/2-i.backdropPaddingX,h-o/2-i.backdropPaddingY,m+2*i.backdropPaddingX,o+2*i.backdropPaddingY)}r.textAlign="center",r.textBaseline="middle",r.fillStyle=f,r.fillText(s,t.xCenter,h)}}}),e.lineArc||d(t)}}});t.scaleService.registerScaleType("radialLinear",p,m)}},{}],48:[function(t,e,n){"use strict";var i=t(6);i="function"==typeof i?i:window.moment,e.exports=function(t){var e=t.helpers,n={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},a={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},r=t.Scale.extend({initialize:function(){if(!i)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return null===t||null===e?null:"undefined"!=typeof this.labelMoments[t]?this.labelMoments[t][e]:null},getLabelDiff:function(t,e){var n=this;return null===t||null===e?null:(void 0===n.labelDiffs&&n.buildLabelDiffs(),"undefined"!=typeof n.labelDiffs[t]?n.labelDiffs[t][e]:null)},getMomentStartOf:function(t){var e=this;return"week"===e.options.time.unit&&e.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(e.options.time.isoWeekday):t.clone().startOf(e.tickUnit)},determineDataLimits:function(){var t=this;t.labelMoments=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0?(e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i))},t),t.firstTick=i.min.call(t,n),t.lastTick=i.max.call(t,n)):(t.firstTick=null,t.lastTick=null),e.each(t.chart.data.datasets,function(a,r){var o=[],s=t.chart.isDatasetVisible(r);"object"==typeof a.data[0]&&null!==a.data[0]?e.each(a.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),o.push(n),s&&(t.firstTick=null!==t.firstTick?i.min(t.firstTick,n):n,t.lastTick=null!==t.lastTick?i.max(t.lastTick,n):n))},t):o=n,t.labelMoments.push(o)},t),t.options.time.min&&(t.firstTick=t.parseTime(t.options.time.min)),t.options.time.max&&(t.lastTick=t.parseTime(t.options.time.max)),t.firstTick=(t.firstTick||i()).clone(),t.lastTick=(t.lastTick||i()).clone()},buildLabelDiffs:function(){var t=this;t.labelDiffs=[];var n=[];t.chart.data.labels&&t.chart.data.labels.length>0&&e.each(t.chart.data.labels,function(e){var i=t.parseTime(e);i.isValid()&&(t.options.time.round&&i.startOf(t.options.time.round),n.push(i.diff(t.firstTick,t.tickUnit,!0)))},t),e.each(t.chart.data.datasets,function(i){var a=[];"object"==typeof i.data[0]&&null!==i.data[0]?e.each(i.data,function(e){var n=t.parseTime(t.getRightValue(e));n.isValid()&&(t.options.time.round&&n.startOf(t.options.time.round),a.push(n.diff(t.firstTick,t.tickUnit,!0)))},t):a=n,t.labelDiffs.push(a)},t)},buildTicks:function(){var i=this;i.ctx.save();var a=e.getValueOrDefault(i.options.ticks.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(i.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),o=e.getValueOrDefault(i.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),s=e.fontString(a,r,o);if(i.ctx.font=s,i.ticks=[],i.unitScale=1,i.scaleSizeInUnits=0,i.options.time.unit)i.tickUnit=i.options.time.unit||"day",i.displayFormat=i.options.time.displayFormats[i.tickUnit],i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,1);else{var l=i.isHorizontal()?i.width:i.height,u=i.tickFormatFunction(i.firstTick,0,[]),d=i.ctx.measureText(u).width,c=Math.cos(e.toRadians(i.options.ticks.maxRotation)),h=Math.sin(e.toRadians(i.options.ticks.maxRotation));d=d*c+a*h;var f=l/d;i.tickUnit=i.options.time.minUnit,i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0),i.displayFormat=i.options.time.displayFormats[i.tickUnit];for(var g=0,m=n.units[g];g=Math.ceil(i.scaleSizeInUnits/f)){i.unitScale=e.getValueOrDefault(i.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(i.scaleSizeInUnits/f)=0&&(i.lastTick=x),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0)}i.options.time.displayFormat&&(i.displayFormat=i.options.time.displayFormat),i.ticks.push(i.firstTick.clone());for(var _=i.unitScale;_<=i.scaleSizeInUnits;_+=i.unitScale){var w=y.clone().add(_,i.tickUnit);if(i.options.time.max&&w.diff(i.lastTick,i.tickUnit,!0)>=0)break;i.ticks.push(w)}var S=i.ticks[i.ticks.length-1].diff(i.lastTick,i.tickUnit);0===S&&0!==i.scaleSizeInUnits||(i.options.time.max?(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.ticks[0],i.tickUnit,!0)):(i.ticks.push(i.lastTick.clone()),i.scaleSizeInUnits=i.lastTick.diff(i.firstTick,i.tickUnit,!0))),i.ctx.restore(),i.labelDiffs=void 0},getLabelForIndex:function(t,e){var n=this,i=n.chart.data.labels&&t 'Show everything', 'never' => 'Never', 'search_results_for' => 'Search results for ":query"', + 'advanced_search' => 'Advanced search', + 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', '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.', @@ -90,9 +92,12 @@ return [ 'expenses_by_category' => 'Expenses by category', 'expenses_by_budget' => 'Expenses by budget', 'income_by_category' => 'Income by category', + 'expenses_by_asset_account' => 'Expenses by asset account', + 'expenses_by_expense_account' => 'Expenses by expense account', 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', 'sum_of_expenses' => 'Sum of expenses', 'sum_of_income' => 'Sum of income', + 'total_sum' => 'Total sum', 'spent_in_specific_budget' => 'Spent in budget ":budget"', 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', 'left_in_budget_limit' => 'Left to spend according to budgeting', @@ -104,7 +109,11 @@ return [ 'current_period' => 'Current period', 'show_the_current_period_and_overview' => 'Show the current period and overview', 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => '":name" between :start and :end', + 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', + 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', + 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', @@ -113,7 +122,33 @@ return [ 'multi_select_no_selection' => 'None selected', 'multi_select_all_selected' => 'All selected', 'multi_select_filter_placeholder' => 'Find..', - + 'between_dates_breadcrumb' => 'Between :start and :end', + 'all_journals_without_budget' => 'All transactions without a budget', + 'journals_without_budget' => 'Transactions without a budget', + 'all_journals_without_category' => 'All transactions without a category', + 'journals_without_category' => 'Transactions without a category', + 'all_journals_for_account' => 'All transactions for account :name', + 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', + 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', + 'transferred' => 'Transferred', + 'all_withdrawal' => 'All expenses', + 'all_transactions' => 'All transactions', + 'title_withdrawal_between' => 'All expenses between :start and :end', + 'all_deposit' => 'All revenue', + 'title_deposit_between' => 'All revenue between :start and :end', + 'all_transfers' => 'All transfers', + 'title_transfers_between' => 'All transfers between :start and :end', + 'all_transfer' => 'All transfers', + 'all_journals_for_tag' => 'All transactions for tag ":tag"', + 'title_transfer_between' => 'All transfers between :start and :end', + 'all_journals_for_category' => 'All transactions for category :name', + 'all_journals_for_budget' => 'All transactions for budget :name', + 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', + 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', + 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', + 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', + 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', + 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', // repeat frequencies: 'repeat_freq_yearly' => 'yearly', @@ -237,6 +272,7 @@ return [ '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_category_is' => 'Category is ":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', @@ -263,6 +299,8 @@ return [ 'rule_trigger_category_is_choice' => 'Category is..', 'rule_trigger_budget_is_choice' => 'Budget is..', 'rule_trigger_tag_is_choice' => '(A) tag is..', + 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', + 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', 'rule_trigger_store_journal' => 'When a transaction is created', 'rule_trigger_update_journal' => 'When a transaction is updated', 'rule_action_set_category' => 'Set category to ":action_value"', @@ -556,6 +594,8 @@ return [ 'select_more_than_one_budget' => 'Please select more than one budget', 'select_more_than_one_tag' => 'Please select more than one tag', 'from_to' => 'From :start to :end', + 'from_to_breadcrumb' => 'from :start to :end', + 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', // categories: 'new_category' => 'New category', @@ -665,6 +705,7 @@ return [ 'report_audit' => 'Transaction history overview between :start and :end', 'report_category' => 'Category report between :start and :end', 'report_budget' => 'Budget report between :start and :end', + 'report_tag' => 'Tag report between :start and :end', 'quick_link_reports' => 'Quick links', 'quick_link_default_report' => 'Default financial report', 'quick_link_audit_report' => 'Transaction history overview', @@ -739,11 +780,15 @@ return [ 'expense_per_budget' => 'Expense per budget', 'income_per_account' => 'Income per account', 'expense_per_account' => 'Expense per account', + 'expense_per_tag' => 'Expense per tag', + 'income_per_tag' => 'Income per tag', 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', 'include_income_not_in_category' => 'Included income not in the selected category(ies)', 'include_income_not_in_account' => 'Included income not in the selected account(s)', + 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', + 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', 'everything_else' => 'Everything else', 'income_and_expenses' => 'Income and expenses', 'spent_average' => 'Spent (average)', @@ -791,7 +836,7 @@ return [ // piggy banks: 'add_money_to_piggy' => 'Add money to piggy bank ":name"', 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'Create new piggy bank', + 'new_piggy_bank' => 'New piggy bank', 'store_piggy_bank' => 'Store new piggy bank', 'stored_piggy_bank' => 'Store new piggy bank ":name"', 'account_status' => 'Account status', @@ -799,6 +844,7 @@ return [ 'sum_of_piggy_banks' => 'Sum of piggy banks', 'saved_so_far' => 'Saved so far', 'left_to_save' => 'Left to save', + 'suggested_amount' => 'Suggested monthly amount 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', @@ -843,126 +889,175 @@ return [ '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_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.', - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', + 'transaction_journal_information' => 'Transaction information', + 'transaction_journal_meta' => 'Meta information', + 'total_amount' => 'Total amount', + 'number_of_decimals' => 'Number of decimals', // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'rule_or_rules' => 'rule(s)', - 'rulegroup_or_groups' => 'rule group(s)', - 'setting_must_confirm_account' => 'Account confirmation', - 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'setting_send_email_notifications' => 'Send email notifications', - 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', + 'administration' => 'Administration', + 'user_administration' => 'User administration', + 'list_all_users' => 'All users', + 'all_users' => 'All users', + 'instance_configuration' => 'Configuration', + 'firefly_instance_configuration' => 'Configuration options for Firefly III', + 'setting_single_user_mode' => 'Single user mode', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', + 'store_configuration' => 'Store configuration', + 'single_user_administration' => 'User administration for :email', + 'edit_user' => 'Edit user :email', + 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', + 'user_data_information' => 'User data', + 'user_information' => 'User information', + 'total_size' => 'total size', + 'budget_or_budgets' => 'budget(s)', + 'budgets_with_limits' => 'budget(s) with configured amount', + 'rule_or_rules' => 'rule(s)', + 'rulegroup_or_groups' => 'rule group(s)', + 'setting_must_confirm_account' => 'Account confirmation', + 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', + 'configuration_updated' => 'The configuration has been updated', + 'setting_is_demo_site' => 'Demo site', + 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', + 'setting_send_email_notifications' => 'Send email notifications', + 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', + 'block_code_bounced' => 'Email message(s) bounced', + 'block_code_expired' => 'Demo account expired', + 'no_block_code' => 'No reason for block or user not blocked', // split a transaction: - 'transaction_meta_data' => 'Transaction meta-data', - 'transaction_dates' => 'Transaction dates', - 'splits' => 'Splits', - 'split_title_withdrawal' => 'Split your new withdrawal', - 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', - 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', - 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', - 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', - 'split_title_deposit' => 'Split your new deposit', - 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', - 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', - 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', - 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_deposit' => 'Store splitted deposit', - 'split_title_transfer' => 'Split your new transfer', - 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', - 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', - 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', - 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_transfer' => 'Store splitted transfer', - 'add_another_split' => 'Add another split', - 'split-transactions' => 'Split transactions', - 'split-new-transaction' => 'Split a new transaction', - 'do_split' => 'Do a split', - 'split_this_withdrawal' => 'Split this withdrawal', - 'split_this_deposit' => 'Split this deposit', - 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', + 'transaction_meta_data' => 'Transaction meta-data', + 'transaction_dates' => 'Transaction dates', + 'splits' => 'Splits', + 'split_title_withdrawal' => 'Split your new withdrawal', + 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', + 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', + 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', + 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_withdrawal' => 'Store splitted withdrawal', + 'update_splitted_withdrawal' => 'Update splitted withdrawal', + 'split_title_deposit' => 'Split your new deposit', + 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', + 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', + 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', + 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_deposit' => 'Store splitted deposit', + 'split_title_transfer' => 'Split your new transfer', + 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', + 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', + 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', + 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_transfer' => 'Store splitted transfer', + 'add_another_split' => 'Add another split', + 'split-transactions' => 'Split transactions', + 'split-new-transaction' => 'Split a new transaction', + 'do_split' => 'Do a split', + 'split_this_withdrawal' => 'Split this withdrawal', + 'split_this_deposit' => 'Split this deposit', + 'split_this_transfer' => 'Split this transfer', + 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', + 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', - 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', - 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_data_index' => 'Index', + 'import_file_type_csv' => 'CSV (comma separated values)', + 'import_file_type_help' => 'Select the type of file you will upload', + 'import_start' => 'Start the import', + 'configure_import' => 'Further configure your import', + 'import_finish_configuration' => 'Finish configuration', + 'settings_for_import' => 'Settings', + 'import_status' => 'Import status', + 'import_status_text' => 'The import is currently running, or will start momentarily.', + 'import_complete' => 'Import configuration complete!', + 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_download_config' => 'Download configuration', + 'import_start_import' => 'Start import', + 'import_data' => 'Import data', + 'import_data_full' => 'Import data into Firefly III', + 'import' => 'Import', + 'import_file_help' => 'Select your file', + 'import_status_settings_complete' => 'The import is ready to start.', + 'import_status_import_complete' => 'The import has completed.', + 'import_status_import_running' => 'The import is currently running. Please be patient.', + 'import_status_header' => 'Import status and progress', + 'import_status_errors' => 'Import errors', + 'import_status_report' => 'Import report', + 'import_finished' => 'Import has finished', + 'import_error_single' => 'An error has occured during the import.', + 'import_error_multi' => 'Some errors occured during the import.', + 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', + 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', + 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', + 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_with_key' => 'Import with key \':key\'', + 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', + 'import_finished_link' => 'The transactions imported can be found in tag :tag.', + 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', + 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', + 'bread_crumb_import_complete' => 'Import ":key" complete', + 'bread_crumb_configure_import' => 'Configure import ":key"', + 'bread_crumb_import_finished' => 'Import ":key" finished', + 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', + 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', + 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', // sandstorm.io errors and messages: - 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', + 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', + + // empty lists? no objects? instructions: + 'no_transactions_in_period' => 'There are no transactions in this period.', + 'no_accounts_title_asset' => 'Let\'s create an asset account!', + 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', + 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', + 'no_accounts_create_asset' => 'Create an asset account', + 'no_accounts_title_expense' => 'Let\'s create an expense account!', + 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', + 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_create_expense' => 'Create an expense account', + 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', + 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', + 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_create_revenue' => 'Create a revenue account', + 'no_budgets_title_default' => 'Let\'s create a budget', + 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', + 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', + 'no_budgets_create_default' => 'Create a budget', + 'no_categories_title_default' => 'Let\'s create a category!', + 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', + 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_categories_create_default' => 'Create a category', + 'no_tags_title_default' => 'Let\'s create a tag!', + 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', + 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_tags_create_default' => 'Create a tag', + 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', + 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', + 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', + 'no_transactions_create_withdrawal' => 'Create an expense', + 'no_transactions_title_deposit' => 'Let\'s create some income!', + 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', + 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', + 'no_transactions_create_deposit' => 'Create a deposit', + 'no_transactions_title_transfers' => 'Let\'s create a transfer!', + 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', + 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', + 'no_transactions_create_transfers' => 'Create a transfer', + 'no_piggies_title_default' => 'Let\'s create a piggy bank!', + 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', + 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', + 'no_piggies_create_default' => 'Create a new piggy bank', + 'no_bills_title_default' => 'Let\'s create a bill!', + 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', + 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', + 'no_bills_create_default' => 'Create a bill', + + ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 271005bc4f..10a2aba64b 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -64,6 +64,11 @@ return [ 'expense_account' => 'Expense account', 'revenue_account' => 'Revenue account', 'decimal_places' => 'Decimal places', + 'exchange_rate_instruction' => 'Foreign currencies', + 'exchanged_amount' => 'Exchanged amount', + 'source_amount' => 'Amount (source)', + 'destination_amount' => 'Amount (destination)', + 'native_amount' => 'Native amount', 'revenue_account_source' => 'Revenue account (source)', 'source_account_asset' => 'Source account (asset account)', diff --git a/resources/lang/es_ES/auth.php b/resources/lang/es_ES/auth.php index 3f4e103f18..45d765e4fa 100644 --- a/resources/lang/es_ES/auth.php +++ b/resources/lang/es_ES/auth.php @@ -25,4 +25,4 @@ return [ 'failed' => 'Las credenciales no coinciden con los registros.', 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor reintente en :seconds segundos.', -]; \ No newline at end of file +]; diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index ddb5479e3f..211e6a3037 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -38,4 +38,4 @@ return [ 'createTag' => 'Crear nueva etiqueta', 'edit_tag' => 'Editar etiqueta ":tag"', 'delete_tag' => 'Eliminar etiqueta ":tag"', -]; \ No newline at end of file +]; diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php deleted file mode 100644 index 223b3f8bd3..0000000000 --- a/resources/lang/es_ES/config.php +++ /dev/null @@ -1,23 +0,0 @@ - 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', - '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', - -]; \ No newline at end of file diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index 27143be1bc..47b611f81a 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types=1); +declare(strict_types = 1); return [ @@ -77,4 +77,4 @@ return [ 'column_tags-space' => 'Etiquetas (separadas por espacios)', 'column_account-number' => 'Caja de ahorro (número de cuenta)', 'column_opposing-number' => 'Cuenta opuesta (número de cuenta)', -]; \ No newline at end of file +]; diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index ed19589949..e2d7695439 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -21,4 +21,4 @@ return [ 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', -]; \ No newline at end of file +]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php deleted file mode 100644 index a8d8567267..0000000000 --- a/resources/lang/es_ES/firefly.php +++ /dev/null @@ -1,1063 +0,0 @@ - 'incomplete translation', - 'close' => 'Close', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', - 'advanced_search' => 'Advanced search', - 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', - '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.', - 'expired_error' => 'Your account has expired, and can no longer be used.', - '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.', - 'help_may_not_be_your_language' => 'This help text is in English. It is not yet 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', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - '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.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'search' => 'Search', - 'search_found_accounts' => 'Found :count account(s) for your query.', - 'search_found_categories' => 'Found :count category(ies) for your query.', - 'search_found_budgets' => 'Found :count budget(s) for your query.', - 'search_found_tags' => 'Found :count tag(s) for your query.', - 'search_found_transactions' => 'Found :count transaction(s) for your query.', - 'results_limited' => 'The results are limited to :count entries.', - 'tagbalancingAct' => 'Balancing act', - 'tagadvancePayment' => 'Advance payment', - 'tagnothing' => '', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account(s)', - 'destination_accounts' => 'Destination account(s)', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', - 'nothing_to_display' => 'There are no transactions to show you', - 'show_all_no_filter' => 'Show all transactions without grouping them by date.', - 'expenses_by_category' => 'Expenses by category', - 'expenses_by_budget' => 'Expenses by budget', - 'income_by_category' => 'Income by category', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', - 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', - 'sum_of_expenses' => 'Sum of expenses', - 'sum_of_income' => 'Sum of income', - 'total_sum' => 'Total sum', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', - 'cannot_change_demo' => 'You cannot change the password of the demonstration account.', - 'cannot_delete_demo' => 'You cannot remove the demonstration account.', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demonstration account', - 'per_period' => 'Per period', - 'all_periods' => 'All periods', - 'current_period' => 'Current period', - 'show_the_current_period_and_overview' => 'Show the current period and overview', - 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', - 'budget_in_period_breadcrumb' => 'Between :start and :end', - 'clone_withdrawal' => 'Clone this withdrawal', - 'clone_deposit' => 'Clone this deposit', - 'clone_transfer' => 'Clone this transfer', - 'transaction_journal_other_options' => 'Other options', - 'multi_select_no_selection' => 'None selected', - 'multi_select_all_selected' => 'All selected', - 'multi_select_filter_placeholder' => 'Find..', - 'between_dates_breadcrumb' => 'Between :start and :end', - 'all_journals_without_budget' => 'All transactions without a budget', - 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_without_category' => 'All transactions without a category', - 'journals_without_category' => 'Transactions without a category', - 'all_journals_for_account' => 'All transactions for account :name', - 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', - 'transferred' => 'Transferred', - 'all_withdrawal' => 'All expenses', - 'all_transactions' => 'All transactions', - 'title_withdrawal_between' => 'All expenses between :start and :end', - 'all_deposit' => 'All revenue', - 'title_deposit_between' => 'All revenue between :start and :end', - 'all_transfers' => 'All transfers', - 'title_transfers_between' => 'All transfers between :start and :end', - 'all_transfer' => 'All transfers', - 'all_journals_for_tag' => 'All transactions for tag ":tag"', - 'title_transfer_between' => 'All transfers between :start and :end', - 'all_journals_for_category' => 'All transactions for category :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', - 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', - 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', - 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', - 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', - 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', - - // repeat frequencies: - 'repeat_freq_yearly' => 'yearly', - 'repeat_freq_monthly' => 'monthly', - 'weekly' => 'weekly', - 'quarterly' => 'quarterly', - 'half-year' => 'every half year', - 'yearly' => 'yearly', - // account confirmation: - 'confirm_account_header' => 'Please confirm your account', - 'confirm_account_intro' => 'An email has been sent to the address you used during your registration. Please check it out for further instructions. If you did not get this message, you can have Firefly send it again.', - 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.', - 'account_is_confirmed' => 'Your account has been confirmed!', - 'invalid_activation_code' => 'It seems the code you are using is not valid, or has expired.', - 'confirm_account_is_resent_header' => 'The confirmation has been resent', - 'confirm_account_is_resent_text' => 'The confirmation message has been resent. If you still did not receive the confirmation message, please contact the site owner at :owner or check the log files to see what went wrong.', - 'confirm_account_is_resent_go_home' => 'Go to the index page of Firefly', - 'confirm_account_not_resent_header' => 'Something went wrong :(', - 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.', - 'confirm_account_not_resent_go_home' => 'Go to the index page of Firefly', - - // 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_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"', - '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', - 'rule_group_select_transactions' => 'Execute rule group ":title" on 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_category_is' => 'Category is ":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..', - 'rule_trigger_from_account_contains_choice' => 'Source account contains..', - 'rule_trigger_to_account_starts_choice' => 'Destination account starts with..', - 'rule_trigger_to_account_ends_choice' => 'Destination account ends with..', - 'rule_trigger_to_account_is_choice' => 'Destination account is..', - 'rule_trigger_to_account_contains_choice' => 'Destination account contains..', - 'rule_trigger_transaction_type_choice' => 'Transaction is of type..', - 'rule_trigger_amount_less_choice' => 'Amount is less than..', - 'rule_trigger_amount_exactly_choice' => 'Amount is..', - 'rule_trigger_amount_more_choice' => 'Amount is more than..', - 'rule_trigger_description_starts_choice' => 'Description starts with..', - 'rule_trigger_description_ends_choice' => 'Description ends with..', - 'rule_trigger_description_contains_choice' => 'Description contains..', - 'rule_trigger_description_is_choice' => 'Description is..', - 'rule_trigger_category_is_choice' => 'Category is..', - 'rule_trigger_budget_is_choice' => 'Budget is..', - 'rule_trigger_tag_is_choice' => '(A) tag is..', - 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', - 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', - 'rule_trigger_store_journal' => 'When a transaction is created', - 'rule_trigger_update_journal' => 'When a transaction 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_action_set_source_account_choice' => 'Set source account to...', - 'rule_action_set_source_account' => 'Set source account to :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to...', - 'rule_action_set_destination_account' => 'Set destination account to :action_value', - - // tags - '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_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_1Y' => 'One year', - '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', - 'saved_preferences' => 'Preferences saved!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', - 'transaction_page_size_title' => 'Page size', - 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', - 'transaction_page_size_label' => 'Page size', - 'between_dates' => '(:start and :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', - 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', - 'optional_tj_date_fields' => 'Date fields', - 'optional_tj_business_fields' => 'Business fields', - 'optional_tj_attachment_fields' => 'Attachment fields', - 'pref_optional_tj_interest_date' => 'Interest date', - 'pref_optional_tj_book_date' => 'Book date', - 'pref_optional_tj_process_date' => 'Processing date', - 'pref_optional_tj_due_date' => 'Due date', - 'pref_optional_tj_payment_date' => 'Payment date', - 'pref_optional_tj_invoice_date' => 'Invoice date', - 'pref_optional_tj_internal_reference' => 'Internal reference', - 'pref_optional_tj_notes' => 'Notes', - 'pref_optional_tj_attachments' => 'Attachments', - 'optional_field_meta_dates' => 'Dates', - 'optional_field_meta_business' => 'Business', - 'optional_field_attachments' => 'Attachments', - 'optional_field_meta_data' => 'Optional meta data', - - - // 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!', - - - // 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"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - - // tour: - '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', - - // convert stuff: - 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', - 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', - 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', - 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', - 'convert_to_Deposit' => 'Convert ":description" to a deposit', - 'convert_to_Transfer' => 'Convert ":description" to a transfer', - 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', - 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', - 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', - 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', - 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', - 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', - 'transaction_journal_convert_options' => 'Convert this transaction', - 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', - 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', - 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', - 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', - 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', - 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', - 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', - 'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.', - 'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.', - 'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.', - 'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into :sourceName instead of taken from it.', - 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', - 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', - 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', - 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', - 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', - 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', - 'converted_to_Deposit' => 'The transaction has been converted to a deposit', - 'converted_to_Transfer' => 'The transaction has been converted to a transfer', - - - // 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', - - // currencies: - 'create_currency' => 'Create a new currency', - '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 it is still in use.', - '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.', - 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', - 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', - - // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - - // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => 'Store new budget', - 'stored_new_budget' => 'Stored new budget ":name"', - 'available_between' => 'Available between :start and :end', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactions_no_budget' => 'Expenses without budget between :start and :end', - 'spent_between' => 'Spent between :start and :end', - '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"', - 'deleted_budget' => 'Deleted budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'updated_budget' => 'Updated budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', - 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', - - // bills: - 'matching_on' => 'Matching on', - 'between_amounts' => 'between :low and :high.', - 'repeats' => 'Repeats', - 'connected_journals' => 'Connected transactions', - 'auto_match_on' => 'Automatically matched by Firefly', - 'auto_match_off' => 'Not automatically matched by Firefly', - 'next_expected_match' => 'Next expected match', - 'delete_bill' => 'Delete bill ":name"', - 'deleted_bill' => 'Deleted bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'more' => 'More', - 'rescan_old' => 'Rescan old transactions', - 'update_bill' => 'Update bill', - 'updated_bill' => 'Updated bill ":name"', - 'store_new_bill' => 'Store new bill', - 'stored_new_bill' => 'Stored new bill ":name"', - 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', - 'rescanned_bill' => 'Rescanned everything.', - 'average_bill_amount_year' => 'Average bill amount (:year)', - 'average_bill_amount_overall' => 'Average bill amount (overall)', - 'not_or_not_yet' => 'Not (yet)', - 'not_expected_period' => 'Not expected this period', - // 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', - 'cash_accounts' => 'Cash accounts', - 'Cash account' => 'Cash account', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', - 'stored_new_account' => 'New account ":name" stored!', - 'updated_account' => 'Updated account ":name"', - 'credit_card_options' => 'Credit card options', - 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', - 'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.', - 'select_more_than_one_account' => 'Please select more than one account', - 'select_more_than_one_category' => 'Please select more than one category', - 'select_more_than_one_budget' => 'Please select more than one budget', - 'select_more_than_one_tag' => 'Please select more than one tag', - 'from_to' => 'From :start to :end', - 'from_to_breadcrumb' => 'from :start to :end', - 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', - - // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Update category', - 'updated_category' => 'Updated category ":name"', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'deleted_category' => 'Deleted category ":name"', - 'store_category' => 'Store new category', - 'stored_category' => 'Stored new category ":name"', - 'without_category_between' => 'Without category between :start and :end', - - // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'updated_withdrawal' => 'Updated withdrawal ":description"', - 'updated_deposit' => 'Updated deposit ":description"', - 'updated_transfer' => 'Updated transfer ":description"', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', - 'deleted_withdrawal' => 'Successfully deleted withdrawal ":description"', - 'deleted_deposit' => 'Successfully deleted deposit ":description"', - 'deleted_transfer' => 'Successfully deleted transfer ":description"', - 'stored_journal' => 'Successfully created new transaction ":description"', - 'select_transactions' => 'Select transactions', - 'stop_selection' => 'Stop selecting transactions', - 'edit_selected' => 'Edit selected', - 'delete_selected' => 'Delete selected', - 'mass_delete_journals' => 'Delete a number of transactions', - 'mass_edit_journals' => 'Edit a number of transactions', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', - - - // new user: - 'welcome' => 'Welcome to Firefly!', - - // 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', - 'divided' => 'divided', - 'toDivide' => 'left to divide', - - // menu and titles, should be recycled as often as possible: - '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', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'monthly' => 'Monthly', - 'profile' => 'Profile', - 'errors' => 'Errors', - - // reports: - 'report_default' => 'Default financial report between :start and :end', - 'report_audit' => 'Transaction history overview between :start and :end', - 'report_category' => 'Category report between :start and :end', - 'report_budget' => 'Budget report between :start and :end', - 'report_tag' => 'Tag report between :start and :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'quick_link_audit_report' => 'Transaction history overview', - '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', - 'active' => 'Active', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'show_full_list' => 'Show entire list', - 'show_only_top' => 'Show only 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_type_category' => 'Category report', - 'report_type_budget' => 'Budget report', - 'report_type_tag' => 'Tag report', - 'report_type_meta-history' => 'Categories, budgets and bills overview', - 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', - 'income_entry' => 'Income from account ":name" between :start and :end', - 'expense_entry' => 'Expenses to account ":name" between :start and :end', - 'category_entry' => 'Expenses in category ":name" between :start and :end', - 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', - 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', - 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', - 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', - 'reports_extra_options' => 'Extra options', - 'report_has_no_extra_options' => 'This report has no extra options', - 'reports_submit' => 'View report', - 'end_after_start_date' => 'End date of report must be after start date.', - 'select_category' => 'Select category(ies)', - 'select_budget' => 'Select budget(s).', - 'select_tag' => 'Select tag(s).', - 'income_per_category' => 'Income per category', - 'expense_per_category' => 'Expense per category', - 'expense_per_budget' => 'Expense per budget', - 'income_per_account' => 'Income per account', - 'expense_per_account' => 'Expense per account', - 'expense_per_tag' => 'Expense per tag', - 'income_per_tag' => 'Income per tag', - 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', - 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', - 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', - 'include_income_not_in_category' => 'Included income not in the selected category(ies)', - 'include_income_not_in_account' => 'Included income not in the selected account(s)', - 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', - 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', - 'everything_else' => 'Everything else', - 'income_and_expenses' => 'Income and expenses', - 'spent_average' => 'Spent (average)', - 'income_average' => 'Income (average)', - 'transaction_count' => 'Transaction count', - 'average_spending_per_account' => 'Average spending per account', - 'average_income_per_account' => 'Average income per account', - 'total' => 'Total', - 'description' => 'Description', - 'sum_of_period' => 'Sum of period', - 'average_in_period' => 'Average in period', - 'account_role_defaultAsset' => 'Default asset account', - 'account_role_sharedAsset' => 'Shared asset account', - 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', - - // charts: - 'chart' => 'Chart', - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'spent_in_budget' => 'Spent in budget', - 'left_to_spend' => 'Left to spend', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'no_budget' => '(no budget)', - 'max-amount' => 'Maximum amount', - 'min-amount' => 'Minumum amount', - 'journal-amount' => '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: - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'New piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'stored_piggy_bank' => 'Store new piggy bank ":name"', - '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', - 'suggested_amount' => 'Suggested monthly amount 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"', - 'updated_piggy_bank' => 'Updated 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"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":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"', - 'deleted_tag' => 'Deleted tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'updated_tag' => 'Updated tag ":tag"', - 'created_tag' => 'Tag ":tag" has been created!', - '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.', - - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', - - // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'rule_or_rules' => 'rule(s)', - 'rulegroup_or_groups' => 'rule group(s)', - 'setting_must_confirm_account' => 'Account confirmation', - 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'setting_send_email_notifications' => 'Send email notifications', - 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', - - - // split a transaction: - 'transaction_meta_data' => 'Transaction meta-data', - 'transaction_dates' => 'Transaction dates', - 'splits' => 'Splits', - 'split_title_withdrawal' => 'Split your new withdrawal', - 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', - 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', - 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', - 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', - 'split_title_deposit' => 'Split your new deposit', - 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', - 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', - 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', - 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_deposit' => 'Store splitted deposit', - 'split_title_transfer' => 'Split your new transfer', - 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', - 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', - 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', - 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_transfer' => 'Store splitted transfer', - 'add_another_split' => 'Add another split', - 'split-transactions' => 'Split transactions', - 'split-new-transaction' => 'Split a new transaction', - 'do_split' => 'Do a split', - 'split_this_withdrawal' => 'Split this withdrawal', - 'split_this_deposit' => 'Split this deposit', - 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', - 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', - 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', - - // sandstorm.io errors and messages: - 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', - - // empty lists? no objects? instructions: - 'no_transactions_in_period' => 'There are no transactions in this period.', - 'no_accounts_title_asset' => 'Let\'s create an asset account!', - 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', - 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', - 'no_accounts_create_asset' => 'Create an asset account', - 'no_accounts_title_expense' => 'Let\'s create an expense account!', - 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', - 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_expense' => 'Create an expense account', - 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', - 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_revenue' => 'Create a revenue account', - 'no_budgets_title_default' => 'Let\'s create a budget', - 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', - 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', - 'no_budgets_create_default' => 'Create a budget', - 'no_categories_title_default' => 'Let\'s create a category!', - 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', - 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_categories_create_default' => 'Create a category', - 'no_tags_title_default' => 'Let\'s create a tag!', - 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', - 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_tags_create_default' => 'Create a tag', - 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', - 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', - 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', - 'no_transactions_create_withdrawal' => 'Create an expense', - 'no_transactions_title_deposit' => 'Let\'s create some income!', - 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', - 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', - 'no_transactions_create_deposit' => 'Create a deposit', - 'no_transactions_title_transfers' => 'Let\'s create a transfer!', - 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', - 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', - 'no_transactions_create_transfers' => 'Create a transfer', - 'no_piggies_title_default' => 'Let\'s create a piggy bank!', - 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', - 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', - 'no_piggies_create_default' => 'Create a new piggy bank', - 'no_bills_title_default' => 'Let\'s create a bill!', - 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', - 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', - 'no_bills_create_default' => 'Create a bill', - - -]; \ No newline at end of file diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php deleted file mode 100644 index 20be49e75a..0000000000 --- a/resources/lang/es_ES/form.php +++ /dev/null @@ -1,189 +0,0 @@ - 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_asset_source_account' => 'Asset account (source)', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'account_from_id' => 'From account', - 'account_to_id' => 'To account', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tagPosition' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'longitude_latitude' => 'Location', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'exchanged_amount' => 'Exchanged amount', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - - 'revenue_account_source' => 'Revenue account (source)', - 'source_account_asset' => 'Source account (asset account)', - 'destination_account_expense' => 'Destination account (expense account)', - 'destination_account_asset' => 'Destination account (asset account)', - 'source_account_revenue' => 'Source account (revenue account)', - 'type' => 'Type', - 'convert_Withdrawal' => 'Convert withdrawal', - 'convert_Deposit' => 'Convert deposit', - 'convert_Transfer' => 'Convert transfer', - - - 'amount' => 'Amount', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'noPiggybank' => '(no piggy bank)', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - '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_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - '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"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - '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.', - - 'email' => 'Email address', - 'password' => 'Password', - 'password_confirmation' => 'Password (again)', - 'blocked' => 'Is blocked?', - 'blocked_code' => 'Reason for block', - - - // admin - 'domain' => 'Domain', - 'single_user_mode' => 'Single user mode', - 'must_confirm_account' => 'New users must activate account', - 'is_demo_site' => 'Is demo site', - - - // import - 'import_file' => 'Import file', - 'configuration_file' => 'Configuration file', - 'import_file_type' => 'Import file type', - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - 'csv_delimiter' => 'CSV field delimiter', - 'csv_import_account' => 'Default import account', - 'csv_config' => 'CSV import configuration', - - - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'internal_reference' => 'Internal reference', -]; \ No newline at end of file diff --git a/resources/lang/es_ES/help.php b/resources/lang/es_ES/help.php deleted file mode 100644 index 61210ffe41..0000000000 --- a/resources/lang/es_ES/help.php +++ /dev/null @@ -1,33 +0,0 @@ - '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 finances.', - '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', -]; \ No newline at end of file diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php deleted file mode 100644 index 90625d54e6..0000000000 --- a/resources/lang/es_ES/list.php +++ /dev/null @@ -1,89 +0,0 @@ - 'Buttons', - 'icon' => 'Icon', - 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference between :start and :end', - 'matchedOn' => 'Matched on', - 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', - 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', -]; \ No newline at end of file diff --git a/resources/lang/es_ES/passwords.php b/resources/lang/es_ES/passwords.php index f61f371a0d..1dfc1a738c 100644 --- a/resources/lang/es_ES/passwords.php +++ b/resources/lang/es_ES/passwords.php @@ -16,4 +16,4 @@ return [ 'sent' => 'Te enviamos un correo con el link para reestablecer tu contraseña!', 'reset' => 'Tu contraseña fue reestablecida!', 'blocked' => 'Buen intento.', -]; \ No newline at end of file +]; diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 843a9438eb..e5ef49397a 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -88,4 +88,4 @@ return [ 'in_array' => 'The :attribute field does not exist in :other.', 'present' => 'The :attribute field must be present.', 'amount_zero' => 'The total amount cannot be zero', -]; \ No newline at end of file +]; diff --git a/resources/lang/hr_HR/auth.php b/resources/lang/hr_HR/auth.php deleted file mode 100644 index 5d833b3d68..0000000000 --- a/resources/lang/hr_HR/auth.php +++ /dev/null @@ -1,28 +0,0 @@ - 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/breadcrumbs.php b/resources/lang/hr_HR/breadcrumbs.php deleted file mode 100644 index 4b23411578..0000000000 --- a/resources/lang/hr_HR/breadcrumbs.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Početna', - 'edit_currency' => 'Uredi valutu ":name"', - 'delete_currency' => 'Obriši valutu ":name"', - 'newPiggyBank' => 'Kreiraj novu kasicu prasicu', - 'edit_piggyBank' => 'Uredi kasicu prasicu ":name"', - 'preferences' => 'Postavke', - 'profile' => 'Profil', - 'changePassword' => 'Promijeni lozinku', - 'bills' => 'Bills', - 'newBill' => 'New bill', - 'edit_bill' => 'Edit bill ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'reports' => 'Izvještaji', - 'searchResult' => 'Pretraživanje za ":query"', - 'withdrawal_list' => 'Troškovi', - 'deposit_list' => 'Revenue, income and deposits', - 'transfer_list' => 'Transfers', - 'transfers_list' => 'Transfers', - 'create_withdrawal' => 'Create new withdrawal', - 'create_deposit' => 'Create new deposit', - 'create_transfer' => 'Create new transfer', - 'edit_journal' => 'Uredi transakciju ":description"', - 'delete_journal' => 'Obriši transakciju ":description"', - 'tags' => 'Oznake', - 'createTag' => 'Kreiraj novu oznaku', - 'edit_tag' => 'Uredi oznaku ":tag"', - 'delete_tag' => 'Obriši oznaku ":tag"', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/config.php b/resources/lang/hr_HR/config.php deleted file mode 100644 index 61ed65512d..0000000000 --- a/resources/lang/hr_HR/config.php +++ /dev/null @@ -1,23 +0,0 @@ - 'hr, Croatian, hr_HR, hr_HR.utf8, hr_HR.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Tjedan %W, %Y', - 'quarter_of_year' => '%B %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/csv.php b/resources/lang/hr_HR/csv.php deleted file mode 100644 index 4424610191..0000000000 --- a/resources/lang/hr_HR/csv.php +++ /dev/null @@ -1,80 +0,0 @@ - 'Configure your import', - 'import_configure_intro' => 'There are some options for your CSV import. Please indicate if your CSV file contains headers on the first column, and what the date format of your date-fields is. That might require some experimentation. The field delimiter is usually a ",", but could also be a ";". Check this carefully.', - 'import_configure_form' => 'Basic CSV import options', - 'header_help' => 'Check this if the first row of your CSV file are the column titles', - 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', - - // roles - 'column_roles_title' => 'Define column roles', - 'column_roles_table' => 'Table', - 'column_name' => 'Name of column', - 'column_example' => 'Column example data', - 'column_role' => 'Column data meaning', - 'do_map_value' => 'Map these values', - 'column' => 'Column', - 'no_example_data' => 'No example data available', - 'store_column_roles' => 'Continue import', - 'do_not_map' => '(do not map)', - 'map_title' => 'Connect import data to Firefly III data', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', - 'store_column_mapping' => 'Store mapping', - - // map things. - - - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching Firefly)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching Firefly)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching Firefly)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching Firefly)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching Firefly)', - 'column_currency-name' => 'Currency name (matching Firefly)', - 'column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'column_sepa-db' => 'SEPA Direct Debet', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/demo.php b/resources/lang/hr_HR/demo.php deleted file mode 100644 index e7f8ea934d..0000000000 --- a/resources/lang/hr_HR/demo.php +++ /dev/null @@ -1,24 +0,0 @@ - 'Sorry, there is no extra demo-explanation text for this page.', - 'see_help_icon' => 'However, the -icon in the top right corner may tell you more.', - 'index' => 'Welcome to Firefly III! On this page you get a quick overview of your finances. For more information, check out Accounts → Asset Accounts and of course the Budgets and Reports pages. Or just take a look around and see where you end up.', - 'accounts-index' => 'Asset accounts are your personal bank accounts. Expense accounts are the accounts you spend money at, such as stores and friends. Revenue accounts are accounts you receive money from, such as your job, the government or other sources of income. On this page you can edit or remove them.', - 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', - 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', - 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', - 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', - 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', - 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', - 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', - 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', - 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/firefly.php b/resources/lang/hr_HR/firefly.php deleted file mode 100644 index a8d8567267..0000000000 --- a/resources/lang/hr_HR/firefly.php +++ /dev/null @@ -1,1063 +0,0 @@ - 'incomplete translation', - 'close' => 'Close', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', - 'advanced_search' => 'Advanced search', - 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', - '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.', - 'expired_error' => 'Your account has expired, and can no longer be used.', - '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.', - 'help_may_not_be_your_language' => 'This help text is in English. It is not yet 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', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - '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.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'search' => 'Search', - 'search_found_accounts' => 'Found :count account(s) for your query.', - 'search_found_categories' => 'Found :count category(ies) for your query.', - 'search_found_budgets' => 'Found :count budget(s) for your query.', - 'search_found_tags' => 'Found :count tag(s) for your query.', - 'search_found_transactions' => 'Found :count transaction(s) for your query.', - 'results_limited' => 'The results are limited to :count entries.', - 'tagbalancingAct' => 'Balancing act', - 'tagadvancePayment' => 'Advance payment', - 'tagnothing' => '', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account(s)', - 'destination_accounts' => 'Destination account(s)', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', - 'nothing_to_display' => 'There are no transactions to show you', - 'show_all_no_filter' => 'Show all transactions without grouping them by date.', - 'expenses_by_category' => 'Expenses by category', - 'expenses_by_budget' => 'Expenses by budget', - 'income_by_category' => 'Income by category', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', - 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', - 'sum_of_expenses' => 'Sum of expenses', - 'sum_of_income' => 'Sum of income', - 'total_sum' => 'Total sum', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', - 'cannot_change_demo' => 'You cannot change the password of the demonstration account.', - 'cannot_delete_demo' => 'You cannot remove the demonstration account.', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demonstration account', - 'per_period' => 'Per period', - 'all_periods' => 'All periods', - 'current_period' => 'Current period', - 'show_the_current_period_and_overview' => 'Show the current period and overview', - 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', - 'budget_in_period_breadcrumb' => 'Between :start and :end', - 'clone_withdrawal' => 'Clone this withdrawal', - 'clone_deposit' => 'Clone this deposit', - 'clone_transfer' => 'Clone this transfer', - 'transaction_journal_other_options' => 'Other options', - 'multi_select_no_selection' => 'None selected', - 'multi_select_all_selected' => 'All selected', - 'multi_select_filter_placeholder' => 'Find..', - 'between_dates_breadcrumb' => 'Between :start and :end', - 'all_journals_without_budget' => 'All transactions without a budget', - 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_without_category' => 'All transactions without a category', - 'journals_without_category' => 'Transactions without a category', - 'all_journals_for_account' => 'All transactions for account :name', - 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', - 'transferred' => 'Transferred', - 'all_withdrawal' => 'All expenses', - 'all_transactions' => 'All transactions', - 'title_withdrawal_between' => 'All expenses between :start and :end', - 'all_deposit' => 'All revenue', - 'title_deposit_between' => 'All revenue between :start and :end', - 'all_transfers' => 'All transfers', - 'title_transfers_between' => 'All transfers between :start and :end', - 'all_transfer' => 'All transfers', - 'all_journals_for_tag' => 'All transactions for tag ":tag"', - 'title_transfer_between' => 'All transfers between :start and :end', - 'all_journals_for_category' => 'All transactions for category :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', - 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', - 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', - 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', - 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', - 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', - - // repeat frequencies: - 'repeat_freq_yearly' => 'yearly', - 'repeat_freq_monthly' => 'monthly', - 'weekly' => 'weekly', - 'quarterly' => 'quarterly', - 'half-year' => 'every half year', - 'yearly' => 'yearly', - // account confirmation: - 'confirm_account_header' => 'Please confirm your account', - 'confirm_account_intro' => 'An email has been sent to the address you used during your registration. Please check it out for further instructions. If you did not get this message, you can have Firefly send it again.', - 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.', - 'account_is_confirmed' => 'Your account has been confirmed!', - 'invalid_activation_code' => 'It seems the code you are using is not valid, or has expired.', - 'confirm_account_is_resent_header' => 'The confirmation has been resent', - 'confirm_account_is_resent_text' => 'The confirmation message has been resent. If you still did not receive the confirmation message, please contact the site owner at :owner or check the log files to see what went wrong.', - 'confirm_account_is_resent_go_home' => 'Go to the index page of Firefly', - 'confirm_account_not_resent_header' => 'Something went wrong :(', - 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.', - 'confirm_account_not_resent_go_home' => 'Go to the index page of Firefly', - - // 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_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"', - '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', - 'rule_group_select_transactions' => 'Execute rule group ":title" on 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_category_is' => 'Category is ":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..', - 'rule_trigger_from_account_contains_choice' => 'Source account contains..', - 'rule_trigger_to_account_starts_choice' => 'Destination account starts with..', - 'rule_trigger_to_account_ends_choice' => 'Destination account ends with..', - 'rule_trigger_to_account_is_choice' => 'Destination account is..', - 'rule_trigger_to_account_contains_choice' => 'Destination account contains..', - 'rule_trigger_transaction_type_choice' => 'Transaction is of type..', - 'rule_trigger_amount_less_choice' => 'Amount is less than..', - 'rule_trigger_amount_exactly_choice' => 'Amount is..', - 'rule_trigger_amount_more_choice' => 'Amount is more than..', - 'rule_trigger_description_starts_choice' => 'Description starts with..', - 'rule_trigger_description_ends_choice' => 'Description ends with..', - 'rule_trigger_description_contains_choice' => 'Description contains..', - 'rule_trigger_description_is_choice' => 'Description is..', - 'rule_trigger_category_is_choice' => 'Category is..', - 'rule_trigger_budget_is_choice' => 'Budget is..', - 'rule_trigger_tag_is_choice' => '(A) tag is..', - 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', - 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', - 'rule_trigger_store_journal' => 'When a transaction is created', - 'rule_trigger_update_journal' => 'When a transaction 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_action_set_source_account_choice' => 'Set source account to...', - 'rule_action_set_source_account' => 'Set source account to :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to...', - 'rule_action_set_destination_account' => 'Set destination account to :action_value', - - // tags - '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_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_1Y' => 'One year', - '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', - 'saved_preferences' => 'Preferences saved!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', - 'transaction_page_size_title' => 'Page size', - 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', - 'transaction_page_size_label' => 'Page size', - 'between_dates' => '(:start and :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', - 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', - 'optional_tj_date_fields' => 'Date fields', - 'optional_tj_business_fields' => 'Business fields', - 'optional_tj_attachment_fields' => 'Attachment fields', - 'pref_optional_tj_interest_date' => 'Interest date', - 'pref_optional_tj_book_date' => 'Book date', - 'pref_optional_tj_process_date' => 'Processing date', - 'pref_optional_tj_due_date' => 'Due date', - 'pref_optional_tj_payment_date' => 'Payment date', - 'pref_optional_tj_invoice_date' => 'Invoice date', - 'pref_optional_tj_internal_reference' => 'Internal reference', - 'pref_optional_tj_notes' => 'Notes', - 'pref_optional_tj_attachments' => 'Attachments', - 'optional_field_meta_dates' => 'Dates', - 'optional_field_meta_business' => 'Business', - 'optional_field_attachments' => 'Attachments', - 'optional_field_meta_data' => 'Optional meta data', - - - // 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!', - - - // 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"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - - // tour: - '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', - - // convert stuff: - 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', - 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', - 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', - 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', - 'convert_to_Deposit' => 'Convert ":description" to a deposit', - 'convert_to_Transfer' => 'Convert ":description" to a transfer', - 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', - 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', - 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', - 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', - 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', - 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', - 'transaction_journal_convert_options' => 'Convert this transaction', - 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', - 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', - 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', - 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', - 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', - 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', - 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', - 'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.', - 'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.', - 'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.', - 'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into :sourceName instead of taken from it.', - 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', - 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', - 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', - 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', - 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', - 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', - 'converted_to_Deposit' => 'The transaction has been converted to a deposit', - 'converted_to_Transfer' => 'The transaction has been converted to a transfer', - - - // 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', - - // currencies: - 'create_currency' => 'Create a new currency', - '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 it is still in use.', - '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.', - 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', - 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', - - // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - - // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => 'Store new budget', - 'stored_new_budget' => 'Stored new budget ":name"', - 'available_between' => 'Available between :start and :end', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactions_no_budget' => 'Expenses without budget between :start and :end', - 'spent_between' => 'Spent between :start and :end', - '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"', - 'deleted_budget' => 'Deleted budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'updated_budget' => 'Updated budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', - 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', - - // bills: - 'matching_on' => 'Matching on', - 'between_amounts' => 'between :low and :high.', - 'repeats' => 'Repeats', - 'connected_journals' => 'Connected transactions', - 'auto_match_on' => 'Automatically matched by Firefly', - 'auto_match_off' => 'Not automatically matched by Firefly', - 'next_expected_match' => 'Next expected match', - 'delete_bill' => 'Delete bill ":name"', - 'deleted_bill' => 'Deleted bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'more' => 'More', - 'rescan_old' => 'Rescan old transactions', - 'update_bill' => 'Update bill', - 'updated_bill' => 'Updated bill ":name"', - 'store_new_bill' => 'Store new bill', - 'stored_new_bill' => 'Stored new bill ":name"', - 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', - 'rescanned_bill' => 'Rescanned everything.', - 'average_bill_amount_year' => 'Average bill amount (:year)', - 'average_bill_amount_overall' => 'Average bill amount (overall)', - 'not_or_not_yet' => 'Not (yet)', - 'not_expected_period' => 'Not expected this period', - // 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', - 'cash_accounts' => 'Cash accounts', - 'Cash account' => 'Cash account', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', - 'stored_new_account' => 'New account ":name" stored!', - 'updated_account' => 'Updated account ":name"', - 'credit_card_options' => 'Credit card options', - 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', - 'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.', - 'select_more_than_one_account' => 'Please select more than one account', - 'select_more_than_one_category' => 'Please select more than one category', - 'select_more_than_one_budget' => 'Please select more than one budget', - 'select_more_than_one_tag' => 'Please select more than one tag', - 'from_to' => 'From :start to :end', - 'from_to_breadcrumb' => 'from :start to :end', - 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', - - // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Update category', - 'updated_category' => 'Updated category ":name"', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'deleted_category' => 'Deleted category ":name"', - 'store_category' => 'Store new category', - 'stored_category' => 'Stored new category ":name"', - 'without_category_between' => 'Without category between :start and :end', - - // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'updated_withdrawal' => 'Updated withdrawal ":description"', - 'updated_deposit' => 'Updated deposit ":description"', - 'updated_transfer' => 'Updated transfer ":description"', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', - 'deleted_withdrawal' => 'Successfully deleted withdrawal ":description"', - 'deleted_deposit' => 'Successfully deleted deposit ":description"', - 'deleted_transfer' => 'Successfully deleted transfer ":description"', - 'stored_journal' => 'Successfully created new transaction ":description"', - 'select_transactions' => 'Select transactions', - 'stop_selection' => 'Stop selecting transactions', - 'edit_selected' => 'Edit selected', - 'delete_selected' => 'Delete selected', - 'mass_delete_journals' => 'Delete a number of transactions', - 'mass_edit_journals' => 'Edit a number of transactions', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', - - - // new user: - 'welcome' => 'Welcome to Firefly!', - - // 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', - 'divided' => 'divided', - 'toDivide' => 'left to divide', - - // menu and titles, should be recycled as often as possible: - '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', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'monthly' => 'Monthly', - 'profile' => 'Profile', - 'errors' => 'Errors', - - // reports: - 'report_default' => 'Default financial report between :start and :end', - 'report_audit' => 'Transaction history overview between :start and :end', - 'report_category' => 'Category report between :start and :end', - 'report_budget' => 'Budget report between :start and :end', - 'report_tag' => 'Tag report between :start and :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'quick_link_audit_report' => 'Transaction history overview', - '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', - 'active' => 'Active', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'show_full_list' => 'Show entire list', - 'show_only_top' => 'Show only 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_type_category' => 'Category report', - 'report_type_budget' => 'Budget report', - 'report_type_tag' => 'Tag report', - 'report_type_meta-history' => 'Categories, budgets and bills overview', - 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', - 'income_entry' => 'Income from account ":name" between :start and :end', - 'expense_entry' => 'Expenses to account ":name" between :start and :end', - 'category_entry' => 'Expenses in category ":name" between :start and :end', - 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', - 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', - 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', - 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', - 'reports_extra_options' => 'Extra options', - 'report_has_no_extra_options' => 'This report has no extra options', - 'reports_submit' => 'View report', - 'end_after_start_date' => 'End date of report must be after start date.', - 'select_category' => 'Select category(ies)', - 'select_budget' => 'Select budget(s).', - 'select_tag' => 'Select tag(s).', - 'income_per_category' => 'Income per category', - 'expense_per_category' => 'Expense per category', - 'expense_per_budget' => 'Expense per budget', - 'income_per_account' => 'Income per account', - 'expense_per_account' => 'Expense per account', - 'expense_per_tag' => 'Expense per tag', - 'income_per_tag' => 'Income per tag', - 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', - 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', - 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', - 'include_income_not_in_category' => 'Included income not in the selected category(ies)', - 'include_income_not_in_account' => 'Included income not in the selected account(s)', - 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', - 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', - 'everything_else' => 'Everything else', - 'income_and_expenses' => 'Income and expenses', - 'spent_average' => 'Spent (average)', - 'income_average' => 'Income (average)', - 'transaction_count' => 'Transaction count', - 'average_spending_per_account' => 'Average spending per account', - 'average_income_per_account' => 'Average income per account', - 'total' => 'Total', - 'description' => 'Description', - 'sum_of_period' => 'Sum of period', - 'average_in_period' => 'Average in period', - 'account_role_defaultAsset' => 'Default asset account', - 'account_role_sharedAsset' => 'Shared asset account', - 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', - - // charts: - 'chart' => 'Chart', - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'spent_in_budget' => 'Spent in budget', - 'left_to_spend' => 'Left to spend', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'no_budget' => '(no budget)', - 'max-amount' => 'Maximum amount', - 'min-amount' => 'Minumum amount', - 'journal-amount' => '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: - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'New piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'stored_piggy_bank' => 'Store new piggy bank ":name"', - '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', - 'suggested_amount' => 'Suggested monthly amount 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"', - 'updated_piggy_bank' => 'Updated 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"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":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"', - 'deleted_tag' => 'Deleted tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'updated_tag' => 'Updated tag ":tag"', - 'created_tag' => 'Tag ":tag" has been created!', - '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.', - - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', - - // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'rule_or_rules' => 'rule(s)', - 'rulegroup_or_groups' => 'rule group(s)', - 'setting_must_confirm_account' => 'Account confirmation', - 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'setting_send_email_notifications' => 'Send email notifications', - 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', - - - // split a transaction: - 'transaction_meta_data' => 'Transaction meta-data', - 'transaction_dates' => 'Transaction dates', - 'splits' => 'Splits', - 'split_title_withdrawal' => 'Split your new withdrawal', - 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', - 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', - 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', - 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', - 'split_title_deposit' => 'Split your new deposit', - 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', - 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', - 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', - 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_deposit' => 'Store splitted deposit', - 'split_title_transfer' => 'Split your new transfer', - 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', - 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', - 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', - 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_transfer' => 'Store splitted transfer', - 'add_another_split' => 'Add another split', - 'split-transactions' => 'Split transactions', - 'split-new-transaction' => 'Split a new transaction', - 'do_split' => 'Do a split', - 'split_this_withdrawal' => 'Split this withdrawal', - 'split_this_deposit' => 'Split this deposit', - 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', - 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', - 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', - - // sandstorm.io errors and messages: - 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', - - // empty lists? no objects? instructions: - 'no_transactions_in_period' => 'There are no transactions in this period.', - 'no_accounts_title_asset' => 'Let\'s create an asset account!', - 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', - 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', - 'no_accounts_create_asset' => 'Create an asset account', - 'no_accounts_title_expense' => 'Let\'s create an expense account!', - 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', - 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_expense' => 'Create an expense account', - 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', - 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_revenue' => 'Create a revenue account', - 'no_budgets_title_default' => 'Let\'s create a budget', - 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', - 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', - 'no_budgets_create_default' => 'Create a budget', - 'no_categories_title_default' => 'Let\'s create a category!', - 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', - 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_categories_create_default' => 'Create a category', - 'no_tags_title_default' => 'Let\'s create a tag!', - 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', - 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_tags_create_default' => 'Create a tag', - 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', - 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', - 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', - 'no_transactions_create_withdrawal' => 'Create an expense', - 'no_transactions_title_deposit' => 'Let\'s create some income!', - 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', - 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', - 'no_transactions_create_deposit' => 'Create a deposit', - 'no_transactions_title_transfers' => 'Let\'s create a transfer!', - 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', - 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', - 'no_transactions_create_transfers' => 'Create a transfer', - 'no_piggies_title_default' => 'Let\'s create a piggy bank!', - 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', - 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', - 'no_piggies_create_default' => 'Create a new piggy bank', - 'no_bills_title_default' => 'Let\'s create a bill!', - 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', - 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', - 'no_bills_create_default' => 'Create a bill', - - -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/form.php b/resources/lang/hr_HR/form.php deleted file mode 100644 index 20be49e75a..0000000000 --- a/resources/lang/hr_HR/form.php +++ /dev/null @@ -1,189 +0,0 @@ - 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_asset_source_account' => 'Asset account (source)', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'account_from_id' => 'From account', - 'account_to_id' => 'To account', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tagPosition' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'longitude_latitude' => 'Location', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'exchanged_amount' => 'Exchanged amount', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - - 'revenue_account_source' => 'Revenue account (source)', - 'source_account_asset' => 'Source account (asset account)', - 'destination_account_expense' => 'Destination account (expense account)', - 'destination_account_asset' => 'Destination account (asset account)', - 'source_account_revenue' => 'Source account (revenue account)', - 'type' => 'Type', - 'convert_Withdrawal' => 'Convert withdrawal', - 'convert_Deposit' => 'Convert deposit', - 'convert_Transfer' => 'Convert transfer', - - - 'amount' => 'Amount', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'noPiggybank' => '(no piggy bank)', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - '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_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - '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"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - '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.', - - 'email' => 'Email address', - 'password' => 'Password', - 'password_confirmation' => 'Password (again)', - 'blocked' => 'Is blocked?', - 'blocked_code' => 'Reason for block', - - - // admin - 'domain' => 'Domain', - 'single_user_mode' => 'Single user mode', - 'must_confirm_account' => 'New users must activate account', - 'is_demo_site' => 'Is demo site', - - - // import - 'import_file' => 'Import file', - 'configuration_file' => 'Configuration file', - 'import_file_type' => 'Import file type', - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - 'csv_delimiter' => 'CSV field delimiter', - 'csv_import_account' => 'Default import account', - 'csv_config' => 'CSV import configuration', - - - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'internal_reference' => 'Internal reference', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/help.php b/resources/lang/hr_HR/help.php deleted file mode 100644 index 61210ffe41..0000000000 --- a/resources/lang/hr_HR/help.php +++ /dev/null @@ -1,33 +0,0 @@ - '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 finances.', - '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', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/list.php b/resources/lang/hr_HR/list.php deleted file mode 100644 index 90625d54e6..0000000000 --- a/resources/lang/hr_HR/list.php +++ /dev/null @@ -1,89 +0,0 @@ - 'Buttons', - 'icon' => 'Icon', - 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference between :start and :end', - 'matchedOn' => 'Matched on', - 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', - 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/pagination.php b/resources/lang/hr_HR/pagination.php deleted file mode 100644 index 0efae8cb62..0000000000 --- a/resources/lang/hr_HR/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - '« Prethodna', - 'next' => 'Sljedeća »', - -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/passwords.php b/resources/lang/hr_HR/passwords.php deleted file mode 100644 index 80362c2eca..0000000000 --- a/resources/lang/hr_HR/passwords.php +++ /dev/null @@ -1,19 +0,0 @@ - 'Passwords must be at least six characters and match the confirmation.', - 'user' => 'Ne možemo naći korisnika s tom e-mail adresom.', - 'token' => 'Token za promjenu lozinke je nevažeći.', - 'sent' => 'Na e-mail smo ti poslali poveznicu za promjenu lozinke!', - 'reset' => 'Tvoja lozinka je promijenjena!', - 'blocked' => 'Lijep pokušaj.', -]; \ No newline at end of file diff --git a/resources/lang/hr_HR/validation.php b/resources/lang/hr_HR/validation.php deleted file mode 100644 index addd0e6f39..0000000000 --- a/resources/lang/hr_HR/validation.php +++ /dev/null @@ -1,91 +0,0 @@ - 'This is not a valid IBAN.', - 'unique_account_number_for_user' => 'It looks like this account number is already in use.', - 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', - '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.', - 'belongs_to_user' => 'The value of :attribute is unknown', - 'accepted' => 'The :attribute must be accepted.', - 'bic' => 'This is not a valid BIC.', - 'more' => ':attribute must be larger than zero.', - '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.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'file' => 'The :attribute must be a file.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'present' => 'The :attribute field must be present.', - 'amount_zero' => 'The total amount cannot be zero', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/auth.php b/resources/lang/ru_RU/auth.php deleted file mode 100644 index bc725bca9b..0000000000 --- a/resources/lang/ru_RU/auth.php +++ /dev/null @@ -1,28 +0,0 @@ - 'Неправильный адрес электронной почты или пароль.', - 'throttle' => 'Слишком много попыток логина. Пожалуйста, попробуйте снова через :seconds секунд.', - -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/breadcrumbs.php b/resources/lang/ru_RU/breadcrumbs.php deleted file mode 100644 index 646511cec4..0000000000 --- a/resources/lang/ru_RU/breadcrumbs.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Главная', - 'edit_currency' => 'Edit currency ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'newPiggyBank' => 'Создать новую копилку', - 'edit_piggyBank' => 'Редактировать копилку ":name"', - 'preferences' => 'Параметры', - 'profile' => 'Профиль', - 'changePassword' => 'Сменить пароль', - 'bills' => 'Счета', - 'newBill' => 'Новый счет', - 'edit_bill' => 'Редактировать счет ":name"', - 'delete_bill' => 'Удалить счет ":name"', - 'reports' => 'Отчёты', - 'searchResult' => 'Поиск ":query"', - 'withdrawal_list' => 'Расходы', - 'deposit_list' => 'Доходы и депозиты', - 'transfer_list' => 'Переводы', - 'transfers_list' => 'Переводы', - 'create_withdrawal' => 'Создать новый вывод средств', - 'create_deposit' => 'Создать новый депозит', - 'create_transfer' => 'Создать новый перевод', - 'edit_journal' => 'Редактировать транзакцию ":description"', - 'delete_journal' => 'Удалить транзакцию ":description"', - 'tags' => 'Теги', - 'createTag' => 'Создать новый тег', - 'edit_tag' => 'Редактировать тег ":tag"', - 'delete_tag' => 'Удалить тег ":tag"', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/config.php b/resources/lang/ru_RU/config.php deleted file mode 100644 index 4e7ce17f0f..0000000000 --- a/resources/lang/ru_RU/config.php +++ /dev/null @@ -1,23 +0,0 @@ - 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%B %e, %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Неделя %W, %Y', - 'quarter_of_year' => '%B %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/csv.php b/resources/lang/ru_RU/csv.php deleted file mode 100644 index 0bfa2c13e0..0000000000 --- a/resources/lang/ru_RU/csv.php +++ /dev/null @@ -1,80 +0,0 @@ - 'Настройки импорта', - 'import_configure_intro' => 'Есть несколько вариантов для импорта CSV. Пожалуйста, укажите, содержит ли CSV-файл заголовки в первом столбце, и каков формат даты в вашем поле даты. Возможно, придется немного поэкспериментировать. Разделитель полей обычно «,», но также может быть «;». Внимательно проверьте это.', - 'import_configure_form' => 'Основные параметры импорта CSV', - 'header_help' => 'Проверьте, если первая строка CSV файла - это заголовки столбцов', - 'date_help' => 'Формат даты и времени в CSV файле. Следуйте формату, как показывает на этой странице. По умолчанию будет анализироваться даты, которые выглядят следующим образом: :dateExample.', - 'delimiter_help' => 'Выберите разделитель полей, который используется в вашем файле. Если не уверены, запятая - самый безопасный вариант.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', - - // roles - 'column_roles_title' => 'Define column roles', - 'column_roles_table' => 'Table', - 'column_name' => 'Name of column', - 'column_example' => 'Column example data', - 'column_role' => 'Column data meaning', - 'do_map_value' => 'Map these values', - 'column' => 'Column', - 'no_example_data' => 'No example data available', - 'store_column_roles' => 'Continue import', - 'do_not_map' => '(do not map)', - 'map_title' => 'Connect import data to Firefly III data', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', - 'store_column_mapping' => 'Store mapping', - - // map things. - - - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching Firefly)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching Firefly)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching Firefly)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching Firefly)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching Firefly)', - 'column_currency-name' => 'Currency name (matching Firefly)', - 'column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'column_sepa-db' => 'SEPA Direct Debet', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/demo.php b/resources/lang/ru_RU/demo.php deleted file mode 100644 index e7f8ea934d..0000000000 --- a/resources/lang/ru_RU/demo.php +++ /dev/null @@ -1,24 +0,0 @@ - 'Sorry, there is no extra demo-explanation text for this page.', - 'see_help_icon' => 'However, the -icon in the top right corner may tell you more.', - 'index' => 'Welcome to Firefly III! On this page you get a quick overview of your finances. For more information, check out Accounts → Asset Accounts and of course the Budgets and Reports pages. Or just take a look around and see where you end up.', - 'accounts-index' => 'Asset accounts are your personal bank accounts. Expense accounts are the accounts you spend money at, such as stores and friends. Revenue accounts are accounts you receive money from, such as your job, the government or other sources of income. On this page you can edit or remove them.', - 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', - 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', - 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', - 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', - 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', - 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', - 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', - 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', - 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php deleted file mode 100644 index a8d8567267..0000000000 --- a/resources/lang/ru_RU/firefly.php +++ /dev/null @@ -1,1063 +0,0 @@ - 'incomplete translation', - 'close' => 'Close', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', - 'advanced_search' => 'Advanced search', - 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', - '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.', - 'expired_error' => 'Your account has expired, and can no longer be used.', - '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.', - 'help_may_not_be_your_language' => 'This help text is in English. It is not yet 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', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - '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.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'search' => 'Search', - 'search_found_accounts' => 'Found :count account(s) for your query.', - 'search_found_categories' => 'Found :count category(ies) for your query.', - 'search_found_budgets' => 'Found :count budget(s) for your query.', - 'search_found_tags' => 'Found :count tag(s) for your query.', - 'search_found_transactions' => 'Found :count transaction(s) for your query.', - 'results_limited' => 'The results are limited to :count entries.', - 'tagbalancingAct' => 'Balancing act', - 'tagadvancePayment' => 'Advance payment', - 'tagnothing' => '', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account(s)', - 'destination_accounts' => 'Destination account(s)', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', - 'nothing_to_display' => 'There are no transactions to show you', - 'show_all_no_filter' => 'Show all transactions without grouping them by date.', - 'expenses_by_category' => 'Expenses by category', - 'expenses_by_budget' => 'Expenses by budget', - 'income_by_category' => 'Income by category', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', - 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', - 'sum_of_expenses' => 'Sum of expenses', - 'sum_of_income' => 'Sum of income', - 'total_sum' => 'Total sum', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', - 'cannot_change_demo' => 'You cannot change the password of the demonstration account.', - 'cannot_delete_demo' => 'You cannot remove the demonstration account.', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demonstration account', - 'per_period' => 'Per period', - 'all_periods' => 'All periods', - 'current_period' => 'Current period', - 'show_the_current_period_and_overview' => 'Show the current period and overview', - 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', - 'budget_in_period_breadcrumb' => 'Between :start and :end', - 'clone_withdrawal' => 'Clone this withdrawal', - 'clone_deposit' => 'Clone this deposit', - 'clone_transfer' => 'Clone this transfer', - 'transaction_journal_other_options' => 'Other options', - 'multi_select_no_selection' => 'None selected', - 'multi_select_all_selected' => 'All selected', - 'multi_select_filter_placeholder' => 'Find..', - 'between_dates_breadcrumb' => 'Between :start and :end', - 'all_journals_without_budget' => 'All transactions without a budget', - 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_without_category' => 'All transactions without a category', - 'journals_without_category' => 'Transactions without a category', - 'all_journals_for_account' => 'All transactions for account :name', - 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', - 'transferred' => 'Transferred', - 'all_withdrawal' => 'All expenses', - 'all_transactions' => 'All transactions', - 'title_withdrawal_between' => 'All expenses between :start and :end', - 'all_deposit' => 'All revenue', - 'title_deposit_between' => 'All revenue between :start and :end', - 'all_transfers' => 'All transfers', - 'title_transfers_between' => 'All transfers between :start and :end', - 'all_transfer' => 'All transfers', - 'all_journals_for_tag' => 'All transactions for tag ":tag"', - 'title_transfer_between' => 'All transfers between :start and :end', - 'all_journals_for_category' => 'All transactions for category :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', - 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', - 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', - 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', - 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', - 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', - - // repeat frequencies: - 'repeat_freq_yearly' => 'yearly', - 'repeat_freq_monthly' => 'monthly', - 'weekly' => 'weekly', - 'quarterly' => 'quarterly', - 'half-year' => 'every half year', - 'yearly' => 'yearly', - // account confirmation: - 'confirm_account_header' => 'Please confirm your account', - 'confirm_account_intro' => 'An email has been sent to the address you used during your registration. Please check it out for further instructions. If you did not get this message, you can have Firefly send it again.', - 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.', - 'account_is_confirmed' => 'Your account has been confirmed!', - 'invalid_activation_code' => 'It seems the code you are using is not valid, or has expired.', - 'confirm_account_is_resent_header' => 'The confirmation has been resent', - 'confirm_account_is_resent_text' => 'The confirmation message has been resent. If you still did not receive the confirmation message, please contact the site owner at :owner or check the log files to see what went wrong.', - 'confirm_account_is_resent_go_home' => 'Go to the index page of Firefly', - 'confirm_account_not_resent_header' => 'Something went wrong :(', - 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.', - 'confirm_account_not_resent_go_home' => 'Go to the index page of Firefly', - - // 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_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"', - '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', - 'rule_group_select_transactions' => 'Execute rule group ":title" on 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_category_is' => 'Category is ":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..', - 'rule_trigger_from_account_contains_choice' => 'Source account contains..', - 'rule_trigger_to_account_starts_choice' => 'Destination account starts with..', - 'rule_trigger_to_account_ends_choice' => 'Destination account ends with..', - 'rule_trigger_to_account_is_choice' => 'Destination account is..', - 'rule_trigger_to_account_contains_choice' => 'Destination account contains..', - 'rule_trigger_transaction_type_choice' => 'Transaction is of type..', - 'rule_trigger_amount_less_choice' => 'Amount is less than..', - 'rule_trigger_amount_exactly_choice' => 'Amount is..', - 'rule_trigger_amount_more_choice' => 'Amount is more than..', - 'rule_trigger_description_starts_choice' => 'Description starts with..', - 'rule_trigger_description_ends_choice' => 'Description ends with..', - 'rule_trigger_description_contains_choice' => 'Description contains..', - 'rule_trigger_description_is_choice' => 'Description is..', - 'rule_trigger_category_is_choice' => 'Category is..', - 'rule_trigger_budget_is_choice' => 'Budget is..', - 'rule_trigger_tag_is_choice' => '(A) tag is..', - 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', - 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', - 'rule_trigger_store_journal' => 'When a transaction is created', - 'rule_trigger_update_journal' => 'When a transaction 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_action_set_source_account_choice' => 'Set source account to...', - 'rule_action_set_source_account' => 'Set source account to :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to...', - 'rule_action_set_destination_account' => 'Set destination account to :action_value', - - // tags - '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_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_1Y' => 'One year', - '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', - 'saved_preferences' => 'Preferences saved!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', - 'transaction_page_size_title' => 'Page size', - 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', - 'transaction_page_size_label' => 'Page size', - 'between_dates' => '(:start and :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', - 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', - 'optional_tj_date_fields' => 'Date fields', - 'optional_tj_business_fields' => 'Business fields', - 'optional_tj_attachment_fields' => 'Attachment fields', - 'pref_optional_tj_interest_date' => 'Interest date', - 'pref_optional_tj_book_date' => 'Book date', - 'pref_optional_tj_process_date' => 'Processing date', - 'pref_optional_tj_due_date' => 'Due date', - 'pref_optional_tj_payment_date' => 'Payment date', - 'pref_optional_tj_invoice_date' => 'Invoice date', - 'pref_optional_tj_internal_reference' => 'Internal reference', - 'pref_optional_tj_notes' => 'Notes', - 'pref_optional_tj_attachments' => 'Attachments', - 'optional_field_meta_dates' => 'Dates', - 'optional_field_meta_business' => 'Business', - 'optional_field_attachments' => 'Attachments', - 'optional_field_meta_data' => 'Optional meta data', - - - // 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!', - - - // 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"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - - // tour: - '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', - - // convert stuff: - 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', - 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', - 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', - 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', - 'convert_to_Deposit' => 'Convert ":description" to a deposit', - 'convert_to_Transfer' => 'Convert ":description" to a transfer', - 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', - 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', - 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', - 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', - 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', - 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', - 'transaction_journal_convert_options' => 'Convert this transaction', - 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', - 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', - 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', - 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', - 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', - 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', - 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', - 'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.', - 'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.', - 'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.', - 'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into :sourceName instead of taken from it.', - 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', - 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', - 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', - 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', - 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', - 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', - 'converted_to_Deposit' => 'The transaction has been converted to a deposit', - 'converted_to_Transfer' => 'The transaction has been converted to a transfer', - - - // 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', - - // currencies: - 'create_currency' => 'Create a new currency', - '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 it is still in use.', - '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.', - 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', - 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', - - // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - - // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => 'Store new budget', - 'stored_new_budget' => 'Stored new budget ":name"', - 'available_between' => 'Available between :start and :end', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactions_no_budget' => 'Expenses without budget between :start and :end', - 'spent_between' => 'Spent between :start and :end', - '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"', - 'deleted_budget' => 'Deleted budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'updated_budget' => 'Updated budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', - 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', - - // bills: - 'matching_on' => 'Matching on', - 'between_amounts' => 'between :low and :high.', - 'repeats' => 'Repeats', - 'connected_journals' => 'Connected transactions', - 'auto_match_on' => 'Automatically matched by Firefly', - 'auto_match_off' => 'Not automatically matched by Firefly', - 'next_expected_match' => 'Next expected match', - 'delete_bill' => 'Delete bill ":name"', - 'deleted_bill' => 'Deleted bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'more' => 'More', - 'rescan_old' => 'Rescan old transactions', - 'update_bill' => 'Update bill', - 'updated_bill' => 'Updated bill ":name"', - 'store_new_bill' => 'Store new bill', - 'stored_new_bill' => 'Stored new bill ":name"', - 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', - 'rescanned_bill' => 'Rescanned everything.', - 'average_bill_amount_year' => 'Average bill amount (:year)', - 'average_bill_amount_overall' => 'Average bill amount (overall)', - 'not_or_not_yet' => 'Not (yet)', - 'not_expected_period' => 'Not expected this period', - // 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', - 'cash_accounts' => 'Cash accounts', - 'Cash account' => 'Cash account', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', - 'stored_new_account' => 'New account ":name" stored!', - 'updated_account' => 'Updated account ":name"', - 'credit_card_options' => 'Credit card options', - 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', - 'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.', - 'select_more_than_one_account' => 'Please select more than one account', - 'select_more_than_one_category' => 'Please select more than one category', - 'select_more_than_one_budget' => 'Please select more than one budget', - 'select_more_than_one_tag' => 'Please select more than one tag', - 'from_to' => 'From :start to :end', - 'from_to_breadcrumb' => 'from :start to :end', - 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', - - // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Update category', - 'updated_category' => 'Updated category ":name"', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'deleted_category' => 'Deleted category ":name"', - 'store_category' => 'Store new category', - 'stored_category' => 'Stored new category ":name"', - 'without_category_between' => 'Without category between :start and :end', - - // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'updated_withdrawal' => 'Updated withdrawal ":description"', - 'updated_deposit' => 'Updated deposit ":description"', - 'updated_transfer' => 'Updated transfer ":description"', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', - 'deleted_withdrawal' => 'Successfully deleted withdrawal ":description"', - 'deleted_deposit' => 'Successfully deleted deposit ":description"', - 'deleted_transfer' => 'Successfully deleted transfer ":description"', - 'stored_journal' => 'Successfully created new transaction ":description"', - 'select_transactions' => 'Select transactions', - 'stop_selection' => 'Stop selecting transactions', - 'edit_selected' => 'Edit selected', - 'delete_selected' => 'Delete selected', - 'mass_delete_journals' => 'Delete a number of transactions', - 'mass_edit_journals' => 'Edit a number of transactions', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', - - - // new user: - 'welcome' => 'Welcome to Firefly!', - - // 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', - 'divided' => 'divided', - 'toDivide' => 'left to divide', - - // menu and titles, should be recycled as often as possible: - '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', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'monthly' => 'Monthly', - 'profile' => 'Profile', - 'errors' => 'Errors', - - // reports: - 'report_default' => 'Default financial report between :start and :end', - 'report_audit' => 'Transaction history overview between :start and :end', - 'report_category' => 'Category report between :start and :end', - 'report_budget' => 'Budget report between :start and :end', - 'report_tag' => 'Tag report between :start and :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'quick_link_audit_report' => 'Transaction history overview', - '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', - 'active' => 'Active', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'show_full_list' => 'Show entire list', - 'show_only_top' => 'Show only 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_type_category' => 'Category report', - 'report_type_budget' => 'Budget report', - 'report_type_tag' => 'Tag report', - 'report_type_meta-history' => 'Categories, budgets and bills overview', - 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', - 'income_entry' => 'Income from account ":name" between :start and :end', - 'expense_entry' => 'Expenses to account ":name" between :start and :end', - 'category_entry' => 'Expenses in category ":name" between :start and :end', - 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', - 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', - 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', - 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', - 'reports_extra_options' => 'Extra options', - 'report_has_no_extra_options' => 'This report has no extra options', - 'reports_submit' => 'View report', - 'end_after_start_date' => 'End date of report must be after start date.', - 'select_category' => 'Select category(ies)', - 'select_budget' => 'Select budget(s).', - 'select_tag' => 'Select tag(s).', - 'income_per_category' => 'Income per category', - 'expense_per_category' => 'Expense per category', - 'expense_per_budget' => 'Expense per budget', - 'income_per_account' => 'Income per account', - 'expense_per_account' => 'Expense per account', - 'expense_per_tag' => 'Expense per tag', - 'income_per_tag' => 'Income per tag', - 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', - 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', - 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', - 'include_income_not_in_category' => 'Included income not in the selected category(ies)', - 'include_income_not_in_account' => 'Included income not in the selected account(s)', - 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', - 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', - 'everything_else' => 'Everything else', - 'income_and_expenses' => 'Income and expenses', - 'spent_average' => 'Spent (average)', - 'income_average' => 'Income (average)', - 'transaction_count' => 'Transaction count', - 'average_spending_per_account' => 'Average spending per account', - 'average_income_per_account' => 'Average income per account', - 'total' => 'Total', - 'description' => 'Description', - 'sum_of_period' => 'Sum of period', - 'average_in_period' => 'Average in period', - 'account_role_defaultAsset' => 'Default asset account', - 'account_role_sharedAsset' => 'Shared asset account', - 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', - - // charts: - 'chart' => 'Chart', - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'spent_in_budget' => 'Spent in budget', - 'left_to_spend' => 'Left to spend', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'no_budget' => '(no budget)', - 'max-amount' => 'Maximum amount', - 'min-amount' => 'Minumum amount', - 'journal-amount' => '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: - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'New piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'stored_piggy_bank' => 'Store new piggy bank ":name"', - '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', - 'suggested_amount' => 'Suggested monthly amount 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"', - 'updated_piggy_bank' => 'Updated 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"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":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"', - 'deleted_tag' => 'Deleted tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'updated_tag' => 'Updated tag ":tag"', - 'created_tag' => 'Tag ":tag" has been created!', - '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.', - - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', - - // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'rule_or_rules' => 'rule(s)', - 'rulegroup_or_groups' => 'rule group(s)', - 'setting_must_confirm_account' => 'Account confirmation', - 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'setting_send_email_notifications' => 'Send email notifications', - 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', - - - // split a transaction: - 'transaction_meta_data' => 'Transaction meta-data', - 'transaction_dates' => 'Transaction dates', - 'splits' => 'Splits', - 'split_title_withdrawal' => 'Split your new withdrawal', - 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', - 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', - 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', - 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', - 'split_title_deposit' => 'Split your new deposit', - 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', - 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', - 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', - 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_deposit' => 'Store splitted deposit', - 'split_title_transfer' => 'Split your new transfer', - 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', - 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', - 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', - 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_transfer' => 'Store splitted transfer', - 'add_another_split' => 'Add another split', - 'split-transactions' => 'Split transactions', - 'split-new-transaction' => 'Split a new transaction', - 'do_split' => 'Do a split', - 'split_this_withdrawal' => 'Split this withdrawal', - 'split_this_deposit' => 'Split this deposit', - 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', - 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', - 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', - - // sandstorm.io errors and messages: - 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', - - // empty lists? no objects? instructions: - 'no_transactions_in_period' => 'There are no transactions in this period.', - 'no_accounts_title_asset' => 'Let\'s create an asset account!', - 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', - 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', - 'no_accounts_create_asset' => 'Create an asset account', - 'no_accounts_title_expense' => 'Let\'s create an expense account!', - 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', - 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_expense' => 'Create an expense account', - 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', - 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_revenue' => 'Create a revenue account', - 'no_budgets_title_default' => 'Let\'s create a budget', - 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', - 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', - 'no_budgets_create_default' => 'Create a budget', - 'no_categories_title_default' => 'Let\'s create a category!', - 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', - 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_categories_create_default' => 'Create a category', - 'no_tags_title_default' => 'Let\'s create a tag!', - 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', - 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_tags_create_default' => 'Create a tag', - 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', - 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', - 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', - 'no_transactions_create_withdrawal' => 'Create an expense', - 'no_transactions_title_deposit' => 'Let\'s create some income!', - 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', - 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', - 'no_transactions_create_deposit' => 'Create a deposit', - 'no_transactions_title_transfers' => 'Let\'s create a transfer!', - 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', - 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', - 'no_transactions_create_transfers' => 'Create a transfer', - 'no_piggies_title_default' => 'Let\'s create a piggy bank!', - 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', - 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', - 'no_piggies_create_default' => 'Create a new piggy bank', - 'no_bills_title_default' => 'Let\'s create a bill!', - 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', - 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', - 'no_bills_create_default' => 'Create a bill', - - -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php deleted file mode 100644 index 20be49e75a..0000000000 --- a/resources/lang/ru_RU/form.php +++ /dev/null @@ -1,189 +0,0 @@ - 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_asset_source_account' => 'Asset account (source)', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'account_from_id' => 'From account', - 'account_to_id' => 'To account', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tagPosition' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'longitude_latitude' => 'Location', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'exchanged_amount' => 'Exchanged amount', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - - 'revenue_account_source' => 'Revenue account (source)', - 'source_account_asset' => 'Source account (asset account)', - 'destination_account_expense' => 'Destination account (expense account)', - 'destination_account_asset' => 'Destination account (asset account)', - 'source_account_revenue' => 'Source account (revenue account)', - 'type' => 'Type', - 'convert_Withdrawal' => 'Convert withdrawal', - 'convert_Deposit' => 'Convert deposit', - 'convert_Transfer' => 'Convert transfer', - - - 'amount' => 'Amount', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'noPiggybank' => '(no piggy bank)', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - '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_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - '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"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - '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.', - - 'email' => 'Email address', - 'password' => 'Password', - 'password_confirmation' => 'Password (again)', - 'blocked' => 'Is blocked?', - 'blocked_code' => 'Reason for block', - - - // admin - 'domain' => 'Domain', - 'single_user_mode' => 'Single user mode', - 'must_confirm_account' => 'New users must activate account', - 'is_demo_site' => 'Is demo site', - - - // import - 'import_file' => 'Import file', - 'configuration_file' => 'Configuration file', - 'import_file_type' => 'Import file type', - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - 'csv_delimiter' => 'CSV field delimiter', - 'csv_import_account' => 'Default import account', - 'csv_config' => 'CSV import configuration', - - - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'internal_reference' => 'Internal reference', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/help.php b/resources/lang/ru_RU/help.php deleted file mode 100644 index 61210ffe41..0000000000 --- a/resources/lang/ru_RU/help.php +++ /dev/null @@ -1,33 +0,0 @@ - '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 finances.', - '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', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php deleted file mode 100644 index 90625d54e6..0000000000 --- a/resources/lang/ru_RU/list.php +++ /dev/null @@ -1,89 +0,0 @@ - 'Buttons', - 'icon' => 'Icon', - 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference between :start and :end', - 'matchedOn' => 'Matched on', - 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', - 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/pagination.php b/resources/lang/ru_RU/pagination.php deleted file mode 100644 index 4eeab21dee..0000000000 --- a/resources/lang/ru_RU/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - '« Previous', - 'next' => 'Next »', - -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/passwords.php b/resources/lang/ru_RU/passwords.php deleted file mode 100644 index 2e11aa92dc..0000000000 --- a/resources/lang/ru_RU/passwords.php +++ /dev/null @@ -1,19 +0,0 @@ - '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.', -]; \ No newline at end of file diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php deleted file mode 100644 index addd0e6f39..0000000000 --- a/resources/lang/ru_RU/validation.php +++ /dev/null @@ -1,91 +0,0 @@ - 'This is not a valid IBAN.', - 'unique_account_number_for_user' => 'It looks like this account number is already in use.', - 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', - '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.', - 'belongs_to_user' => 'The value of :attribute is unknown', - 'accepted' => 'The :attribute must be accepted.', - 'bic' => 'This is not a valid BIC.', - 'more' => ':attribute must be larger than zero.', - '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.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'file' => 'The :attribute must be a file.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'present' => 'The :attribute field must be present.', - 'amount_zero' => 'The total amount cannot be zero', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/auth.php b/resources/lang/zh_HK/auth.php deleted file mode 100644 index 5d833b3d68..0000000000 --- a/resources/lang/zh_HK/auth.php +++ /dev/null @@ -1,28 +0,0 @@ - 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/breadcrumbs.php b/resources/lang/zh_HK/breadcrumbs.php deleted file mode 100644 index 6bcf9b862d..0000000000 --- a/resources/lang/zh_HK/breadcrumbs.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Home', - 'edit_currency' => 'Edit currency ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'newPiggyBank' => 'Create a new piggy bank', - 'edit_piggyBank' => 'Edit piggy bank ":name"', - 'preferences' => 'Preferences', - 'profile' => 'Profile', - 'changePassword' => 'Change your password', - 'bills' => 'Bills', - 'newBill' => 'New bill', - 'edit_bill' => 'Edit bill ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'reports' => 'Reports', - 'searchResult' => 'Search for ":query"', - 'withdrawal_list' => 'Expenses', - 'deposit_list' => 'Revenue, income and deposits', - 'transfer_list' => 'Transfers', - 'transfers_list' => 'Transfers', - 'create_withdrawal' => 'Create new withdrawal', - 'create_deposit' => 'Create new deposit', - 'create_transfer' => 'Create new transfer', - 'edit_journal' => 'Edit transaction ":description"', - 'delete_journal' => 'Delete transaction ":description"', - 'tags' => 'Tags', - 'createTag' => 'Create new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'delete_tag' => 'Delete tag ":tag"', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/config.php b/resources/lang/zh_HK/config.php deleted file mode 100644 index 827ebea3f0..0000000000 --- a/resources/lang/zh_HK/config.php +++ /dev/null @@ -1,23 +0,0 @@ - 'en, English, en_US, en_US.utf8, en_US.UTF-8', - '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', - -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/csv.php b/resources/lang/zh_HK/csv.php deleted file mode 100644 index 4424610191..0000000000 --- a/resources/lang/zh_HK/csv.php +++ /dev/null @@ -1,80 +0,0 @@ - 'Configure your import', - 'import_configure_intro' => 'There are some options for your CSV import. Please indicate if your CSV file contains headers on the first column, and what the date format of your date-fields is. That might require some experimentation. The field delimiter is usually a ",", but could also be a ";". Check this carefully.', - 'import_configure_form' => 'Basic CSV import options', - 'header_help' => 'Check this if the first row of your CSV file are the column titles', - 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', - - // roles - 'column_roles_title' => 'Define column roles', - 'column_roles_table' => 'Table', - 'column_name' => 'Name of column', - 'column_example' => 'Column example data', - 'column_role' => 'Column data meaning', - 'do_map_value' => 'Map these values', - 'column' => 'Column', - 'no_example_data' => 'No example data available', - 'store_column_roles' => 'Continue import', - 'do_not_map' => '(do not map)', - 'map_title' => 'Connect import data to Firefly III data', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', - 'store_column_mapping' => 'Store mapping', - - // map things. - - - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching Firefly)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching Firefly)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching Firefly)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching Firefly)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching Firefly)', - 'column_currency-name' => 'Currency name (matching Firefly)', - 'column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'column_sepa-db' => 'SEPA Direct Debet', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/demo.php b/resources/lang/zh_HK/demo.php deleted file mode 100644 index e7f8ea934d..0000000000 --- a/resources/lang/zh_HK/demo.php +++ /dev/null @@ -1,24 +0,0 @@ - 'Sorry, there is no extra demo-explanation text for this page.', - 'see_help_icon' => 'However, the -icon in the top right corner may tell you more.', - 'index' => 'Welcome to Firefly III! On this page you get a quick overview of your finances. For more information, check out Accounts → Asset Accounts and of course the Budgets and Reports pages. Or just take a look around and see where you end up.', - 'accounts-index' => 'Asset accounts are your personal bank accounts. Expense accounts are the accounts you spend money at, such as stores and friends. Revenue accounts are accounts you receive money from, such as your job, the government or other sources of income. On this page you can edit or remove them.', - 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', - 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', - 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', - 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', - 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', - 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', - 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', - 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', - 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/firefly.php b/resources/lang/zh_HK/firefly.php deleted file mode 100644 index a8d8567267..0000000000 --- a/resources/lang/zh_HK/firefly.php +++ /dev/null @@ -1,1063 +0,0 @@ - 'incomplete translation', - 'close' => 'Close', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', - 'advanced_search' => 'Advanced search', - 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', - '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.', - 'expired_error' => 'Your account has expired, and can no longer be used.', - '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.', - 'help_may_not_be_your_language' => 'This help text is in English. It is not yet 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', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - '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.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'search' => 'Search', - 'search_found_accounts' => 'Found :count account(s) for your query.', - 'search_found_categories' => 'Found :count category(ies) for your query.', - 'search_found_budgets' => 'Found :count budget(s) for your query.', - 'search_found_tags' => 'Found :count tag(s) for your query.', - 'search_found_transactions' => 'Found :count transaction(s) for your query.', - 'results_limited' => 'The results are limited to :count entries.', - 'tagbalancingAct' => 'Balancing act', - 'tagadvancePayment' => 'Advance payment', - 'tagnothing' => '', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account(s)', - 'destination_accounts' => 'Destination account(s)', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', - 'nothing_to_display' => 'There are no transactions to show you', - 'show_all_no_filter' => 'Show all transactions without grouping them by date.', - 'expenses_by_category' => 'Expenses by category', - 'expenses_by_budget' => 'Expenses by budget', - 'income_by_category' => 'Income by category', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', - 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', - 'sum_of_expenses' => 'Sum of expenses', - 'sum_of_income' => 'Sum of income', - 'total_sum' => 'Total sum', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', - 'cannot_change_demo' => 'You cannot change the password of the demonstration account.', - 'cannot_delete_demo' => 'You cannot remove the demonstration account.', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demonstration account', - 'per_period' => 'Per period', - 'all_periods' => 'All periods', - 'current_period' => 'Current period', - 'show_the_current_period_and_overview' => 'Show the current period and overview', - 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', - 'budget_in_period_breadcrumb' => 'Between :start and :end', - 'clone_withdrawal' => 'Clone this withdrawal', - 'clone_deposit' => 'Clone this deposit', - 'clone_transfer' => 'Clone this transfer', - 'transaction_journal_other_options' => 'Other options', - 'multi_select_no_selection' => 'None selected', - 'multi_select_all_selected' => 'All selected', - 'multi_select_filter_placeholder' => 'Find..', - 'between_dates_breadcrumb' => 'Between :start and :end', - 'all_journals_without_budget' => 'All transactions without a budget', - 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_without_category' => 'All transactions without a category', - 'journals_without_category' => 'Transactions without a category', - 'all_journals_for_account' => 'All transactions for account :name', - 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', - 'transferred' => 'Transferred', - 'all_withdrawal' => 'All expenses', - 'all_transactions' => 'All transactions', - 'title_withdrawal_between' => 'All expenses between :start and :end', - 'all_deposit' => 'All revenue', - 'title_deposit_between' => 'All revenue between :start and :end', - 'all_transfers' => 'All transfers', - 'title_transfers_between' => 'All transfers between :start and :end', - 'all_transfer' => 'All transfers', - 'all_journals_for_tag' => 'All transactions for tag ":tag"', - 'title_transfer_between' => 'All transfers between :start and :end', - 'all_journals_for_category' => 'All transactions for category :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', - 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', - 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', - 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', - 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', - 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', - - // repeat frequencies: - 'repeat_freq_yearly' => 'yearly', - 'repeat_freq_monthly' => 'monthly', - 'weekly' => 'weekly', - 'quarterly' => 'quarterly', - 'half-year' => 'every half year', - 'yearly' => 'yearly', - // account confirmation: - 'confirm_account_header' => 'Please confirm your account', - 'confirm_account_intro' => 'An email has been sent to the address you used during your registration. Please check it out for further instructions. If you did not get this message, you can have Firefly send it again.', - 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.', - 'account_is_confirmed' => 'Your account has been confirmed!', - 'invalid_activation_code' => 'It seems the code you are using is not valid, or has expired.', - 'confirm_account_is_resent_header' => 'The confirmation has been resent', - 'confirm_account_is_resent_text' => 'The confirmation message has been resent. If you still did not receive the confirmation message, please contact the site owner at :owner or check the log files to see what went wrong.', - 'confirm_account_is_resent_go_home' => 'Go to the index page of Firefly', - 'confirm_account_not_resent_header' => 'Something went wrong :(', - 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.', - 'confirm_account_not_resent_go_home' => 'Go to the index page of Firefly', - - // 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_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"', - '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', - 'rule_group_select_transactions' => 'Execute rule group ":title" on 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_category_is' => 'Category is ":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..', - 'rule_trigger_from_account_contains_choice' => 'Source account contains..', - 'rule_trigger_to_account_starts_choice' => 'Destination account starts with..', - 'rule_trigger_to_account_ends_choice' => 'Destination account ends with..', - 'rule_trigger_to_account_is_choice' => 'Destination account is..', - 'rule_trigger_to_account_contains_choice' => 'Destination account contains..', - 'rule_trigger_transaction_type_choice' => 'Transaction is of type..', - 'rule_trigger_amount_less_choice' => 'Amount is less than..', - 'rule_trigger_amount_exactly_choice' => 'Amount is..', - 'rule_trigger_amount_more_choice' => 'Amount is more than..', - 'rule_trigger_description_starts_choice' => 'Description starts with..', - 'rule_trigger_description_ends_choice' => 'Description ends with..', - 'rule_trigger_description_contains_choice' => 'Description contains..', - 'rule_trigger_description_is_choice' => 'Description is..', - 'rule_trigger_category_is_choice' => 'Category is..', - 'rule_trigger_budget_is_choice' => 'Budget is..', - 'rule_trigger_tag_is_choice' => '(A) tag is..', - 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', - 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', - 'rule_trigger_store_journal' => 'When a transaction is created', - 'rule_trigger_update_journal' => 'When a transaction 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_action_set_source_account_choice' => 'Set source account to...', - 'rule_action_set_source_account' => 'Set source account to :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to...', - 'rule_action_set_destination_account' => 'Set destination account to :action_value', - - // tags - '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_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_1Y' => 'One year', - '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', - 'saved_preferences' => 'Preferences saved!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', - 'transaction_page_size_title' => 'Page size', - 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', - 'transaction_page_size_label' => 'Page size', - 'between_dates' => '(:start and :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', - 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', - 'optional_tj_date_fields' => 'Date fields', - 'optional_tj_business_fields' => 'Business fields', - 'optional_tj_attachment_fields' => 'Attachment fields', - 'pref_optional_tj_interest_date' => 'Interest date', - 'pref_optional_tj_book_date' => 'Book date', - 'pref_optional_tj_process_date' => 'Processing date', - 'pref_optional_tj_due_date' => 'Due date', - 'pref_optional_tj_payment_date' => 'Payment date', - 'pref_optional_tj_invoice_date' => 'Invoice date', - 'pref_optional_tj_internal_reference' => 'Internal reference', - 'pref_optional_tj_notes' => 'Notes', - 'pref_optional_tj_attachments' => 'Attachments', - 'optional_field_meta_dates' => 'Dates', - 'optional_field_meta_business' => 'Business', - 'optional_field_attachments' => 'Attachments', - 'optional_field_meta_data' => 'Optional meta data', - - - // 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!', - - - // 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"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - - // tour: - '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', - - // convert stuff: - 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', - 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', - 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', - 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', - 'convert_to_Deposit' => 'Convert ":description" to a deposit', - 'convert_to_Transfer' => 'Convert ":description" to a transfer', - 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', - 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', - 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', - 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', - 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', - 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', - 'transaction_journal_convert_options' => 'Convert this transaction', - 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', - 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', - 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', - 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', - 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', - 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', - 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', - 'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.', - 'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.', - 'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.', - 'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into :sourceName instead of taken from it.', - 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', - 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', - 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', - 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', - 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', - 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', - 'converted_to_Deposit' => 'The transaction has been converted to a deposit', - 'converted_to_Transfer' => 'The transaction has been converted to a transfer', - - - // 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', - - // currencies: - 'create_currency' => 'Create a new currency', - '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 it is still in use.', - '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.', - 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', - 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', - - // forms: - 'mandatoryFields' => 'Mandatory fields', - 'optionalFields' => 'Optional fields', - 'options' => 'Options', - - // budgets: - 'create_new_budget' => 'Create a new budget', - 'store_new_budget' => 'Store new budget', - 'stored_new_budget' => 'Stored new budget ":name"', - 'available_between' => 'Available between :start and :end', - 'transactionsWithoutBudget' => 'Expenses without budget', - 'transactions_no_budget' => 'Expenses without budget between :start and :end', - 'spent_between' => 'Spent between :start and :end', - '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"', - 'deleted_budget' => 'Deleted budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'updated_budget' => 'Updated budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', - 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', - - // bills: - 'matching_on' => 'Matching on', - 'between_amounts' => 'between :low and :high.', - 'repeats' => 'Repeats', - 'connected_journals' => 'Connected transactions', - 'auto_match_on' => 'Automatically matched by Firefly', - 'auto_match_off' => 'Not automatically matched by Firefly', - 'next_expected_match' => 'Next expected match', - 'delete_bill' => 'Delete bill ":name"', - 'deleted_bill' => 'Deleted bill ":name"', - 'edit_bill' => 'Edit bill ":name"', - 'more' => 'More', - 'rescan_old' => 'Rescan old transactions', - 'update_bill' => 'Update bill', - 'updated_bill' => 'Updated bill ":name"', - 'store_new_bill' => 'Store new bill', - 'stored_new_bill' => 'Stored new bill ":name"', - 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', - 'rescanned_bill' => 'Rescanned everything.', - 'average_bill_amount_year' => 'Average bill amount (:year)', - 'average_bill_amount_overall' => 'Average bill amount (overall)', - 'not_or_not_yet' => 'Not (yet)', - 'not_expected_period' => 'Not expected this period', - // 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', - 'cash_accounts' => 'Cash accounts', - 'Cash account' => 'Cash account', - 'account_type' => 'Account type', - 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', - 'stored_new_account' => 'New account ":name" stored!', - 'updated_account' => 'Updated account ":name"', - 'credit_card_options' => 'Credit card options', - 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', - 'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.', - 'select_more_than_one_account' => 'Please select more than one account', - 'select_more_than_one_category' => 'Please select more than one category', - 'select_more_than_one_budget' => 'Please select more than one budget', - 'select_more_than_one_tag' => 'Please select more than one tag', - 'from_to' => 'From :start to :end', - 'from_to_breadcrumb' => 'from :start to :end', - 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', - - // categories: - 'new_category' => 'New category', - 'create_new_category' => 'Create a new category', - 'without_category' => 'Without a category', - 'update_category' => 'Update category', - 'updated_category' => 'Updated category ":name"', - 'categories' => 'Categories', - 'edit_category' => 'Edit category ":name"', - 'no_category' => '(no category)', - 'category' => 'Category', - 'delete_category' => 'Delete category ":name"', - 'deleted_category' => 'Deleted category ":name"', - 'store_category' => 'Store new category', - 'stored_category' => 'Stored new category ":name"', - 'without_category_between' => 'Without category between :start and :end', - - // transactions: - 'update_withdrawal' => 'Update withdrawal', - 'update_deposit' => 'Update deposit', - 'update_transfer' => 'Update transfer', - 'updated_withdrawal' => 'Updated withdrawal ":description"', - 'updated_deposit' => 'Updated deposit ":description"', - 'updated_transfer' => 'Updated transfer ":description"', - 'delete_withdrawal' => 'Delete withdrawal ":description"', - 'delete_deposit' => 'Delete deposit ":description"', - 'delete_transfer' => 'Delete transfer ":description"', - 'deleted_withdrawal' => 'Successfully deleted withdrawal ":description"', - 'deleted_deposit' => 'Successfully deleted deposit ":description"', - 'deleted_transfer' => 'Successfully deleted transfer ":description"', - 'stored_journal' => 'Successfully created new transaction ":description"', - 'select_transactions' => 'Select transactions', - 'stop_selection' => 'Stop selecting transactions', - 'edit_selected' => 'Edit selected', - 'delete_selected' => 'Delete selected', - 'mass_delete_journals' => 'Delete a number of transactions', - 'mass_edit_journals' => 'Edit a number of transactions', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', - - - // new user: - 'welcome' => 'Welcome to Firefly!', - - // 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', - 'divided' => 'divided', - 'toDivide' => 'left to divide', - - // menu and titles, should be recycled as often as possible: - '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', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'account' => 'Account', - 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', - 'Deposit' => 'Deposit', - 'Transfer' => 'Transfer', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'overview' => 'Overview', - 'saveOnAccount' => 'Save on account', - 'unknown' => 'Unknown', - 'daily' => 'Daily', - 'monthly' => 'Monthly', - 'profile' => 'Profile', - 'errors' => 'Errors', - - // reports: - 'report_default' => 'Default financial report between :start and :end', - 'report_audit' => 'Transaction history overview between :start and :end', - 'report_category' => 'Category report between :start and :end', - 'report_budget' => 'Budget report between :start and :end', - 'report_tag' => 'Tag report between :start and :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'quick_link_audit_report' => 'Transaction history overview', - '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', - 'active' => 'Active', - 'difference' => 'Difference', - 'in' => 'In', - 'out' => 'Out', - 'topX' => 'top :number', - 'show_full_list' => 'Show entire list', - 'show_only_top' => 'Show only 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_type_category' => 'Category report', - 'report_type_budget' => 'Budget report', - 'report_type_tag' => 'Tag report', - 'report_type_meta-history' => 'Categories, budgets and bills overview', - 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', - 'report_included_accounts' => 'Included accounts', - 'report_date_range' => 'Date range', - 'report_preset_ranges' => 'Pre-set ranges', - 'shared' => 'Shared', - 'fiscal_year' => 'Fiscal year', - 'income_entry' => 'Income from account ":name" between :start and :end', - 'expense_entry' => 'Expenses to account ":name" between :start and :end', - 'category_entry' => 'Expenses in category ":name" between :start and :end', - 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', - 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', - 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', - 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', - 'reports_extra_options' => 'Extra options', - 'report_has_no_extra_options' => 'This report has no extra options', - 'reports_submit' => 'View report', - 'end_after_start_date' => 'End date of report must be after start date.', - 'select_category' => 'Select category(ies)', - 'select_budget' => 'Select budget(s).', - 'select_tag' => 'Select tag(s).', - 'income_per_category' => 'Income per category', - 'expense_per_category' => 'Expense per category', - 'expense_per_budget' => 'Expense per budget', - 'income_per_account' => 'Income per account', - 'expense_per_account' => 'Expense per account', - 'expense_per_tag' => 'Expense per tag', - 'income_per_tag' => 'Income per tag', - 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', - 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', - 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', - 'include_income_not_in_category' => 'Included income not in the selected category(ies)', - 'include_income_not_in_account' => 'Included income not in the selected account(s)', - 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', - 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', - 'everything_else' => 'Everything else', - 'income_and_expenses' => 'Income and expenses', - 'spent_average' => 'Spent (average)', - 'income_average' => 'Income (average)', - 'transaction_count' => 'Transaction count', - 'average_spending_per_account' => 'Average spending per account', - 'average_income_per_account' => 'Average income per account', - 'total' => 'Total', - 'description' => 'Description', - 'sum_of_period' => 'Sum of period', - 'average_in_period' => 'Average in period', - 'account_role_defaultAsset' => 'Default asset account', - 'account_role_sharedAsset' => 'Shared asset account', - 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', - - // charts: - 'chart' => 'Chart', - 'dayOfMonth' => 'Day of the month', - 'month' => 'Month', - 'budget' => 'Budget', - 'spent' => 'Spent', - 'spent_in_budget' => 'Spent in budget', - 'left_to_spend' => 'Left to spend', - 'earned' => 'Earned', - 'overspent' => 'Overspent', - 'left' => 'Left', - 'no_budget' => '(no budget)', - 'max-amount' => 'Maximum amount', - 'min-amount' => 'Minumum amount', - 'journal-amount' => '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: - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'New piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'stored_piggy_bank' => 'Store new piggy bank ":name"', - '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', - 'suggested_amount' => 'Suggested monthly amount 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"', - 'updated_piggy_bank' => 'Updated 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"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":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"', - 'deleted_tag' => 'Deleted tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'updated_tag' => 'Updated tag ":tag"', - 'created_tag' => 'Tag ":tag" has been created!', - '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.', - - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', - - // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'rule_or_rules' => 'rule(s)', - 'rulegroup_or_groups' => 'rule group(s)', - 'setting_must_confirm_account' => 'Account confirmation', - 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'setting_send_email_notifications' => 'Send email notifications', - 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', - - - // split a transaction: - 'transaction_meta_data' => 'Transaction meta-data', - 'transaction_dates' => 'Transaction dates', - 'splits' => 'Splits', - 'split_title_withdrawal' => 'Split your new withdrawal', - 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', - 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', - 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', - 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', - 'split_title_deposit' => 'Split your new deposit', - 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', - 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', - 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', - 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_deposit' => 'Store splitted deposit', - 'split_title_transfer' => 'Split your new transfer', - 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', - 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', - 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', - 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'store_splitted_transfer' => 'Store splitted transfer', - 'add_another_split' => 'Add another split', - 'split-transactions' => 'Split transactions', - 'split-new-transaction' => 'Split a new transaction', - 'do_split' => 'Do a split', - 'split_this_withdrawal' => 'Split this withdrawal', - 'split_this_deposit' => 'Split this deposit', - 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', - 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', - 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', - - // sandstorm.io errors and messages: - 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', - - // empty lists? no objects? instructions: - 'no_transactions_in_period' => 'There are no transactions in this period.', - 'no_accounts_title_asset' => 'Let\'s create an asset account!', - 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', - 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', - 'no_accounts_create_asset' => 'Create an asset account', - 'no_accounts_title_expense' => 'Let\'s create an expense account!', - 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', - 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_expense' => 'Create an expense account', - 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', - 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', - 'no_accounts_create_revenue' => 'Create a revenue account', - 'no_budgets_title_default' => 'Let\'s create a budget', - 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', - 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', - 'no_budgets_create_default' => 'Create a budget', - 'no_categories_title_default' => 'Let\'s create a category!', - 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', - 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_categories_create_default' => 'Create a category', - 'no_tags_title_default' => 'Let\'s create a tag!', - 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', - 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', - 'no_tags_create_default' => 'Create a tag', - 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', - 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', - 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', - 'no_transactions_create_withdrawal' => 'Create an expense', - 'no_transactions_title_deposit' => 'Let\'s create some income!', - 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', - 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', - 'no_transactions_create_deposit' => 'Create a deposit', - 'no_transactions_title_transfers' => 'Let\'s create a transfer!', - 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', - 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', - 'no_transactions_create_transfers' => 'Create a transfer', - 'no_piggies_title_default' => 'Let\'s create a piggy bank!', - 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', - 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', - 'no_piggies_create_default' => 'Create a new piggy bank', - 'no_bills_title_default' => 'Let\'s create a bill!', - 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', - 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', - 'no_bills_create_default' => 'Create a bill', - - -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/form.php b/resources/lang/zh_HK/form.php deleted file mode 100644 index 20be49e75a..0000000000 --- a/resources/lang/zh_HK/form.php +++ /dev/null @@ -1,189 +0,0 @@ - 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_asset_source_account' => 'Asset account (source)', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'account_from_id' => 'From account', - 'account_to_id' => 'To account', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tagPosition' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'longitude_latitude' => 'Location', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'exchanged_amount' => 'Exchanged amount', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - - 'revenue_account_source' => 'Revenue account (source)', - 'source_account_asset' => 'Source account (asset account)', - 'destination_account_expense' => 'Destination account (expense account)', - 'destination_account_asset' => 'Destination account (asset account)', - 'source_account_revenue' => 'Source account (revenue account)', - 'type' => 'Type', - 'convert_Withdrawal' => 'Convert withdrawal', - 'convert_Deposit' => 'Convert deposit', - 'convert_Transfer' => 'Convert transfer', - - - 'amount' => 'Amount', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'noPiggybank' => '(no piggy bank)', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - '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_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - '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"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - '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.', - - 'email' => 'Email address', - 'password' => 'Password', - 'password_confirmation' => 'Password (again)', - 'blocked' => 'Is blocked?', - 'blocked_code' => 'Reason for block', - - - // admin - 'domain' => 'Domain', - 'single_user_mode' => 'Single user mode', - 'must_confirm_account' => 'New users must activate account', - 'is_demo_site' => 'Is demo site', - - - // import - 'import_file' => 'Import file', - 'configuration_file' => 'Configuration file', - 'import_file_type' => 'Import file type', - 'csv_comma' => 'A comma (,)', - 'csv_semicolon' => 'A semicolon (;)', - 'csv_tab' => 'A tab (invisible)', - 'csv_delimiter' => 'CSV field delimiter', - 'csv_import_account' => 'Default import account', - 'csv_config' => 'CSV import configuration', - - - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'internal_reference' => 'Internal reference', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/help.php b/resources/lang/zh_HK/help.php deleted file mode 100644 index 61210ffe41..0000000000 --- a/resources/lang/zh_HK/help.php +++ /dev/null @@ -1,33 +0,0 @@ - '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 finances.', - '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', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/list.php b/resources/lang/zh_HK/list.php deleted file mode 100644 index 90625d54e6..0000000000 --- a/resources/lang/zh_HK/list.php +++ /dev/null @@ -1,89 +0,0 @@ - 'Buttons', - 'icon' => 'Icon', - 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference between :start and :end', - 'matchedOn' => 'Matched on', - 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', - 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/pagination.php b/resources/lang/zh_HK/pagination.php deleted file mode 100644 index 4eeab21dee..0000000000 --- a/resources/lang/zh_HK/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - '« Previous', - 'next' => 'Next »', - -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/passwords.php b/resources/lang/zh_HK/passwords.php deleted file mode 100644 index 2e11aa92dc..0000000000 --- a/resources/lang/zh_HK/passwords.php +++ /dev/null @@ -1,19 +0,0 @@ - '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.', -]; \ No newline at end of file diff --git a/resources/lang/zh_HK/validation.php b/resources/lang/zh_HK/validation.php deleted file mode 100644 index addd0e6f39..0000000000 --- a/resources/lang/zh_HK/validation.php +++ /dev/null @@ -1,91 +0,0 @@ - 'This is not a valid IBAN.', - 'unique_account_number_for_user' => 'It looks like this account number is already in use.', - 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', - '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.', - 'belongs_to_user' => 'The value of :attribute is unknown', - 'accepted' => 'The :attribute must be accepted.', - 'bic' => 'This is not a valid BIC.', - 'more' => ':attribute must be larger than zero.', - '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.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'file' => 'The :attribute must be a file.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'present' => 'The :attribute field must be present.', - 'amount_zero' => 'The total amount cannot be zero', -]; \ No newline at end of file diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig index 2063b86e79..1747c1a20b 100644 --- a/resources/views/accounts/create.twig +++ b/resources/views/accounts/create.twig @@ -19,7 +19,7 @@ {{ ExpandedForm.text('name') }} {% if what == 'asset' %} {# Not really mandatory but OK #} - {{ ExpandedForm.select('currency_id', currencies) }} + {{ ExpandedForm.select('currency_id', currencySelectList, null, {helpText:'account_default_currency'|_}) }} {% endif %} @@ -39,10 +39,10 @@ {% if what == 'asset' %} - {{ ExpandedForm.balance('openingBalance') }} + {{ ExpandedForm.nonSelectableBalance('openingBalance') }} {{ ExpandedForm.date('openingBalanceDate') }} {{ ExpandedForm.select('accountRole', roles,null,{'helpText' : 'asset_account_role_help'|_}) }} - {{ ExpandedForm.balance('virtualBalance') }} + {{ ExpandedForm.nonSelectableBalance('virtualBalance') }} {% endif %} @@ -70,6 +70,13 @@ {% block scripts %} + {# JS currency list for update thing #} + {% endblock %} diff --git a/resources/views/accounts/delete.twig b/resources/views/accounts/delete.twig index 7203d40a25..919ee63a76 100644 --- a/resources/views/accounts/delete.twig +++ b/resources/views/accounts/delete.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute().getName(), account) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account) }} {% endblock %} {% block content %} diff --git a/resources/views/accounts/edit.twig b/resources/views/accounts/edit.twig index e01e7f98ee..5123ee9751 100644 --- a/resources/views/accounts/edit.twig +++ b/resources/views/accounts/edit.twig @@ -19,7 +19,7 @@ {{ ExpandedForm.text('name') }} {% if account.accounttype.type == 'Default account' or account.accounttype.type == 'Asset account' %} {# Not really mandatory but OK #} - {{ ExpandedForm.select('currency_id', currencies) }} + {{ ExpandedForm.select('currency_id', currencySelectList) }} {% endif %} @@ -36,10 +36,12 @@ {{ ExpandedForm.text('accountNumber') }} {% if account.accounttype.type == 'Default account' or account.accounttype.type == 'Asset account' %} - {{ ExpandedForm.balance('openingBalance',null, {'currency' : openingBalance ? openingBalance.transactionCurrency : null}) }} + + {# get opening balance entry for this thing! #} + {{ ExpandedForm.nonSelectableBalance('openingBalance',null, {'currency' : currency }) }} {{ ExpandedForm.date('openingBalanceDate') }} {{ ExpandedForm.select('accountRole', roles) }} - {{ ExpandedForm.balance('virtualBalance',null) }} + {{ ExpandedForm.nonSelectableBalance('virtualBalance',null, {'currency' : currency }) }} {% endif %} {{ ExpandedForm.checkbox('active','1') }} @@ -80,6 +82,15 @@ {% block scripts %} + + {# JS currency list for update thing #} + + {% endblock %} diff --git a/resources/views/accounts/index.twig b/resources/views/accounts/index.twig index 84f653aacb..e40e3984a3 100644 --- a/resources/views/accounts/index.twig +++ b/resources/views/accounts/index.twig @@ -5,32 +5,38 @@ {% endblock %} {% block content %} + {% if accounts.count > 0 %} +
+
+
+
+

{{ subTitle }}

-
-
-
-
-

{{ subTitle }}

- - -
- +
+ {% include 'list.accounts' %} +
-
- {% include 'list.accounts' %} -
-
+ {% else %} + {% include 'partials.empty' with {what: what, type: 'accounts',route: route('accounts.create', [what])} %} + {% endif %} {% endblock %} {% block styles %} diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index 4d9d3444a8..bedb9ee721 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account, start, end) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account, moment, start, end) }} {% endblock %} {% block content %} @@ -9,9 +9,13 @@
-

{{ account.name }} - ({{ trans('firefly.from_to', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }})

- +

+ {% if moment == 'all' %} + {{ trans('firefly.chart_all_journals_for_account', {name:account.name}) }} + {% else %} + {{ trans('firefly.chart_account_in_period', {name: account.name, start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }} + {% endif %} +

@@ -24,7 +28,7 @@
- +
@@ -36,8 +40,8 @@

{{ 'expenses_by_category'|_ }}

-
- +
+
@@ -48,8 +52,8 @@

{{ 'expenses_by_budget'|_ }}

-
- +
+
@@ -60,33 +64,33 @@

{{ 'income_by_category'|_ }}

-
- +
+
-{% if entries %} -
-
-

{{ 'showEverything'|_ }}

+ {% if periods.count > 0 %} + -
-{% endif %} + {% endif %}
-
+

{{ 'transactions'|_ }}

{% include 'list.journals-tasker' with {sorting:true, hideBills:true, hideBudgets: true, hideCategories: true} %} - {% if entries %} + {% if periods.count > 0 %}

- + {{ 'show_all_no_filter'|_ }}

@@ -101,28 +105,27 @@
- {% if entries %} + {% if periods.count > 0 %}
- - {% for entry in entries %} - {% if (entry[2] != 0 or entry[3] != 0) or (accountType == 'Asset account') %} -
+ {% for period in periods %} + {% if (period.spent != 0 or period.earned != 0) %} +
- {% if entry[2] != 0 or (accountType == 'Asset account') %} + {% if period.spent != 0 %} - + {% endif %} - {% if entry[3] != 0 or (accountType == 'Asset account') %} + {% if period.earned != 0 %} - + {% endif %}
{{ 'spent'|_ }}{{ entry[2]|formatAmount }}{{ period.spent|formatAmount }}
{{ 'earned'|_ }}{{ entry[3]|formatAmount }}{{ period.earned|formatAmount }}
@@ -130,7 +133,7 @@
{% endif %} {% endfor %} -

{{ 'showEverything'|_ }}

+

{{ 'showEverything'|_ }}

{% endif %}
@@ -141,18 +144,31 @@ {% block scripts %} + + + diff --git a/resources/views/auth/confirmation/error.twig b/resources/views/auth/confirmation/error.twig deleted file mode 100644 index a417e4db7b..0000000000 --- a/resources/views/auth/confirmation/error.twig +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Firefly III - - - - - - - - - {# favicons #} - {% include('partials.favicons') %} - - - -
- - -
-
-

{{ 'confirm_account_header'|_ }}

-
-
- -
-
-

- {{ 'confirm_account_intro'|_ }} -

-

- {{ 'confirm_account_resend_email'|_ }} -

-
-
-
- - diff --git a/resources/views/auth/confirmation/no-resent.twig b/resources/views/auth/confirmation/no-resent.twig deleted file mode 100644 index 64c24e95e0..0000000000 --- a/resources/views/auth/confirmation/no-resent.twig +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Firefly III - - - - - - - - - {# favicons #} - {% include('partials.favicons') %} - - - -
- - -
-
-

{{ 'confirm_account_not_resent_header'|_ }}

-
-
- -
-
-

- {{ trans('firefly.confirm_account_not_resent_intro', {owner:owner})|raw }} -

-

- {{ 'confirm_account_not_resent_go_home'|_ }} -

-
-
-
- - diff --git a/resources/views/auth/confirmation/resent.twig b/resources/views/auth/confirmation/resent.twig deleted file mode 100644 index accae395df..0000000000 --- a/resources/views/auth/confirmation/resent.twig +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Firefly III - - - - - - - - - {# favicons #} - {% include('partials.favicons') %} - - - -
- - -
-
-

{{ 'confirm_account_is_resent_header'|_ }}

-
-
- -
-
-

- {{ trans('firefly.confirm_account_is_resent_text', {owner:owner})|raw }} -

-

- {{ 'confirm_account_is_resent_go_home'|_ }} -

-
-
-
- - diff --git a/resources/views/bills/index.twig b/resources/views/bills/index.twig index 28bc6261fe..b56f32da2b 100644 --- a/resources/views/bills/index.twig +++ b/resources/views/bills/index.twig @@ -5,33 +5,30 @@ {% endblock %} {% block content %} -
-
-
-
-

{{ title }}

+ {% if bills.count == 0 %} + {% include 'partials.empty' with {what: 'default', type: 'bills',route: route('bills.create')} %} + {% else %} +
+
+
+
+

{{ title }}

- -
-
- - + +
+
+ + +
-
-
- {% include 'list/bills' %} +
+ {% include 'list/bills' %} +
-
-{% endblock %} -{% block styles %} - -{% endblock %} - -{% block scripts %} - -{% endblock %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index 9227d99d46..2b01b46c29 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -93,7 +93,7 @@

{{ 'chart'|_ }}

- +
diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index f1aa8a889a..9ecec09211 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -71,18 +71,22 @@

- -
-
-

{{ 'createBudget'|_ }}

+ {% if budgets.count > 0 and inactive.count > 0 %} +
+
+

{{ 'createBudget'|_ }}

+
+
- -
+ {% endif %}
+ {% if budgets.count == 0 and inactive.count == 0 %} + {% include 'partials.empty' with {what: 'default', type: 'budgets',route: route('budgets.create')} %} + {% endif %}
{% for budget in budgets %}
diff --git a/resources/views/budgets/no-budget.twig b/resources/views/budgets/no-budget.twig index e8e1f0809b..fd23bde560 100644 --- a/resources/views/budgets/no-budget.twig +++ b/resources/views/budgets/no-budget.twig @@ -1,23 +1,81 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, subTitle) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, moment, start, end) }} {% endblock %} {% block content %} + + {# upper show-all instruction #} + {% if periods.count > 0 %} + + {% endif %} +
-
+

{{ subTitle }}

-
- {% include 'list.journals-tasker' with {'journals': journals} %} +
+ {% include 'list.journals-tasker' with {'journals': journals,'hideBudgets': true} %} + {% if periods.count > 0 %} +

+ + {{ 'show_all_no_filter'|_ }} +

+ {% else %} +

+ + {{ 'show_the_current_period_and_overview'|_ }} +

+ {% endif %}
+ + {% if periods.count > 0 %} +
+ {% for period in periods %} + {% if period.count > 0 %} +
+ +
+ + + + + + + + + +
{{ 'transactions'|_ }}{{ period.count }}
{{ 'spent'|_ }}{{ period.sum|formatAmount }}
+
+
+ {% endif %} + {% endfor %} +
+ {% endif %} +
+ {# lower show-all instruction #} + {% if periods.count > 0 %} + + {% endif %} + {% endblock %} {% block scripts %} diff --git a/resources/views/budgets/show.twig b/resources/views/budgets/show.twig index e1750adff8..3e598b1d6e 100644 --- a/resources/views/budgets/show.twig +++ b/resources/views/budgets/show.twig @@ -9,7 +9,13 @@
-

{{ 'overview'|_ }}

+

+ {% if budgetLimit %} + {{ trans('firefly.chart_budget_in_period', {name: budget.name, start: budgetLimit.start_date.formatLocalized(monthAndDayFormat), end: budgetLimit.end_date.formatLocalized(monthAndDayFormat) }) }} + {% else %} + {{ trans('firefly.chart_all_journals_for_budget', {name:budget.name}) }} + {% endif %} +

@@ -23,7 +29,46 @@
- + +
+
+
+
+ +
+
+
+
+

{{ 'expenses_by_category'|_ }}

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

{{ 'expenses_by_asset_account'|_ }}

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

{{ 'expenses_by_expense_account'|_ }}

+
+
+
+ +
@@ -43,7 +88,15 @@

{{ 'transactions'|_ }}

- {% include 'list.journals-tasker' %} + {% include 'list.journals-tasker' with {hideBudgets:true, hideBills:true} %} + {% if budgetLimit %} +

+ + + {{ 'show_all_no_filter'|_ }} + +

+ {% endif %}
@@ -101,13 +154,21 @@ {% block scripts %} diff --git a/resources/views/categories/index.twig b/resources/views/categories/index.twig index d4cfc7c335..8316a444b9 100644 --- a/resources/views/categories/index.twig +++ b/resources/views/categories/index.twig @@ -5,38 +5,37 @@ {% endblock %} {% block content %} -
-
-
-
-

{{ 'categories'|_ }}

+ {% if categories.count > 0 %} +
+
+
+
+

{{ 'categories'|_ }}

- -
- -
-
- {% include 'list/categories' %} +
+
+ {% include 'list/categories' %} +
-
+ {% else %} + {% include 'partials.empty' with {what: 'default', type: 'categories',route: route('categories.create')} %} + {% endif %} {% endblock %} {% block styles %} - {% endblock %} {% block scripts %} - - - - {% endblock %} diff --git a/resources/views/categories/no-category.twig b/resources/views/categories/no-category.twig index c302ca0203..c6ebc131f4 100644 --- a/resources/views/categories/no-category.twig +++ b/resources/views/categories/no-category.twig @@ -1,23 +1,94 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, subTitle) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, moment, start, end) }} {% endblock %} {% block content %} + + {# upper show-all instruction #} + {% if periods.count > 0 %} + + {% endif %} +
-
+
- {{ subTitle }} +

{{ subTitle }}

-
- {% include 'list.journals-tasker' %} +
+ {% include 'list.journals-tasker' with {'journals': journals, 'hideCategories':true} %} + {% if periods.count > 0 %} +

+ + {{ 'show_all_no_filter'|_ }} +

+ {% else %} +

+ + {{ 'show_the_current_period_and_overview'|_ }} +

+ {% endif %}
+ + {% if periods.count > 0 %} +
+ {% for period in periods %} + {% if period.count > 0 %} +
+ +
+ + + + + + {% if period.spent != 0 %} + + + + + {% endif %} + {% if period.earned != 0 %} + + + + + {% endif %} + {% if period.transferred != 0 %} + + + + + {% endif %} +
{{ 'transactions'|_ }}{{ period.count }}
{{ 'spent'|_ }}{{ period.spent|formatAmount }}
{{ 'earned'|_ }}{{ period.earned|formatAmount }}
{{ 'transferred'|_ }}{{ period.transferred|formatAmountPlain }}
+
+
+ {% endif %} + {% endfor %} +
+ {% endif %} +
+ {# lower show-all instruction #} + {% if periods.count > 0 %} + + {% endif %} {% endblock %} {% block scripts %} diff --git a/resources/views/categories/show.twig b/resources/views/categories/show.twig index c798a6b587..186ad996de 100644 --- a/resources/views/categories/show.twig +++ b/resources/views/categories/show.twig @@ -1,71 +1,64 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, category, start, end) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, category, moment, start, end) }} {% endblock %} {% block content %}
- {% if method == 'default' %} + {% if moment != 'all' %} {# both charts #}
-

{{ 'overview'|_ }} ({{ 'per_period'|_|lower }})

+

+ {{ trans('firefly.chart_category_in_period', {name: category.name, start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }} +

- +
-

{{ 'overview'|_ }} ({{ 'all_periods'|_|lower }})

+

+ {{ trans('firefly.chart_category_all', {name: category.name }) }} +

- +
{% endif %} - {% if method == 'date' %} - {# single chart #} -
-
-
-

{{ 'overview'|_ }} ({{ 'current_period'|_|lower }})

-
-
- -
-
-
- {% endif %} - {% if method == 'all' %} + {% if moment == 'all' %} {# all chart #}
-

{{ 'overview'|_ }} ({{ 'all_periods'|_|lower }})

+

+ {{ trans('firefly.chart_category_all', {name: category.name }) }} +

- +
{% endif %}
- {% if entries %} + {% if periods.count > 0 %} {% endif %}
-
+
@@ -73,10 +66,10 @@
{% include 'list.journals-tasker' with {hideCategories: true} %} - {% if entries %} + {% if periods.count > 0 %}

- + {{ 'show_all_no_filter'|_ }}

@@ -91,34 +84,45 @@
- {% if entries %} + {% if periods.count > 0 %}
- {% for entry in entries %} - {% if entry[2] != 0 or entry[3] != 0 %} -
+ {% for period in periods %} + {% if period.spent != 0 or period.earned != 0 or period.sum != 0 %} +
- {% if entry[2] != 0 %} + {% if period.spent != 0 %} - + {% endif %} - {% if entry[3] != 0 %} + {% if period.earned != 0 %} - + + + {% endif %} + {% if period.earned != 0 and period.spent != 0 %} + + + + + {% endif %} + {% if period.transferred != 0 %} + + + {% endif %}
{{ 'spent'|_ }}{{ entry[2]|formatAmount }}{{ period.spent|formatAmount }}
{{ 'earned'|_ }}{{ entry[3]|formatAmount }}{{ period.earned|formatAmount }}
{{ 'sum'|_ }}{{ period.sum|formatAmount }}
{{ 'transferred'|_ }}{{ period.transferred|formatAmountPlain }}
{% endif %} - {% endfor %}
{% endif %} @@ -128,7 +132,7 @@ {% block scripts %} diff --git a/resources/views/csv/column-roles.twig b/resources/views/csv/column-roles.twig deleted file mode 100644 index 92b2590df7..0000000000 --- a/resources/views/csv/column-roles.twig +++ /dev/null @@ -1,77 +0,0 @@ -{% extends "./layout/default" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_column_roles_title'|_ }}

-
-
-

{{ 'csv_column_roles_text'|_ }}

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

{{ 'csv_column_roles_table'|_ }}

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

{{ 'csv_download_config_title'|_ }}

-
-
-

- {{ 'csv_download_config_text'|_ }} -

- -

- {{ 'csv_do_download_config'|_ }} -

- -

- {{ 'csv_more_information_text'|_ }} -

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

{{ 'csv_index_title'|_ }}

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

{{ 'csv_index_beta_warning'|_ }}

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

{{ 'csv_index_unsupported_warning'|_ }}

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

{{ 'csv_upload_form'|_ }}

-
-
- - - {{ ExpandedForm.checkbox('has_headers',1,null,{helpText: 'csv_header_help'|_}) }} - {{ ExpandedForm.text('date_format','Ymd',{helpText: trans('firefly.csv_date_help', {dateExample: phpdate('Ymd')}) }) }} - - {{ ExpandedForm.file('csv',{helpText: 'csv_csv_file_help'|_}) }} - - {{ ExpandedForm.select('csv_delimiter', delimiters, 0, {helpText: 'csv_delimiter_help'|_} ) }} - - {{ ExpandedForm.file('csv_config',{helpText: 'csv_csv_config_file_help'|_}) }} - - {{ ExpandedForm.select('csv_import_account', accounts, 0, {helpText: 'csv_import_account_help'|_} ) }} - - {{ ExpandedForm.multiCheckbox('specifix', specifix) }} - - - - {% if not uploadPossible %} -
-
-   -
- -
-
{{ path }}
-

- {{ 'csv_upload_not_writeable'|_ }} -

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

{{ 'csv_map_title'|_ }}

-
-
-

- {{ 'csv_map_text'|_ }} -

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

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

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

{{ 'csv_process_title'|_ }}

-
-
-

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

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

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

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

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

- {% endif %} - -

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

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

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

-
-
- -
-
-{% endblock %} diff --git a/resources/views/demo/import/configure.twig b/resources/views/demo/import/configure.twig index a94b4914bb..eee25e4315 100644 --- a/resources/views/demo/import/configure.twig +++ b/resources/views/demo/import/configure.twig @@ -1,3 +1,3 @@ {{ trans('demo.import-configure-security') }} -

+

{{ trans('demo.import-configure-configuration') }} diff --git a/resources/views/demo/reports/index.twig b/resources/views/demo/reports/index.twig index 535fb63c44..adf49baea5 100644 --- a/resources/views/demo/reports/index.twig +++ b/resources/views/demo/reports/index.twig @@ -1,6 +1,6 @@ {{ trans('demo.reports-index-start')|raw }} -
-
+
+
{{ trans('demo.reports-index-examples', { one: route('reports.report.default', ['1,2,3','currentMonthStart','currentMonthEnd']), two: route('reports.report.default', ['1,2,3','20160101','20161231']), diff --git a/resources/views/emails/error-html.twig b/resources/views/emails/error-html.twig index 5594a2a7eb..1fe56d6b7e 100644 --- a/resources/views/emails/error-html.twig +++ b/resources/views/emails/error-html.twig @@ -1,6 +1,6 @@ {% include 'emails.header-html' %}

- Firefly III ran into an error: {{ errorMessage }} + Firefly III v{{ version }} ran into an error: {{ errorMessage }}

diff --git a/resources/views/emails/error-text.twig b/resources/views/emails/error-text.twig index e195cdb631..56d4ea7a39 100644 --- a/resources/views/emails/error-text.twig +++ b/resources/views/emails/error-text.twig @@ -1,5 +1,5 @@ {% include 'emails.header-text' %} -Firefly III ran into an error: {{ errorMessage }}. +Firefly III v{{ version }} ran into an error: {{ errorMessage }}. The error was of type "{{ class }}". diff --git a/resources/views/emails/password-html.twig b/resources/views/emails/password-html.twig index 07e4e0a80a..2b15f16191 100644 --- a/resources/views/emails/password-html.twig +++ b/resources/views/emails/password-html.twig @@ -8,6 +8,6 @@

- {{ url }} + {{ url }}

{% include 'emails.footer-html' %} diff --git a/resources/views/errors/500.twig b/resources/views/errors/500.twig index 456f0b73dd..5c90946598 100644 --- a/resources/views/errors/500.twig +++ b/resources/views/errors/500.twig @@ -1,6 +1,6 @@ - +