Merge branch 'release/4.3.7'

This commit is contained in:
James Cole 2017-03-06 21:07:40 +01:00
commit 436a270524
186 changed files with 6787 additions and 2729 deletions

View File

@ -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.

7
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -3,6 +3,35 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [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).

View File

@ -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)
@ -34,3 +34,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)

View File

@ -84,7 +84,7 @@ class CreateImport extends Command
$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;

View File

@ -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.');

View File

@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Collector;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\Transaction;
use Illuminate\Database\Query\JoinClause;

View File

@ -13,7 +13,6 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use Crypt;
use Steam;
/**

View File

@ -141,52 +141,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,34 +166,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
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
*

View File

@ -151,52 +151,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.');
@ -221,7 +179,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;
@ -240,85 +198,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
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
*

View File

@ -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
{

View File

@ -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
{

View File

@ -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);

View File

@ -80,4 +80,124 @@ class Support
return $result;
}
/**
* @return Collection
*/
public function getTopExpenses(): Collection
{
$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 int $sortFlag
*
* @return array
*/
protected 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;
}
/**
* @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;
}
}

View File

@ -0,0 +1,219 @@
<?php
/**
* MonthReportGenerator.php
* 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.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Tag;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
use Log;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class MonthReportGenerator extends Support implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Collection */
private $expenses;
/** @var Collection */
private $income;
/** @var Carbon */
private $start;
/** @var Collection */
private $tags;
/**
* MonthReportGenerator constructor.
*/
public function __construct()
{
$this->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()->disableFilter();
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$transactions = self::filterExpenses($transactions, $accountIds);
$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();
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$transactions = self::filterIncome($transactions, $accountIds);
$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;
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* MultiYearReportGenerator.php
* 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.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Tag;
/**
* Class MultiYearReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class MultiYearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@ -0,0 +1,26 @@
<?php
/**
* YearReportGenerator.php
* 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.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Tag;
/**
* Class YearReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class YearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@ -57,9 +57,15 @@ class StoredJournalEventHandler
/*
* 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);
$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);
if (bccomp($amount, '0') === 0) {
Log::debug('Amount is zero, will not create event.');
return true;
}
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
$repetition->save();
@ -134,8 +140,8 @@ class StoredJournalEventHandler
*/
private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string
{
$amount = TransactionJournal::amountPositive($journal);
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
$amount = $journal->amountPositive();
$sources = $journal->sourceAccountList()->pluck('id')->toArray();
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
$compare = bcmul($repetition->currentamount, '-1');

View File

@ -44,6 +44,8 @@ interface AttachmentHelperInterface
/**
* @param Model $model
*
* @param null|array $files
*
* @return bool
*/
public function saveAttachmentsForModel(Model $model, array $files = null): bool;

View File

@ -14,11 +14,13 @@ namespace FireflyIII\Helpers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
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 +48,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 +72,7 @@ class MetaPieChart implements MetaPieChartInterface
$this->accounts = new Collection;
$this->budgets = new Collection;
$this->categories = new Collection;
$this->tags = new Collection;
}
/**
@ -87,7 +91,6 @@ class MetaPieChart implements MetaPieChartInterface
if ($this->collectOtherObjects && $direction === 'expense') {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
@ -182,6 +185,18 @@ class MetaPieChart implements MetaPieChartInterface
return $this;
}
/**
* @param Collection $tags
*
* @return MetaPieChartInterface
*/
public function setTags(Collection $tags): MetaPieChartInterface
{
$this->tags = $tags;
return $this;
}
/**
* @param User $user
*
@ -219,6 +234,11 @@ 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();
@ -233,8 +253,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) {
@ -264,7 +289,7 @@ class MetaPieChart implements MetaPieChartInterface
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 +299,22 @@ class MetaPieChart implements MetaPieChartInterface
return $chartData;
}
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;
}
}

View File

@ -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
*

View File

@ -430,6 +430,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
*

View File

@ -141,6 +141,13 @@ interface JournalCollectorInterface
*/
public function setTag(Tag $tag): JournalCollectorInterface;
/**
* @param Collection $tags
*
* @return JournalCollectorInterface
*/
public function setTags(Collection $tags): JournalCollectorInterface;
/**
* @param array $types
*

View File

@ -227,101 +227,80 @@ class AccountController extends Controller
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
}
/**
* @param Request $request
* @param JournalCollectorInterface $collector
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function show(Request $request, JournalCollectorInterface $collector, Account $account)
{
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;
// grab those journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id);
// generate entries for each period (and cache those)
$entries = $this->periodEntries($account);
return view('accounts.show', compact('account', 'accountType', 'entries', '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
* @param string $moment
*/
public function showByDate(Request $request, Account $account, string $date)
public function show(Request $request, Account $account, string $moment = '')
{
$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')]);
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
return $this->redirectToOriginalAccount($account);
}
$subTitle = $account->name;
$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;
// prep for "all" view.
if ($moment === 'all') {
$subTitle = $account->name . ' (' . strtolower(strval(trans('firefly.everything'))) . ')';
$chartUri = route('chart.account.all', [$account->id]);
}
// prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$subTitle = $account->name . ' (' . strval(
trans(
'firefly.from_to_breadcrumb',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
)
) . ')';
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
$periods = $this->periodEntries($account);
}
// 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->periodEntries($account);
}
$accountType = $account->accountType->type;
$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);
$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')));
}
}
// 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'));
return view('accounts.show', compact('account', 'accountType', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
}
/**
@ -430,9 +409,7 @@ 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:

View File

@ -122,12 +122,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;
}
/**

View File

@ -79,9 +79,11 @@ 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, CookieJar $cookieJar)
{

View File

@ -105,7 +105,8 @@ class BudgetController extends Controller
}
/**
* @param Budget $budget
* @param Request $request
* @param Budget $budget
*
* @return View
*/
@ -214,6 +215,8 @@ class BudgetController extends Controller
}
/**
* @param BudgetIncomeRequest $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function postUpdateIncome(BudgetIncomeRequest $request)
@ -420,7 +423,7 @@ class BudgetController extends Controller
$cache->addProperty('get-limits');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
/** @var AccountRepositoryInterface $accountRepository */

View File

@ -339,7 +339,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);

View File

@ -67,7 +67,7 @@ class AccountController extends Controller
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.');
@ -112,7 +112,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 +139,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 +153,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 +182,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 +210,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 +239,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 +279,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 +293,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,6 +322,20 @@ 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
@ -320,7 +359,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 +414,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 +460,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');
@ -463,7 +502,7 @@ class AccountController extends Controller
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.');

View File

@ -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();

View File

@ -80,7 +80,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 +133,7 @@ class BudgetController extends Controller
$cache->addProperty($budgetLimit->id);
if ($cache->has()) {
return Response::json($cache->get());
return Response::json($cache->get()); // @codeCoverageIgnore
}
$entries = [];
@ -170,7 +170,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 +227,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 +268,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:
@ -386,9 +386,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,

View File

@ -131,7 +131,7 @@ 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);
@ -203,6 +203,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
*
@ -268,21 +269,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;
}
}

View File

@ -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);
@ -106,12 +106,12 @@ class CategoryController extends Controller
}
/**
* @param CRI $repository
* @param Category $category
* @param CategoryRepositoryInterface $repository
* @param Category $category
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function currentPeriod(CRI $repository, Category $category)
public function currentPeriod(CategoryRepositoryInterface $repository, Category $category)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
@ -121,12 +121,12 @@ class CategoryController extends Controller
}
/**
* @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 +136,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 +161,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 +178,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);
@ -210,14 +210,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 +225,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);
@ -257,14 +257,14 @@ 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, $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
@ -277,14 +277,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 +297,7 @@ class CategoryController extends Controller
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
// chart data

View File

@ -179,7 +179,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);
@ -331,22 +331,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;
}
}

View File

@ -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);

View File

@ -64,7 +64,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,7 +103,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
}
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$source = $this->getChartData($accounts, $start, $end);
@ -161,7 +161,7 @@ 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 = [
@ -246,7 +246,7 @@ class ReportController extends Controller
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}

View File

@ -0,0 +1,359 @@
<?php
/**
* TagReportController.php
* 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.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Generator\Report\Tag\MonthReportGenerator;
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Response;
class TagReportController extends Controller
{
/** @var GeneratorInterface */
protected $generator;
/**
*
*/
public function __construct()
{
parent::__construct();
// create chart generator:
$this->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;
}
$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()->disableFilter();
$accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
return $set;
}
/**
* @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();
$accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
return $set;
}
/**
* @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;
}
}

View File

@ -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;
}
/**

View File

@ -50,6 +50,8 @@ class CurrencyController extends Controller
}
/**
* @param Request $request
*
* @return View
*/
public function create(Request $request)
@ -90,6 +92,7 @@ class CurrencyController extends Controller
/**
* @param Request $request
* @param CurrencyRepositoryInterface $repository
* @param TransactionCurrency $currency
*
@ -115,6 +118,7 @@ class CurrencyController extends Controller
}
/**
* @param Request $request
* @param CurrencyRepositoryInterface $repository
* @param TransactionCurrency $currency
*

View File

@ -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 = [

View File

@ -17,7 +17,7 @@ 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;
@ -97,11 +97,11 @@ class HomeController extends Controller
}
/**
* @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);

View File

@ -13,6 +13,10 @@ namespace FireflyIII\Http\Controllers;
use Amount;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\Request;
use Navigation;
use Preferences;
@ -29,6 +33,35 @@ class JavascriptController extends Controller
/**
*
*/
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];
$data['accounts'][$accountId] = $entry;
}
return response()
->view('javascript.accounts', $data, 200)
->header('Content-Type', 'text/javascript');
}
/**
* @param Request $request
*
* @return \Illuminate\Http\Response
*/
public function variables(Request $request)
{
$picker = $this->getDateRangePicker();

View File

@ -155,7 +155,7 @@ 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]);
@ -183,7 +183,7 @@ 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]);
@ -329,7 +329,9 @@ class JsonController extends Controller
}
/**
* @param JournalRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function transactionTypes(JournalRepositoryInterface $repository)
{

View File

@ -290,7 +290,7 @@ class PiggyBankController extends Controller
$repository->createEvent($piggyBank, $amount);
Session::flash(
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name]))
);
Preferences::mark();

View File

@ -239,7 +239,7 @@ class ReportController extends Controller
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
// get the destinations:
$sources = TransactionJournal::sourceAccountList($transaction->transactionJournal)->pluck('id')->toArray();
$sources = $transaction->transactionJournal->sourceAccountList()->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $sources));
@ -275,7 +275,7 @@ class ReportController extends Controller
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
// get the destinations:
$destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
$destinations = $transaction->destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));

View File

@ -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 */

View File

@ -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);

View File

@ -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.

View File

@ -47,7 +47,7 @@ class CategoryController extends Controller
if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
@ -81,7 +81,7 @@ class CategoryController extends Controller
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 +114,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 */

View File

@ -46,7 +46,7 @@ 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);
$type = 'expense-entry';
@ -73,7 +73,7 @@ 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);
$type = 'income-entry';
@ -101,7 +101,7 @@ 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);

View File

@ -530,6 +530,7 @@ class RuleController extends Controller
];
if (is_array($data['rule-triggers'])) {
foreach ($data['rule-triggers'] as $index => $triggerType) {
$data['rule-trigger-stop'][$index] = $data['rule-trigger-stop'][$index] ?? 0;
$triggers[] = [
'type' => $triggerType,
'value' => $data['rule-trigger-values'][$index],

View File

@ -56,7 +56,7 @@ class SearchController extends Controller
$limit = 20;
// ui stuff:
$subTitle = '';
$subTitle = '';
// query stuff
$query = null;

View File

@ -159,8 +159,8 @@ class TagController extends Controller
/*
* Can this tag become another type?
*/
$allowAdvance = Tag::tagAllowAdvance($tag);
$allowToBalancingAct = Tag::tagAllowBalancing($tag);
$allowAdvance = $tag->tagAllowAdvance();
$allowToBalancingAct = $tag->tagAllowBalancing();
// edit tag options:
if ($allowAdvance === false) {
@ -183,25 +183,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,7 +218,7 @@ class TagController extends Controller
}
}
return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection'));
return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection', 'count'));
}
/**
@ -226,24 +228,71 @@ class TagController extends Controller
*
* @return View
*/
public function show(Request $request, JournalCollectorInterface $collector, Tag $tag, string $moment = '')
public function show(Request $request, JournalCollectorInterface $collector, Tag $tag)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = clone session('end', Carbon::now()->endOfMonth());
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$periods = $this->getPeriodOverview($tag);
// use collector:
$collector->setAllAssetAccounts()
->setLimit($pageSize)->setPage($page)->setTag($tag)->withOpposingAccount()->disableInternalFilter()
->withBudgetInformation()->withCategoryInformation()->setRange($start, $end);
$journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id);
$sum = $journals->sum(
function (Transaction $transaction) {
return $transaction->transaction_amount;
}
);
return view('tags.show', compact('tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end'));
}
/**
* @param Request $request
* @param JournalCollectorInterface $collector
* @param Tag $tag
*
* @return View
*/
public function showAll(Request $request, JournalCollectorInterface $collector, Tag $tag)
{
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTag($tag)
->withOpposingAccount()->disableInternalFilter()
->withBudgetInformation()->withCategoryInformation();
$journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id . '/all');
$sum = $journals->sum(
function (Transaction $transaction) {
return $transaction->transaction_amount;
}
);
return view('tags.show', compact('tag', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end'));
}
public function showByDate(Request $request, JournalCollectorInterface $collector, Tag $tag, string $date)
{
$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());
try {
$start = new Carbon($date);
$end = Navigation::endOfPeriod($start, $range);
} catch (Exception $e) {
$start = Navigation::startOfPeriod($this->repository->firstUseDate($tag), $range);
$end = Navigation::startOfPeriod($this->repository->lastUseDate($tag), $range);
}
$subTitle = $tag->tag;
@ -254,7 +303,7 @@ class TagController extends Controller
// use collector:
$collector->setAllAssetAccounts()
->setLimit($pageSize)->setPage($page)->setTag($tag)
->setLimit($pageSize)->setPage($page)->setTag($tag)->withOpposingAccount()->disableInternalFilter()
->withBudgetInformation()->withCategoryInformation()->setRange($start, $end);
$journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id);
@ -336,7 +385,7 @@ class TagController extends Controller
$cache->addProperty($tag->id);
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$collection = new Collection;

View File

@ -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,14 @@ 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;
@ -210,13 +213,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
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six
$data = [

View File

@ -130,8 +130,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 +144,7 @@ class MassController extends Controller
$filtered->push($journal);
}
if (count($messages)) {
if (count($messages) > 0) {
Session::flash('info', $messages);
}
@ -156,9 +156,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;
@ -195,7 +195,7 @@ 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;

View File

@ -83,8 +83,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 +98,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 +162,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 +190,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 +212,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,7 +224,7 @@ 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());
@ -226,27 +233,27 @@ class SingleController extends Controller
// 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(),
// 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'),
];
@ -313,22 +320,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 +345,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 +357,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'));

View File

@ -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;

View File

@ -154,7 +154,6 @@ class TransactionController extends Controller
/** @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();

View File

@ -14,11 +14,11 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Middleware;
use Closure;
use Cookie;
use Illuminate\Support\Facades\Auth;
use Preferences;
use Session;
use Log;
use Cookie;
/**
* Class RedirectIfTwoFactorAuthenticated
*

View File

@ -13,6 +13,8 @@ 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.');
}
}

View File

@ -45,11 +45,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,

View File

@ -76,30 +76,26 @@ Breadcrumbs::register(
);
Breadcrumbs::register(
'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start, Carbon $end) {
'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) {
$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]));
$title = '';
$route = '';
if (!is_null($start) && !is_null($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_breadcrumb', ['start' => $startString, 'end' => $endString]));
$route = route('accounts.show.date', [$account->id, $start->format('Y-m-d')]);
}
if (is_null($start) && is_null($end)) {
$title = $title = $account->name . ' (' . strtolower(strval(trans('firefly.everything'))) . ')';
$route = route('accounts.show.date', [$account->id, 'all']);
}
$breadcrumbs->parent('accounts.show', $account);
$breadcrumbs->push($title, route('accounts.show.date', [$account->id, $start->format('Y-m-d')]));
$breadcrumbs->push($title, $route);
}
);
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);
@ -556,6 +552,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');
@ -783,10 +792,15 @@ 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);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
return;
}
$breadcrumbs->parent('index');
}
);

View File

@ -13,7 +13,7 @@ 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 . ')';

View File

@ -16,7 +16,6 @@ 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
*/

View File

@ -108,6 +108,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

View File

@ -14,7 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Models;
use Crypt;
use FireflyIII\Support\Models\TagSupport;
use FireflyIII\Support\Models\TagTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Watson\Validating\ValidatingTrait;
@ -24,8 +25,10 @@ use Watson\Validating\ValidatingTrait;
*
* @package FireflyIII\Models
*/
class Tag extends TagSupport
class Tag extends Model
{
use ValidatingTrait, SoftDeletes, TagTrait;
/**
* The attributes that should be casted to native types.
*
@ -44,7 +47,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 +105,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;

View File

@ -16,8 +16,9 @@ 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));

View File

@ -457,7 +457,8 @@ class AccountRepository implements AccountRepositoryInterface
$name = $data['name'];
$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,

View File

@ -127,12 +127,12 @@ 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')));
$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']));
}

View File

@ -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()));
$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'],

View File

@ -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;
}

View File

@ -193,6 +193,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/
public function store(array $data): TransactionCurrency
{
/** @var TransactionCurrency $currency */
$currency = TransactionCurrency::create(
[
'name' => $data['name'],

View File

@ -40,6 +40,7 @@ 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;
@ -173,7 +174,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']);

View File

@ -68,6 +68,14 @@ class TagRepository implements TagRepositoryInterface
return false;
}
/**
* @return int
*/
public function count(): int
{
return $this->user->tags()->count();
}
/**
* @param Tag $tag
*
@ -163,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
*
@ -357,8 +375,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.*']);
@ -369,8 +387,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) {
/*

View File

@ -37,6 +37,11 @@ interface TagRepositoryInterface
*/
public function connect(TransactionJournal $journal, Tag $tag): bool;
/**
* @return int
*/
public function count(): int;
/**
* This method destroys a tag.
*
@ -83,6 +88,13 @@ interface TagRepositoryInterface
*/
public function get(): Collection;
/**
* @param string $type
*
* @return Collection
*/
public function getByType(string $type): Collection;
/**
* @param Tag $tag
*

View File

@ -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)));

View File

@ -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();

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -187,7 +187,7 @@ class Amount
{
$currency = $journal->transactionCurrency;
return $this->formatAnything($currency, TransactionJournal::amount($journal), $coloured);
return $this->formatAnything($currency, $journal->amount(), $coloured);
}
/**
@ -220,7 +220,7 @@ 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'));
@ -245,7 +245,7 @@ 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();
@ -264,7 +264,7 @@ class Amount
$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();

View File

@ -39,7 +39,11 @@ 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;

View File

@ -1,10 +1,8 @@
<?php
/**
* TagSupport.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
* TagTrait.php
* 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.
*/
@ -13,32 +11,33 @@ declare(strict_types = 1);
namespace FireflyIII\Support\Models;
use FireflyIII\Models\Tag;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* FireflyIII\Support\Models\TagSupport
*
* @mixin \Eloquent
* @property Collection $transactionjournals
* @property string $tagMode
*
* Class TagSupport
*
* @package FireflyIII\Support\Models
*/
class TagSupport extends Model
trait TagTrait
{
/**
* Can a tag become an advance payment?
*
* @param Tag $tag
*
* @return bool
*/
public static function tagAllowAdvance(Tag $tag): bool
public function tagAllowAdvance(): bool
{
/*
* If this tag is a balancing act, and it contains transfers, it cannot be
* changes to an advancePayment.
*/
if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
foreach ($tag->transactionjournals as $journal) {
if ($this->tagMode == 'balancingAct' || $this->tagMode == 'nothing') {
foreach ($this->transactionjournals as $journal) {
if ($journal->isTransfer()) {
return false;
}
@ -49,7 +48,7 @@ class TagSupport extends Model
* If this tag contains more than one expenses, it cannot become an advance payment.
*/
$count = 0;
foreach ($tag->transactionjournals as $journal) {
foreach ($this->transactionjournals as $journal) {
if ($journal->isWithdrawal()) {
$count++;
}
@ -64,23 +63,21 @@ class TagSupport extends Model
/**
* Can a tag become a balancing act?
*
* @param Tag $tag
*
* @return bool
*/
public static function tagAllowBalancing(Tag $tag): bool
public function tagAllowBalancing(): bool
{
/*
* If has more than two transactions already, cannot become a balancing act:
*/
if ($tag->transactionjournals->count() > 2) {
if ($this->transactionjournals->count() > 2) {
return false;
}
/*
* If any transaction is a deposit, cannot become a balancing act.
*/
foreach ($tag->transactionjournals as $journal) {
foreach ($this->transactionjournals as $journal) {
if ($journal->isDeposit()) {
return false;
}

View File

@ -1,10 +1,8 @@
<?php
/**
* TransactionJournalSupport.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
* TransactionJournalTrait.php
* 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.
*/
@ -24,34 +22,34 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* Class TransactionJournalSupport
* Class TransactionJournalTrait
*
* @property int $id
* @method Collection transactions()
* @method bool isWithdrawal()
*
* @package FireflyIII\Support\Models
* @mixin \Eloquent
*/
class TransactionJournalSupport extends Model
trait TransactionJournalTrait
{
/**
* @param TransactionJournal $journal
*
* @return string
* @throws FireflyException
*/
public static function amount(TransactionJournal $journal): string
public function amount(): string
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('amount');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
// saves on queries:
$amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
$amount = $this->transactions()->where('amount', '>', 0)->get()->sum('amount');
if ($journal->isWithdrawal()) {
if ($this->isWithdrawal()) {
$amount = $amount * -1;
}
$amount = strval($amount);
@ -61,22 +59,20 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
*
* @return string
*/
public static function amountPositive(TransactionJournal $journal): string
public function amountPositive(): string
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('amount-positive');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
// saves on queries:
$amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
$amount = $this->transactions()->where('amount', '>', 0)->get()->sum('amount');
$amount = strval($amount);
$cache->store($amount);
@ -85,13 +81,11 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
*
* @return int
*/
public static function budgetId(TransactionJournal $journal): int
public function budgetId(): int
{
$budget = $journal->budgets()->first();
$budget = $this->budgets()->first();
if (!is_null($budget)) {
return $budget->id;
}
@ -100,13 +94,11 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
*
* @return string
*/
public static function categoryAsString(TransactionJournal $journal): string
public function categoryAsString(): string
{
$category = $journal->categories()->first();
$category = $this->categories()->first();
if (!is_null($category)) {
return $category->name;
}
@ -115,29 +107,28 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
* @param string $dateField
*
* @return string
*/
public static function dateAsString(TransactionJournal $journal, string $dateField = ''): string
public function dateAsString(string $dateField = ''): string
{
if ($dateField === '') {
return $journal->date->format('Y-m-d');
return $this->date->format('Y-m-d');
}
if (!is_null($journal->$dateField) && $journal->$dateField instanceof Carbon) {
if (!is_null($this->$dateField) && $this->$dateField instanceof Carbon) {
// make field NULL
$carbon = clone $journal->$dateField;
$journal->$dateField = null;
$journal->save();
$carbon = clone $this->$dateField;
$this->$dateField = null;
$this->save();
// create meta entry
$journal->setMeta($dateField, $carbon);
$this->setMeta($dateField, $carbon);
// return that one instead.
return $carbon->format('Y-m-d');
}
$metaField = $journal->getMeta($dateField);
$metaField = $this->getMeta($dateField);
if (!is_null($metaField)) {
$carbon = new Carbon($metaField);
@ -150,45 +141,42 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public static function destinationAccountList(TransactionJournal $journal): Collection
public function destinationAccountList(): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('destination-account-list');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$transactions = $journal->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get();
$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;
}
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public static function destinationTransactionList(TransactionJournal $journal): Collection
public function destinationTransactionList(): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('destination-transaction-list');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$list = $journal->transactions()->where('amount', '>', 0)->with('account')->get();
$list = $this->transactions()->where('amount', '>', 0)->with('account')->get();
$cache->store($list);
return $list;
@ -200,7 +188,7 @@ class TransactionJournalSupport extends Model
*
* @return bool
*/
public static function isJoined(Builder $query, string $table): bool
public function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (is_null($joins)) {
@ -216,92 +204,73 @@ class TransactionJournalSupport extends Model
}
/**
* @param TransactionJournal $journal
*
* @return int
*/
public static function piggyBankId(TransactionJournal $journal): int
public function piggyBankId(): int
{
if ($journal->piggyBankEvents()->count() > 0) {
return $journal->piggyBankEvents()->orderBy('date', 'DESC')->first()->piggy_bank_id;
if ($this->piggyBankEvents()->count() > 0) {
return $this->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
public function sourceAccountList(): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('source-account-list');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$transactions = $journal->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get();
$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;
}
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public static function sourceTransactionList(TransactionJournal $journal): Collection
public function sourceTransactionList(): Collection
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('source-transaction-list');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$list = $journal->transactions()->where('amount', '<', 0)->with('account')->get();
$list = $this->transactions()->where('amount', '<', 0)->with('account')->get();
$cache->store($list);
return $list;
}
/**
* @param TransactionJournal $journal
*
* @return string
*/
public static function transactionTypeStr(TransactionJournal $journal): string
public function transactionTypeStr(): string
{
$cache = new CacheProperties;
$cache->addProperty($journal->id);
$cache->addProperty($this->id);
$cache->addProperty('transaction-journal');
$cache->addProperty('type-string');
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$typeStr = $journal->transaction_type_type ?? $journal->transactionType->type;
$typeStr = $this->transaction_type_type ?? $this->transactionType->type;
$cache->store($typeStr);
return $typeStr;

View File

@ -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);
}
/**

View File

@ -44,59 +44,58 @@ class Modifier
switch ($modifier['type']) {
default:
throw new FireflyException(sprintf('Search modifier "%s" is not (yet) supported. Sorry!', $modifier['type']));
break;
case 'amount':
case 'amount_is':
$res = Modifier::amountCompare($transaction, $modifier['value'], 0);
$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 = Modifier::amountCompare($transaction, $modifier['value'], 1);
$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 = Modifier::amountCompare($transaction, $modifier['value'], -1);
$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 = Modifier::stringCompare($transaction->account_name, $modifier['value']);
$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 = Modifier::stringCompare($transaction->opposing_account_name, $modifier['value']);
$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 = Modifier::category($transaction, $modifier['value']);
$res = self::category($transaction, $modifier['value']);
Log::debug(sprintf('Category is %s? %s', $modifier['value'], var_export($res, true)));
break;
case 'budget':
$res = Modifier::budget($transaction, $modifier['value']);
$res = self::budget($transaction, $modifier['value']);
Log::debug(sprintf('Budget is %s? %s', $modifier['value'], var_export($res, true)));
break;
case 'bill':
$res = Modifier::stringCompare(strval($transaction->bill_name), $modifier['value']);
$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 = Modifier::stringCompare($transaction->transaction_type_type, $modifier['value']);
$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 = Modifier::sameDate($transaction->date, $modifier['value']);
$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 = Modifier::dateBefore($transaction->date, $modifier['value']);
$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 = Modifier::dateAfter($transaction->date, $modifier['value']);
$res = self::dateAfter($transaction->date, $modifier['value']);
Log::debug(sprintf('Date is %s? %s', $modifier['value'], var_export($res, true)));
break;
}

View File

@ -43,7 +43,7 @@ class Steam
$cache->addProperty('balance');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$balance = strval(
@ -74,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(
@ -108,7 +108,7 @@ class Steam
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get();
return $cache->get(); // @codeCoverageIgnore
}
$balances = [];
@ -158,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')

View File

@ -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();
}
);
}

View File

@ -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):

View File

@ -39,6 +39,12 @@ class PiggyBank extends Twig_Extension
}
);
$functions[] = new Twig_SimpleFunction(
'suggestedMonthlyAmount', function (PB $piggyBank) {
return $piggyBank->getSuggestedMonthlyAmount();
}
);
return $functions;
}

Some files were not shown because too many files have changed in this diff Show More