Merge branch 'release/4.7.5.2'

This commit is contained in:
James Cole
2018-07-28 13:38:06 +02:00
715 changed files with 14227 additions and 12139 deletions

View File

@@ -9,9 +9,16 @@ VM_NAME = File.basename(File.dirname(File.dirname(__FILE__))) + "_sandstorm_#{Ti
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
# ugly hack to prevent hashicorp's bitrot. See https://github.com/hashicorp/vagrant/issues/9442
# this setting is required for pre-2.0 vagrant, but causes an error as of 2.0.3,
# remove entirely when confident nobody uses vagrant 1.x for anything.
unless Vagrant::DEFAULT_SERVER_URL.frozen?
Vagrant::DEFAULT_SERVER_URL.replace('https://vagrantcloud.com')
end
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Base on the Sandstorm snapshots of the official Debian 8 (jessie) box.
config.vm.box = "sandstorm/debian-jessie64"
config.vm.box = "debian/contrib-jessie64"
if Vagrant.has_plugin?("vagrant-vbguest") then
# vagrant-vbguest is a Vagrant plugin that upgrades

View File

@@ -1,3 +1,17 @@
# 4.7.5.2
- [Issue 1527](https://github.com/firefly-iii/firefly-iii/issues/1527), fixed views for transactions without a budget.
- [Issue 1553](https://github.com/firefly-iii/firefly-iii/issues/1553), report could not handle transactions before the first one in the system.
- [Issue 1549](https://github.com/firefly-iii/firefly-iii/issues/1549) update a budget will also update any rules that refer to that budget.
- [Issue 1530](https://github.com/firefly-iii/firefly-iii/issues/1530), fix issue with bill chart.
- [Issue 1563](https://github.com/firefly-iii/firefly-iii/issues/1563), fix piggy bank suggested amount
- [Issue 1571](https://github.com/firefly-iii/firefly-iii/issues/1571), fix OAuth in Sandstorm
- [Issue 1568](https://github.com/firefly-iii/firefly-iii/issues/1568), bug in Sandstorm user code.
- [Issue 1569](https://github.com/firefly-iii/firefly-iii/issues/1569), optimized Sandstorm build by [ocdtrekkie](https://github.com/ocdtrekkie)
- Fixed a bug where transfers would be stored inversely when using the CSV import.
- Retired the "Rabobank description"-fix, because it is no longer necessary.
- Fixed a bug where users could not delete budget limits in the API.
- Piggy bank notes are visible again.
# 4.7.5.1
- [Issue 1531](https://github.com/firefly-iii/firefly-iii/issues/1531), the database routine incorrectly reports empty categories.
- [Issue 1532](https://github.com/firefly-iii/firefly-iii/issues/1532), broken dropdown for autosuggest things.

View File

@@ -9,6 +9,10 @@ CURL_OPTS="--silent --show-error"
echo localhost > /etc/hostname
hostname localhost
# Install curl that is needed below.
apt-get update
apt-get install -y curl
# The following line copies stderr through stderr to cat without accidentally leaving it in the
# output file. Be careful when changing. See: https://github.com/sandstorm-io/vagrant-spk/pull/159
curl $CURL_OPTS https://install.sandstorm.io/ 2>&1 > /host-dot-sandstorm/caches/install.sh | cat

View File

@@ -209,13 +209,6 @@ opt/app/.env.sandstorm
opt/app/.gitattributes
opt/app/.htaccess
opt/app/.sandstorm/.gitattributes
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_provision
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_set_name
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/creator_uid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/id
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/index_uuid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/private_key
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/synced_folders
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/vagrant_cwd
opt/app/.sandstorm/Vagrantfile
opt/app/.sandstorm/app-graphics/firefly-iii-128.png
@@ -242,15 +235,41 @@ opt/app/LICENSE
opt/app/app.json
opt/app/app/Api/V1/Controllers/AboutController.php
opt/app/app/Api/V1/Controllers/AccountController.php
opt/app/app/Api/V1/Controllers/AttachmentController.php
opt/app/app/Api/V1/Controllers/AvailableBudgetController.php
opt/app/app/Api/V1/Controllers/BillController.php
opt/app/app/Api/V1/Controllers/BudgetController.php
opt/app/app/Api/V1/Controllers/BudgetLimitController.php
opt/app/app/Api/V1/Controllers/CategoryController.php
opt/app/app/Api/V1/Controllers/ConfigurationController.php
opt/app/app/Api/V1/Controllers/Controller.php
opt/app/app/Api/V1/Controllers/CurrencyController.php
opt/app/app/Api/V1/Controllers/CurrencyExchangeRateController.php
opt/app/app/Api/V1/Controllers/JournalLinkController.php
opt/app/app/Api/V1/Controllers/LinkTypeController.php
opt/app/app/Api/V1/Controllers/PiggyBankController.php
opt/app/app/Api/V1/Controllers/PreferenceController.php
opt/app/app/Api/V1/Controllers/RecurrenceController.php
opt/app/app/Api/V1/Controllers/RuleController.php
opt/app/app/Api/V1/Controllers/RuleGroupController.php
opt/app/app/Api/V1/Controllers/TransactionController.php
opt/app/app/Api/V1/Controllers/UserController.php
opt/app/app/Api/V1/Requests/AccountRequest.php
opt/app/app/Api/V1/Requests/AttachmentRequest.php
opt/app/app/Api/V1/Requests/AvailableBudgetRequest.php
opt/app/app/Api/V1/Requests/BillRequest.php
opt/app/app/Api/V1/Requests/BudgetLimitRequest.php
opt/app/app/Api/V1/Requests/BudgetRequest.php
opt/app/app/Api/V1/Requests/CategoryRequest.php
opt/app/app/Api/V1/Requests/CurrencyRequest.php
opt/app/app/Api/V1/Requests/JournalLinkRequest.php
opt/app/app/Api/V1/Requests/LinkTypeRequest.php
opt/app/app/Api/V1/Requests/PiggyBankRequest.php
opt/app/app/Api/V1/Requests/PreferenceRequest.php
opt/app/app/Api/V1/Requests/RecurrenceRequest.php
opt/app/app/Api/V1/Requests/Request.php
opt/app/app/Api/V1/Requests/RuleGroupRequest.php
opt/app/app/Api/V1/Requests/RuleRequest.php
opt/app/app/Api/V1/Requests/TransactionRequest.php
opt/app/app/Api/V1/Requests/UserRequest.php
opt/app/app/Console/Commands/CreateExport.php
@@ -269,6 +288,7 @@ opt/app/app/Events/AdminRequestedTestMessage.php
opt/app/app/Events/Event.php
opt/app/app/Events/RegisteredUser.php
opt/app/app/Events/RequestedNewPassword.php
opt/app/app/Events/RequestedReportOnJournals.php
opt/app/app/Events/RequestedVersionCheckStatus.php
opt/app/app/Events/StoredTransactionJournal.php
opt/app/app/Events/UpdatedTransactionJournal.php
@@ -289,11 +309,13 @@ opt/app/app/Export/Exporter/ExporterInterface.php
opt/app/app/Export/ProcessorInterface.php
opt/app/app/Factory/AccountFactory.php
opt/app/app/Factory/AccountMetaFactory.php
opt/app/app/Factory/AttachmentFactory.php
opt/app/app/Factory/BillFactory.php
opt/app/app/Factory/BudgetFactory.php
opt/app/app/Factory/CategoryFactory.php
opt/app/app/Factory/PiggyBankEventFactory.php
opt/app/app/Factory/PiggyBankFactory.php
opt/app/app/Factory/RecurrenceFactory.php
opt/app/app/Factory/TagFactory.php
opt/app/app/Factory/TransactionCurrencyFactory.php
opt/app/app/Factory/TransactionFactory.php
@@ -325,6 +347,7 @@ opt/app/app/Generator/Report/Tag/MultiYearReportGenerator.php
opt/app/app/Generator/Report/Tag/YearReportGenerator.php
opt/app/app/Handlers/Events/APIEventHandler.php
opt/app/app/Handlers/Events/AdminEventHandler.php
opt/app/app/Handlers/Events/AutomationHandler.php
opt/app/app/Handlers/Events/StoredJournalEventHandler.php
opt/app/app/Handlers/Events/UpdatedJournalEventHandler.php
opt/app/app/Handlers/Events/UserEventHandler.php
@@ -365,8 +388,13 @@ opt/app/app/Helpers/Report/PopupReport.php
opt/app/app/Helpers/Report/PopupReportInterface.php
opt/app/app/Helpers/Report/ReportHelper.php
opt/app/app/Helpers/Report/ReportHelperInterface.php
opt/app/app/Helpers/Update/UpdateTrait.php
opt/app/app/Http/Controllers/Account/CreateController.php
opt/app/app/Http/Controllers/Account/DeleteController.php
opt/app/app/Http/Controllers/Account/EditController.php
opt/app/app/Http/Controllers/Account/IndexController.php
opt/app/app/Http/Controllers/Account/ReconcileController.php
opt/app/app/Http/Controllers/AccountController.php
opt/app/app/Http/Controllers/Account/ShowController.php
opt/app/app/Http/Controllers/Admin/ConfigurationController.php
opt/app/app/Http/Controllers/Admin/HomeController.php
opt/app/app/Http/Controllers/Admin/LinkController.php
@@ -379,7 +407,14 @@ opt/app/app/Http/Controllers/Auth/RegisterController.php
opt/app/app/Http/Controllers/Auth/ResetPasswordController.php
opt/app/app/Http/Controllers/Auth/TwoFactorController.php
opt/app/app/Http/Controllers/BillController.php
opt/app/app/Http/Controllers/BudgetController.php
opt/app/app/Http/Controllers/Budget/AmountController.php
opt/app/app/Http/Controllers/Budget/CreateController.php
opt/app/app/Http/Controllers/Budget/DeleteController.php
opt/app/app/Http/Controllers/Budget/EditController.php
opt/app/app/Http/Controllers/Budget/IndexController.php
opt/app/app/Http/Controllers/Budget/ShowController.php
opt/app/app/Http/Controllers/Category/NoCategoryController.php
opt/app/app/Http/Controllers/Category/ShowController.php
opt/app/app/Http/Controllers/CategoryController.php
opt/app/app/Http/Controllers/Chart/AccountController.php
opt/app/app/Http/Controllers/Chart/BillController.php
@@ -407,12 +442,18 @@ opt/app/app/Http/Controllers/Json/BoxController.php
opt/app/app/Http/Controllers/Json/ExchangeController.php
opt/app/app/Http/Controllers/Json/FrontpageController.php
opt/app/app/Http/Controllers/Json/IntroController.php
opt/app/app/Http/Controllers/Json/ReconcileController.php
opt/app/app/Http/Controllers/Json/RecurrenceController.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/Popup/ReportController.php
opt/app/app/Http/Controllers/PreferencesController.php
opt/app/app/Http/Controllers/ProfileController.php
opt/app/app/Http/Controllers/Recurring/CreateController.php
opt/app/app/Http/Controllers/Recurring/DeleteController.php
opt/app/app/Http/Controllers/Recurring/EditController.php
opt/app/app/Http/Controllers/Recurring/IndexController.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
@@ -420,7 +461,11 @@ opt/app/app/Http/Controllers/Report/CategoryController.php
opt/app/app/Http/Controllers/Report/ExpenseController.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/Rule/CreateController.php
opt/app/app/Http/Controllers/Rule/DeleteController.php
opt/app/app/Http/Controllers/Rule/EditController.php
opt/app/app/Http/Controllers/Rule/IndexController.php
opt/app/app/Http/Controllers/Rule/SelectController.php
opt/app/app/Http/Controllers/RuleGroupController.php
opt/app/app/Http/Controllers/SearchController.php
opt/app/app/Http/Controllers/System/InstallController.php
@@ -471,6 +516,7 @@ opt/app/app/Http/Requests/PiggyBankFormRequest.php
opt/app/app/Http/Requests/ProfileFormRequest.php
opt/app/app/Http/Requests/ReconciliationStoreRequest.php
opt/app/app/Http/Requests/ReconciliationUpdateRequest.php
opt/app/app/Http/Requests/RecurrenceFormRequest.php
opt/app/app/Http/Requests/ReportFormRequest.php
opt/app/app/Http/Requests/Request.php
opt/app/app/Http/Requests/RuleFormRequest.php
@@ -482,8 +528,6 @@ opt/app/app/Http/Requests/TestRuleFormRequest.php
opt/app/app/Http/Requests/TokenFormRequest.php
opt/app/app/Http/Requests/UserFormRequest.php
opt/app/app/Http/Requests/UserRegistrationRequest.php
opt/app/app/Import/Configuration/BunqConfigurator.php
opt/app/app/Import/Configuration/ConfiguratorInterface.php
opt/app/app/Import/Converter/Amount.php
opt/app/app/Import/Converter/AmountCredit.php
opt/app/app/Import/Converter/AmountDebit.php
@@ -508,12 +552,6 @@ opt/app/app/Import/Mapper/TransactionCurrencies.php
opt/app/app/Import/MapperPreProcess/PreProcessorInterface.php
opt/app/app/Import/MapperPreProcess/TagsComma.php
opt/app/app/Import/MapperPreProcess/TagsSpace.php
opt/app/app/Import/Object/ImportAccount.php
opt/app/app/Import/Object/ImportBill.php
opt/app/app/Import/Object/ImportBudget.php
opt/app/app/Import/Object/ImportCategory.php
opt/app/app/Import/Object/ImportCurrency.php
opt/app/app/Import/Object/ImportJournal.php
opt/app/app/Import/Prerequisites/BunqPrerequisites.php
opt/app/app/Import/Prerequisites/FakePrerequisites.php
opt/app/app/Import/Prerequisites/FilePrerequisites.php
@@ -531,6 +569,7 @@ opt/app/app/Import/Specifics/RabobankDescription.php
opt/app/app/Import/Specifics/SnsDescription.php
opt/app/app/Import/Specifics/SpecificInterface.php
opt/app/app/Import/Storage/ImportArrayStorage.php
opt/app/app/Jobs/CreateRecurringTransactions.php
opt/app/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php
opt/app/app/Jobs/ExecuteRuleOnExistingTransactions.php
opt/app/app/Jobs/Job.php
@@ -540,6 +579,7 @@ opt/app/app/Mail/AdminTestMail.php
opt/app/app/Mail/ConfirmEmailChangeMail.php
opt/app/app/Mail/OAuthTokenCreatedMail.php
opt/app/app/Mail/RegisteredUser.php
opt/app/app/Mail/ReportNewJournalsMail.php
opt/app/app/Mail/RequestedNewPassword.php
opt/app/app/Mail/UndoEmailChangeMail.php
opt/app/app/Models/Account.php
@@ -561,6 +601,11 @@ opt/app/app/Models/PiggyBank.php
opt/app/app/Models/PiggyBankEvent.php
opt/app/app/Models/PiggyBankRepetition.php
opt/app/app/Models/Preference.php
opt/app/app/Models/Recurrence.php
opt/app/app/Models/RecurrenceMeta.php
opt/app/app/Models/RecurrenceRepetition.php
opt/app/app/Models/RecurrenceTransaction.php
opt/app/app/Models/RecurrenceTransactionMeta.php
opt/app/app/Models/Role.php
opt/app/app/Models/Rule.php
opt/app/app/Models/RuleAction.php
@@ -588,8 +633,8 @@ opt/app/app/Providers/ExportJobServiceProvider.php
opt/app/app/Providers/FireflyServiceProvider.php
opt/app/app/Providers/FireflySessionProvider.php
opt/app/app/Providers/JournalServiceProvider.php
opt/app/app/Providers/LogServiceProvider.php
opt/app/app/Providers/PiggyBankServiceProvider.php
opt/app/app/Providers/RecurringServiceProvider.php
opt/app/app/Providers/RouteServiceProvider.php
opt/app/app/Providers/RuleGroupServiceProvider.php
opt/app/app/Providers/RuleServiceProvider.php
@@ -600,7 +645,6 @@ opt/app/app/Repositories/Account/AccountRepository.php
opt/app/app/Repositories/Account/AccountRepositoryInterface.php
opt/app/app/Repositories/Account/AccountTasker.php
opt/app/app/Repositories/Account/AccountTaskerInterface.php
opt/app/app/Repositories/Account/FindAccountsTrait.php
opt/app/app/Repositories/Attachment/AttachmentRepository.php
opt/app/app/Repositories/Attachment/AttachmentRepositoryInterface.php
opt/app/app/Repositories/Bill/BillRepository.php
@@ -621,18 +665,22 @@ opt/app/app/Repositories/LinkType/LinkTypeRepository.php
opt/app/app/Repositories/LinkType/LinkTypeRepositoryInterface.php
opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php
opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
opt/app/app/Repositories/Recurring/RecurringRepository.php
opt/app/app/Repositories/Recurring/RecurringRepositoryInterface.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/TransactionType/TransactionTypeRepository.php
opt/app/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php
opt/app/app/Repositories/User/UserRepository.php
opt/app/app/Repositories/User/UserRepositoryInterface.php
opt/app/app/Rules/BelongsUser.php
opt/app/app/Rules/IsAssetAccountId.php
opt/app/app/Rules/IsValidAttachmentModel.php
opt/app/app/Rules/UniqueIban.php
opt/app/app/Rules/ValidRecurrenceRepetitionType.php
opt/app/app/Rules/ValidRecurrenceRepetitionValue.php
opt/app/app/Rules/ValidTransactions.php
opt/app/app/Services/Bunq/ApiContext.php
opt/app/app/Services/Bunq/MonetaryAccount.php
@@ -650,16 +698,20 @@ opt/app/app/Services/Internal/Destroy/BillDestroyService.php
opt/app/app/Services/Internal/Destroy/CategoryDestroyService.php
opt/app/app/Services/Internal/Destroy/CurrencyDestroyService.php
opt/app/app/Services/Internal/Destroy/JournalDestroyService.php
opt/app/app/Services/Internal/Destroy/RecurrenceDestroyService.php
opt/app/app/Services/Internal/File/EncryptService.php
opt/app/app/Services/Internal/Support/AccountServiceTrait.php
opt/app/app/Services/Internal/Support/BillServiceTrait.php
opt/app/app/Services/Internal/Support/JournalServiceTrait.php
opt/app/app/Services/Internal/Support/RecurringTransactionTrait.php
opt/app/app/Services/Internal/Support/TransactionServiceTrait.php
opt/app/app/Services/Internal/Support/TransactionTypeTrait.php
opt/app/app/Services/Internal/Update/AccountUpdateService.php
opt/app/app/Services/Internal/Update/BillUpdateService.php
opt/app/app/Services/Internal/Update/CategoryUpdateService.php
opt/app/app/Services/Internal/Update/CurrencyUpdateService.php
opt/app/app/Services/Internal/Update/JournalUpdateService.php
opt/app/app/Services/Internal/Update/RecurrenceUpdateService.php
opt/app/app/Services/Internal/Update/TransactionUpdateService.php
opt/app/app/Services/Password/PwndVerifierV2.php
opt/app/app/Services/Password/Verifier.php
@@ -705,6 +757,8 @@ opt/app/app/Support/Facades/Navigation.php
opt/app/app/Support/Facades/Preferences.php
opt/app/app/Support/Facades/Steam.php
opt/app/app/Support/FireflyConfig.php
opt/app/app/Support/Http/Controllers/DateCalculation.php
opt/app/app/Support/Http/Controllers/RuleManagement.php
opt/app/app/Support/Import/Information/BunqInformation.php
opt/app/app/Support/Import/Information/GetSpectreCustomerTrait.php
opt/app/app/Support/Import/Information/GetSpectreTokenTrait.php
@@ -759,7 +813,6 @@ opt/app/app/Support/Twig/Journal.php
opt/app/app/Support/Twig/Loader/AccountLoader.php
opt/app/app/Support/Twig/Loader/TransactionJournalLoader.php
opt/app/app/Support/Twig/Loader/TransactionLoader.php
opt/app/app/Support/Twig/PiggyBank.php
opt/app/app/Support/Twig/Rule.php
opt/app/app/Support/Twig/Transaction.php
opt/app/app/Support/Twig/Translation.php
@@ -823,18 +876,32 @@ opt/app/app/TransactionRules/Triggers/TriggerInterface.php
opt/app/app/TransactionRules/Triggers/UserAction.php
opt/app/app/Transformers/AccountTransformer.php
opt/app/app/Transformers/AttachmentTransformer.php
opt/app/app/Transformers/AvailableBudgetTransformer.php
opt/app/app/Transformers/BillTransformer.php
opt/app/app/Transformers/BudgetLimitTransformer.php
opt/app/app/Transformers/BudgetTransformer.php
opt/app/app/Transformers/CategoryTransformer.php
opt/app/app/Transformers/CurrencyExchangeRateTransformer.php
opt/app/app/Transformers/CurrencyTransformer.php
opt/app/app/Transformers/JournalLinkTransformer.php
opt/app/app/Transformers/JournalMetaTransformer.php
opt/app/app/Transformers/LinkTypeTransformer.php
opt/app/app/Transformers/NoteTransformer.php
opt/app/app/Transformers/PiggyBankEventTransformer.php
opt/app/app/Transformers/PiggyBankTransformer.php
opt/app/app/Transformers/PreferenceTransformer.php
opt/app/app/Transformers/RecurrenceTransformer.php
opt/app/app/Transformers/RuleActionTransformer.php
opt/app/app/Transformers/RuleGroupTransformer.php
opt/app/app/Transformers/RuleTransformer.php
opt/app/app/Transformers/RuleTriggerTransformer.php
opt/app/app/Transformers/TagTransformer.php
opt/app/app/Transformers/TransactionTransformer.php
opt/app/app/Transformers/UserTransformer.php
opt/app/app/User.php
opt/app/app/Validation/FireflyValidator.php
opt/app/app/Validation/RecurrenceValidation.php
opt/app/app/Validation/TransactionValidation.php
opt/app/artisan
opt/app/bootstrap/app.php
opt/app/bootstrap/cache/packages.php
@@ -842,7 +909,6 @@ opt/app/bootstrap/cache/services.php
opt/app/changelog.md
opt/app/composer.json
opt/app/composer.lock
opt/app/composer.phar
opt/app/config/app.php
opt/app/config/auth.php
opt/app/config/breadcrumbs.php
@@ -887,6 +953,7 @@ opt/app/database/migrations/2018_01_01_000005_create_oauth_personal_access_clien
opt/app/database/migrations/2018_03_19_141348_changes_for_v472.php
opt/app/database/migrations/2018_04_07_210913_changes_for_v473.php
opt/app/database/migrations/2018_04_29_174524_changes_for_v474.php
opt/app/database/migrations/2018_06_08_200526_changes_for_v475.php
opt/app/database/seeds/AccountTypeSeeder.php
opt/app/database/seeds/ConfigSeeder.php
opt/app/database/seeds/DatabaseSeeder.php
@@ -1105,6 +1172,8 @@ opt/app/public/js/ff/piggy-banks/edit.js
opt/app/public/js/ff/piggy-banks/index.js
opt/app/public/js/ff/piggy-banks/show.js
opt/app/public/js/ff/preferences/index.js
opt/app/public/js/ff/recurring/create.js
opt/app/public/js/ff/recurring/edit.js
opt/app/public/js/ff/reports/account/month.js
opt/app/public/js/ff/reports/all.js
opt/app/public/js/ff/reports/audit/all.js
@@ -1153,6 +1222,12 @@ opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css
opt/app/public/lib/adminlte/img/icons.png
opt/app/public/lib/adminlte/js/app.js
opt/app/public/lib/adminlte/js/app.min.js
opt/app/public/lib/fc/fullcalendar.css
opt/app/public/lib/fc/fullcalendar.js
opt/app/public/lib/fc/fullcalendar.min.css
opt/app/public/lib/fc/fullcalendar.min.js
opt/app/public/lib/fc/fullcalendar.print.css
opt/app/public/lib/fc/fullcalendar.print.min.css
opt/app/public/lib/intro/intro.min.js
opt/app/public/lib/intro/introjs-rtl.min.css
opt/app/public/lib/intro/introjs.min.css
@@ -1419,6 +1494,8 @@ opt/app/resources/views/demo/import/index.twig
opt/app/resources/views/demo/index.twig
opt/app/resources/views/demo/no-demo-text.twig
opt/app/resources/views/demo/piggy-banks/index.twig
opt/app/resources/views/demo/recurring/index.twig
opt/app/resources/views/demo/recurring/recurring-create.twig
opt/app/resources/views/demo/reports/index.twig
opt/app/resources/views/demo/transactions/index.twig
opt/app/resources/views/emails/access-token-created-html.twig
@@ -1441,6 +1518,8 @@ opt/app/resources/views/emails/password-html.twig
opt/app/resources/views/emails/password-text.twig
opt/app/resources/views/emails/registered-html.twig
opt/app/resources/views/emails/registered-text.twig
opt/app/resources/views/emails/report-new-journals-html.twig
opt/app/resources/views/emails/report-new-journals-text.twig
opt/app/resources/views/emails/undo-email-change-html.twig
opt/app/resources/views/emails/undo-email-change-text.twig
opt/app/resources/views/error.twig
@@ -1538,6 +1617,11 @@ opt/app/resources/views/profile/change-password.twig
opt/app/resources/views/profile/code.twig
opt/app/resources/views/profile/delete-account.twig
opt/app/resources/views/profile/index.twig
opt/app/resources/views/recurring/create.twig
opt/app/resources/views/recurring/delete.twig
opt/app/resources/views/recurring/edit.twig
opt/app/resources/views/recurring/index.twig
opt/app/resources/views/recurring/show.twig
opt/app/resources/views/reports/account/report.twig
opt/app/resources/views/reports/audit/report.twig
opt/app/resources/views/reports/budget/month.twig
@@ -2060,30 +2144,6 @@ opt/app/vendor/defuse/php-encryption/src/Key.php
opt/app/vendor/defuse/php-encryption/src/KeyOrPassword.php
opt/app/vendor/defuse/php-encryption/src/KeyProtectedByPassword.php
opt/app/vendor/defuse/php-encryption/src/RuntimeTests.php
opt/app/vendor/doctrine/annotations/CHANGELOG.md
opt/app/vendor/doctrine/annotations/LICENSE
opt/app/vendor/doctrine/annotations/README.md
opt/app/vendor/doctrine/annotations/composer.json
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php
opt/app/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php
opt/app/vendor/doctrine/annotations/phpstan.neon
opt/app/vendor/doctrine/cache/LICENSE
opt/app/vendor/doctrine/cache/README.md
opt/app/vendor/doctrine/cache/UPGRADE.md
@@ -2118,89 +2178,11 @@ opt/app/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php
opt/app/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php
opt/app/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php
opt/app/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php
opt/app/vendor/doctrine/collections/CONTRIBUTING.md
opt/app/vendor/doctrine/collections/LICENSE
opt/app/vendor/doctrine/collections/README.md
opt/app/vendor/doctrine/collections/composer.json
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php
opt/app/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php
opt/app/vendor/doctrine/common/LICENSE
opt/app/vendor/doctrine/common/README.md
opt/app/vendor/doctrine/common/UPGRADE_TO_2_1
opt/app/vendor/doctrine/common/UPGRADE_TO_2_2
opt/app/vendor/doctrine/common/composer.json
opt/app/vendor/doctrine/common/humbug.json.dist
opt/app/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php
opt/app/vendor/doctrine/common/lib/Doctrine/Common/Version.php
opt/app/vendor/doctrine/common/phpstan.neon
opt/app/vendor/doctrine/dbal/.github/ISSUE_TEMPLATE/BC_Break.md
opt/app/vendor/doctrine/dbal/.github/ISSUE_TEMPLATE/Bug.md
opt/app/vendor/doctrine/dbal/.github/ISSUE_TEMPLATE/Feature_Request.md
opt/app/vendor/doctrine/dbal/.github/ISSUE_TEMPLATE/Support_Question.md
opt/app/vendor/doctrine/dbal/.github/PULL_REQUEST_TEMPLATE.md
opt/app/vendor/doctrine/dbal/LICENSE
opt/app/vendor/doctrine/dbal/README.md
opt/app/vendor/doctrine/dbal/SECURITY.md
@@ -2324,6 +2306,7 @@ opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.ph
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MariaDb102Keywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL80Keywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL100Keywords.php
@@ -2343,6 +2326,7 @@ opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywo
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MariaDb1027Platform.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL80Platform.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL100Platform.php
@@ -2367,7 +2351,6 @@ opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpress
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/README.markdown
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php
@@ -2421,6 +2404,7 @@ opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWor
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Dumper.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/TransactionIsolationLevel.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php
@@ -2455,6 +2439,16 @@ opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeImmutableType.ph
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php
opt/app/vendor/doctrine/dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php
opt/app/vendor/doctrine/dbal/phpstan.neon.dist
opt/app/vendor/doctrine/event-manager/LICENSE
opt/app/vendor/doctrine/event-manager/README.md
opt/app/vendor/doctrine/event-manager/composer.json
opt/app/vendor/doctrine/event-manager/docs/en/index.rst
opt/app/vendor/doctrine/event-manager/docs/en/reference/index.rst
opt/app/vendor/doctrine/event-manager/docs/en/sidebar.rst
opt/app/vendor/doctrine/event-manager/lib/Doctrine/Common/EventArgs.php
opt/app/vendor/doctrine/event-manager/lib/Doctrine/Common/EventManager.php
opt/app/vendor/doctrine/event-manager/lib/Doctrine/Common/EventSubscriber.php
opt/app/vendor/doctrine/inflector/LICENSE
opt/app/vendor/doctrine/inflector/README.md
opt/app/vendor/doctrine/inflector/composer.json
@@ -2904,6 +2898,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/Refr
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/StatusCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/TableGuesser.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/stubs/seeder.stub
@@ -3176,6 +3171,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutMiddle
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/stubs/facade.stub
opt/app/vendor/laravel/framework/src/Illuminate/Hashing/AbstractHasher.php
opt/app/vendor/laravel/framework/src/Illuminate/Hashing/ArgonHasher.php
opt/app/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php
opt/app/vendor/laravel/framework/src/Illuminate/Hashing/HashManager.php
@@ -4432,9 +4428,6 @@ opt/app/vendor/ramsey/uuid/CONTRIBUTING.md
opt/app/vendor/ramsey/uuid/LICENSE
opt/app/vendor/ramsey/uuid/README.md
opt/app/vendor/ramsey/uuid/composer.json
opt/app/vendor/ramsey/uuid/docs/Makefile
opt/app/vendor/ramsey/uuid/docs/conf.py
opt/app/vendor/ramsey/uuid/docs/index.rst
opt/app/vendor/ramsey/uuid/src/BinaryUtils.php
opt/app/vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php
opt/app/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php
@@ -4532,6 +4525,10 @@ opt/app/vendor/swiftmailer/swiftmailer/doc/messages.rst
opt/app/vendor/swiftmailer/swiftmailer/doc/plugins.rst
opt/app/vendor/swiftmailer/swiftmailer/doc/sending.rst
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder/IdnAddressEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder/Utf8AddressEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoderException.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php
@@ -4594,6 +4591,7 @@ opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.ph
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NullContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php
@@ -4670,6 +4668,8 @@ opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/Pl
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/EightBitMimeHandler.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/SmtpUtf8Handler.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php
opt/app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php
@@ -6327,6 +6327,7 @@ opt/app/vendor/symfony/var-dumper/Resources/bin/var-dump-server
opt/app/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css
opt/app/vendor/symfony/var-dumper/Resources/functions/dump.php
opt/app/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js
opt/app/vendor/symfony/var-dumper/Server/Connection.php
opt/app/vendor/symfony/var-dumper/Server/DumpServer.php
opt/app/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php
opt/app/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php
@@ -6351,6 +6352,7 @@ opt/app/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php
opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php
opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php
opt/app/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml
opt/app/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php
opt/app/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php
opt/app/vendor/symfony/var-dumper/VarDumper.php
opt/app/vendor/symfony/var-dumper/composer.json
@@ -7259,6 +7261,14 @@ opt/app/vendor/zendframework/zend-diactoros/src/ServerRequestFactory.php
opt/app/vendor/zendframework/zend-diactoros/src/Stream.php
opt/app/vendor/zendframework/zend-diactoros/src/UploadedFile.php
opt/app/vendor/zendframework/zend-diactoros/src/Uri.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/create_uploaded_file.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/marshal_headers_from_sapi.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/marshal_method_from_sapi.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/marshal_protocol_version_from_sapi.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/marshal_uri_from_sapi.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/normalize_server.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/normalize_uploaded_files.php
opt/app/vendor/zendframework/zend-diactoros/src/functions/parse_cookie_header.php
proc/cpuinfo
sandstorm-http-bridge
sandstorm-http-bridge-config
@@ -7433,14 +7443,17 @@ usr/share/zoneinfo/Africa/Libreville
usr/share/zoneinfo/Africa/Lome
usr/share/zoneinfo/Africa/Luanda
usr/share/zoneinfo/Africa/Lubumbashi
usr/share/zoneinfo/Africa/Lusaka
usr/share/zoneinfo/Africa/Malabo
usr/share/zoneinfo/Africa/Maputo
usr/share/zoneinfo/Africa/Maseru
usr/share/zoneinfo/Africa/Mbabane
usr/share/zoneinfo/Africa/Mogadishu
usr/share/zoneinfo/Africa/Nairobi
usr/share/zoneinfo/Africa/Niamey
usr/share/zoneinfo/Africa/Nouakchott
usr/share/zoneinfo/Africa/Ouagadougou
usr/share/zoneinfo/Africa/Porto-Novo
usr/share/zoneinfo/Africa/Sao_Tome
usr/share/zoneinfo/Africa/Timbuktu
usr/share/zoneinfo/Africa/Tripoli
@@ -7459,7 +7472,9 @@ usr/share/zoneinfo/America/Argentina/Mendoza
usr/share/zoneinfo/America/Aruba
usr/share/zoneinfo/America/Atikokan
usr/share/zoneinfo/America/Atka
usr/share/zoneinfo/America/Cayman
usr/share/zoneinfo/America/Chicago
usr/share/zoneinfo/America/Coral_Harbour
usr/share/zoneinfo/America/Cordoba
usr/share/zoneinfo/America/Curacao
usr/share/zoneinfo/America/Denver
@@ -7480,6 +7495,7 @@ usr/share/zoneinfo/America/Jamaica
usr/share/zoneinfo/America/Kentucky
usr/share/zoneinfo/America/Kentucky/Louisville
usr/share/zoneinfo/America/Knox_IN
usr/share/zoneinfo/America/Kralendijk
usr/share/zoneinfo/America/Los_Angeles
usr/share/zoneinfo/America/Lower_Princes
usr/share/zoneinfo/America/Manaus
@@ -7498,6 +7514,7 @@ usr/share/zoneinfo/America/Porto_Acre
usr/share/zoneinfo/America/Puerto_Rico
usr/share/zoneinfo/America/Regina
usr/share/zoneinfo/America/Rio_Branco
usr/share/zoneinfo/America/Santa_Isabel
usr/share/zoneinfo/America/Santiago
usr/share/zoneinfo/America/Sao_Paulo
usr/share/zoneinfo/America/Shiprock
@@ -7511,6 +7528,7 @@ usr/share/zoneinfo/America/Tijuana
usr/share/zoneinfo/America/Toronto
usr/share/zoneinfo/America/Tortola
usr/share/zoneinfo/America/Vancouver
usr/share/zoneinfo/America/Virgin
usr/share/zoneinfo/America/Whitehorse
usr/share/zoneinfo/America/Winnipeg
usr/share/zoneinfo/Antarctica
@@ -7521,8 +7539,10 @@ usr/share/zoneinfo/Asia
usr/share/zoneinfo/Asia/Aden
usr/share/zoneinfo/Asia/Ashgabat
usr/share/zoneinfo/Asia/Bangkok
usr/share/zoneinfo/Asia/Calcutta
usr/share/zoneinfo/Asia/Chongqing
usr/share/zoneinfo/Asia/Chungking
usr/share/zoneinfo/Asia/Dacca
usr/share/zoneinfo/Asia/Dhaka
usr/share/zoneinfo/Asia/Dubai
usr/share/zoneinfo/Asia/Harbin
@@ -7530,24 +7550,31 @@ usr/share/zoneinfo/Asia/Ho_Chi_Minh
usr/share/zoneinfo/Asia/Hong_Kong
usr/share/zoneinfo/Asia/Istanbul
usr/share/zoneinfo/Asia/Jerusalem
usr/share/zoneinfo/Asia/Kashgar
usr/share/zoneinfo/Asia/Kathmandu
usr/share/zoneinfo/Asia/Katmandu
usr/share/zoneinfo/Asia/Kolkata
usr/share/zoneinfo/Asia/Macao
usr/share/zoneinfo/Asia/Macau
usr/share/zoneinfo/Asia/Makassar
usr/share/zoneinfo/Asia/Nicosia
usr/share/zoneinfo/Asia/Phnom_Penh
usr/share/zoneinfo/Asia/Qatar
usr/share/zoneinfo/Asia/Riyadh
usr/share/zoneinfo/Asia/Saigon
usr/share/zoneinfo/Asia/Seoul
usr/share/zoneinfo/Asia/Shanghai
usr/share/zoneinfo/Asia/Singapore
usr/share/zoneinfo/Asia/Taipei
usr/share/zoneinfo/Asia/Tehran
usr/share/zoneinfo/Asia/Tel_Aviv
usr/share/zoneinfo/Asia/Thimbu
usr/share/zoneinfo/Asia/Thimphu
usr/share/zoneinfo/Asia/Tokyo
usr/share/zoneinfo/Asia/Ulaanbaatar
usr/share/zoneinfo/Asia/Ulan_Bator
usr/share/zoneinfo/Asia/Urumqi
usr/share/zoneinfo/Asia/Yangon
usr/share/zoneinfo/Atlantic
usr/share/zoneinfo/Atlantic/Faroe
usr/share/zoneinfo/Atlantic/Jan_Mayen
@@ -7560,16 +7587,21 @@ usr/share/zoneinfo/Australia/Broken_Hill
usr/share/zoneinfo/Australia/Canberra
usr/share/zoneinfo/Australia/Darwin
usr/share/zoneinfo/Australia/Hobart
usr/share/zoneinfo/Australia/LHI
usr/share/zoneinfo/Australia/Lord_Howe
usr/share/zoneinfo/Australia/Melbourne
usr/share/zoneinfo/Australia/NSW
usr/share/zoneinfo/Australia/North
usr/share/zoneinfo/Australia/Perth
usr/share/zoneinfo/Australia/Queensland
usr/share/zoneinfo/Australia/Sydney
usr/share/zoneinfo/Australia/West
usr/share/zoneinfo/Brazil
usr/share/zoneinfo/Canada
usr/share/zoneinfo/Canada/Atlantic
usr/share/zoneinfo/Canada/East-Saskatchewan
usr/share/zoneinfo/Canada/Saskatchewan
usr/share/zoneinfo/Chile
usr/share/zoneinfo/Chile/EasterIsland
usr/share/zoneinfo/Etc
usr/share/zoneinfo/Etc/GMT
usr/share/zoneinfo/Etc/GMT+0
@@ -7583,6 +7615,7 @@ usr/share/zoneinfo/Etc/Zulu
usr/share/zoneinfo/Europe
usr/share/zoneinfo/Europe/Belfast
usr/share/zoneinfo/Europe/Belgrade
usr/share/zoneinfo/Europe/Bratislava
usr/share/zoneinfo/Europe/Busingen
usr/share/zoneinfo/Europe/Chisinau
usr/share/zoneinfo/Europe/Dublin
@@ -7594,25 +7627,37 @@ usr/share/zoneinfo/Europe/Jersey
usr/share/zoneinfo/Europe/Lisbon
usr/share/zoneinfo/Europe/Ljubljana
usr/share/zoneinfo/Europe/London
usr/share/zoneinfo/Europe/Mariehamn
usr/share/zoneinfo/Europe/Moscow
usr/share/zoneinfo/Europe/Nicosia
usr/share/zoneinfo/Europe/Oslo
usr/share/zoneinfo/Europe/Podgorica
usr/share/zoneinfo/Europe/Prague
usr/share/zoneinfo/Europe/Rome
usr/share/zoneinfo/Europe/San_Marino
usr/share/zoneinfo/Europe/Sarajevo
usr/share/zoneinfo/Europe/Skopje
usr/share/zoneinfo/Europe/Tiraspol
usr/share/zoneinfo/Europe/Vaduz
usr/share/zoneinfo/Europe/Vatican
usr/share/zoneinfo/Europe/Warsaw
usr/share/zoneinfo/Europe/Zagreb
usr/share/zoneinfo/Europe/Zurich
usr/share/zoneinfo/GB
usr/share/zoneinfo/GB-Eire
usr/share/zoneinfo/GMT
usr/share/zoneinfo/GMT+0
usr/share/zoneinfo/GMT-0
usr/share/zoneinfo/GMT0
usr/share/zoneinfo/Greenwich
usr/share/zoneinfo/Indian
usr/share/zoneinfo/Indian/Antananarivo
usr/share/zoneinfo/Indian/Comoro
usr/share/zoneinfo/Indian/Mayotte
usr/share/zoneinfo/Mexico
usr/share/zoneinfo/Mexico/BajaNorte
usr/share/zoneinfo/Mexico/BajaSur
usr/share/zoneinfo/Mexico/General
usr/share/zoneinfo/Pacific
usr/share/zoneinfo/Pacific/Auckland
usr/share/zoneinfo/Pacific/Chatham
@@ -7629,18 +7674,35 @@ usr/share/zoneinfo/Pacific/Pitcairn
usr/share/zoneinfo/Pacific/Pohnpei
usr/share/zoneinfo/Pacific/Samoa
usr/share/zoneinfo/Pacific/Truk
usr/share/zoneinfo/Pacific/Yap
usr/share/zoneinfo/SystemV
usr/share/zoneinfo/SystemV/AST4
usr/share/zoneinfo/SystemV/AST4ADT
usr/share/zoneinfo/SystemV/CST6
usr/share/zoneinfo/SystemV/CST6CDT
usr/share/zoneinfo/SystemV/EST5
usr/share/zoneinfo/SystemV/EST5EDT
usr/share/zoneinfo/SystemV/HST10
usr/share/zoneinfo/SystemV/MST7
usr/share/zoneinfo/SystemV/MST7MDT
usr/share/zoneinfo/SystemV/PST8
usr/share/zoneinfo/SystemV/PST8PDT
usr/share/zoneinfo/SystemV/YST9
usr/share/zoneinfo/SystemV/YST9YDT
usr/share/zoneinfo/US
usr/share/zoneinfo/US/Alaska
usr/share/zoneinfo/US/Aleutian
usr/share/zoneinfo/US/Arizona
usr/share/zoneinfo/US/Central
usr/share/zoneinfo/US/East-Indiana
usr/share/zoneinfo/US/Eastern
usr/share/zoneinfo/US/Hawaii
usr/share/zoneinfo/US/Indiana-Starke
usr/share/zoneinfo/US/Michigan
usr/share/zoneinfo/US/Mountain
usr/share/zoneinfo/US/Pacific
usr/share/zoneinfo/US/Pacific-New
usr/share/zoneinfo/US/Samoa
usr/share/zoneinfo/UTC
usr/share/zoneinfo/Universal
usr/share/zoneinfo/Zulu

View File

@@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
manifest = (
appTitle = (defaultText = "Firefly III"),
appVersion = 14,
appMarketingVersion = (defaultText = "4.7.5.1"),
appVersion = 15,
appMarketingVersion = (defaultText = "4.7.5.2"),
actions = [
# Define your "new document" handlers here.

View File

@@ -14,10 +14,7 @@ apt-get install -y python-software-properties software-properties-common
# install all languages
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# es_ES.UTF-8 UTF-8/es_ES.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# id_ID.UTF-8 UTF-8/id_ID.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# it_IT.UTF-8 UTF-8/it_IT.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# pl_PL.UTF-8 UTF-8/pl_PL.UTF-8 UTF-8/g' /etc/locale.gen
@@ -35,7 +32,7 @@ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E9C74FEEA2098A6E
add-apt-repository "deb http://packages.dotdeb.org jessie all"
# add another repos
apt-get install apt-transport-https lsb-release ca-certificates
apt-get install -y apt-transport-https lsb-release ca-certificates
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list

View File

@@ -59,7 +59,7 @@ RUN (crontab -l ; echo "* * * * * root $FIREFLY_PATH/artisan schedule:run >> /va
RUN docker-php-ext-install -j$(nproc) curl gd intl json readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql
# Generate locales supported by Firefly III
RUN echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8pt_BR.UTF-8 UTF-8ru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\n\n" > /etc/locale.gen && locale-gen
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\n\n" > /etc/locale.gen && locale-gen
# copy Apache config to correct spot.
COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf

View File

@@ -232,4 +232,4 @@ class AttachmentController extends Controller
return response()->json([], 204);
}
}
}

View File

@@ -187,4 +187,4 @@ class AvailableBudgetController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -185,4 +185,4 @@ class BudgetController extends Controller
}
}
}

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon;
use FireflyIII\Api\V1\Requests\BudgetLimitRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\BudgetLimit;
@@ -34,13 +34,13 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Log;
/**
* Class BudgetLimitController.
@@ -95,34 +95,17 @@ class BudgetLimitController extends Controller
{
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$start = null;
$end = null;
$budgetId = (int)($request->get('budget_id') ?? 0);
$budget = $this->repository->findNull($budgetId);
$this->parameters->set('budget_id', $budgetId);
try {
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$this->parameters->set('start', $start->format('Y-m-d'));
} catch (InvalidArgumentException $e) {
Log::debug(sprintf('Invalid date: %s', $e->getMessage()));
}
try {
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$this->parameters->set('end', $end->format('Y-m-d'));
} catch (InvalidArgumentException $e) {
Log::debug(sprintf('Invalid date: %s', $e->getMessage()));
}
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
$this->parameters->set('budget_id', $budgetId);
$collection = new Collection;
if (null === $budget) {
$collection = $this->repository->getAllBudgetLimits($start, $end);
$collection = $this->repository->getAllBudgetLimits($this->parameters->get('start'), $this->parameters->get('end'));
}
if (null !== $budget) {
$collection = $this->repository->getBudgetLimits($budget, $start, $end);
$collection = $this->repository->getBudgetLimits($budget, $this->parameters->get('start'), $this->parameters->get('end'));
}
$count = $collection->count();
@@ -212,4 +195,4 @@ class BudgetLimitController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -185,4 +185,4 @@ class CategoryController extends Controller
}
}
}

View File

@@ -132,4 +132,4 @@ class ConfigurationController extends Controller
return $data;
}
}
}

View File

@@ -108,4 +108,4 @@ class CurrencyExchangeRateController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -214,4 +214,4 @@ class JournalLinkController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -207,4 +207,4 @@ class LinkTypeController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -185,4 +185,4 @@ class PiggyBankController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -34,7 +34,6 @@ use League\Fractal\Manager;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Preferences;
/**
*
@@ -60,7 +59,7 @@ class PreferenceController extends Controller
];
$preferences = new Collection;
foreach ($available as $name) {
$pref = Preferences::getForUser($user, $name);
$pref = app('preferences')->getForUser($user, $name);
if (null !== $pref) {
$preferences->push($pref);
}
@@ -130,7 +129,7 @@ class PreferenceController extends Controller
$newValue = 1 === (int)$data['data'];
break;
}
$result = Preferences::set($preference->name, $newValue);
$result = app('preferences')->set($preference->name, $newValue);
// create some objects:
$manager = new Manager;
@@ -143,4 +142,4 @@ class PreferenceController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -177,4 +177,4 @@ class RecurrenceController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -175,4 +175,4 @@ class RuleController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -176,4 +176,4 @@ class RuleGroupController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}
}

View File

@@ -94,4 +94,4 @@ class AttachmentRequest extends Request
return $rules;
}
}
}

View File

@@ -55,7 +55,6 @@ class AvailableBudgetRequest extends Request
}
/**
* TODO must also accept currency code.
* The rules that the incoming request must be matched against.
*
* @return array
@@ -73,4 +72,4 @@ class AvailableBudgetRequest extends Request
}
}
}

View File

@@ -115,7 +115,7 @@ class BillRequest extends Request
$min = (float)($data['amount_min'] ?? 0);
$max = (float)($data['amount_max'] ?? 0);
if ($min > $max) {
$validator->errors()->add('amount_min', trans('validation.amount_min_over_max'));
$validator->errors()->add('amount_min', (string)trans('validation.amount_min_over_max'));
}
}
);

View File

@@ -80,4 +80,4 @@ class BudgetLimitRequest extends Request
return $rules;
}
}
}

View File

@@ -79,4 +79,4 @@ class BudgetRequest extends Request
return $rules;
}
}
}

View File

@@ -78,4 +78,4 @@ class CategoryRequest extends Request
return $rules;
}
}
}

View File

@@ -57,8 +57,6 @@ class JournalLinkRequest extends Request
}
/**
* TODO include link-type name as optional parameter.
* TODO be consistent and remove notes from this object.
*
* The rules that the incoming request must be matched against.
*
@@ -74,4 +72,4 @@ class JournalLinkRequest extends Request
];
}
}
}

View File

@@ -71,7 +71,6 @@ class LinkTypeRequest extends Request
'outward' => 'required|unique:link_types,outward|min:1|different:inward',
'inward' => 'required|unique:link_types,inward|min:1|different:outward',
];
// Rule::unique('users')->ignore($user->id),
switch ($this->method()) {
@@ -89,4 +88,4 @@ class LinkTypeRequest extends Request
return $rules;
}
}
}

View File

@@ -93,4 +93,4 @@ class PiggyBankRequest extends Request
return $rules;
}
}
}

View File

@@ -66,4 +66,4 @@ class PreferenceRequest extends Request
];
}
}
}

View File

@@ -84,8 +84,7 @@ class RecurrenceRequest extends Request
*/
public function rules(): array
{
$today = new Carbon;
$today->addDay();
$today = Carbon::create()->addDay();
return [
'type' => 'required|in:withdrawal,transfer,deposit',
@@ -201,4 +200,4 @@ class RecurrenceRequest extends Request
return $return;
}
}
}

View File

@@ -82,4 +82,4 @@ class RuleGroupRequest extends Request
return $rules;
}
}
}

View File

@@ -141,7 +141,7 @@ class RuleRequest extends Request
$repetitions = $data['rule-actions'] ?? [];
// need at least one transaction
if (0 === \count($repetitions)) {
$validator->errors()->add('title', trans('validation.at_least_one_action'));
$validator->errors()->add('title', (string)trans('validation.at_least_one_action'));
}
}
@@ -156,7 +156,7 @@ class RuleRequest extends Request
$repetitions = $data['rule-triggers'] ?? [];
// need at least one transaction
if (0 === \count($repetitions)) {
$validator->errors()->add('title', trans('validation.at_least_one_trigger'));
$validator->errors()->add('title', (string)trans('validation.at_least_one_trigger'));
}
}
}
}

View File

@@ -34,7 +34,6 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Console\Command;
use Log;
use Preferences;
/**
* Class CreateImport.
@@ -232,7 +231,7 @@ class CreateImport extends Command
}
}
// clear cache for user:
Preferences::setForUser($user, 'lastActivity', microtime());
app('preferences')->setForUser($user, 'lastActivity', microtime());
return 0;
}
@@ -266,8 +265,8 @@ class CreateImport extends Command
*/
private function validArguments(): bool
{
$file = $this->argument('file');
$configuration = $this->argument('configuration');
$file = (string)$this->argument('file');
$configuration = (string)$this->argument('configuration');
$cwd = getcwd();
$validTypes = config('import.options.file.import_formats');
$type = strtolower($this->option('type'));

View File

@@ -63,9 +63,9 @@ class DecryptAttachment extends Command
$repository = app(AttachmentRepositoryInterface::class);
$attachmentId = (int)$this->argument('id');
$attachment = $repository->findWithoutUser($attachmentId);
$attachmentName = trim($this->argument('name'));
$storagePath = realpath(trim($this->argument('directory')));
if (null === $attachment->id) {
$attachmentName = trim((string)$this->argument('name'));
$storagePath = realpath(trim((string)$this->argument('directory')));
if (null === $attachment) {
$this->error(sprintf('No attachment with id #%d', $attachmentId));
Log::error(sprintf('DecryptAttachment: No attachment with id #%d', $attachmentId));

View File

@@ -59,20 +59,21 @@ class Import extends Command
*
* @throws FireflyException
*/
public function handle(): void
public function handle(): int
{
Log::debug('Start start-import command');
$jobKey = $this->argument('key');
$jobKey = (string)$this->argument('key');
/** @var ImportJob $job */
$job = ImportJob::where('key', $jobKey)->first();
if (null === $job) {
$this->errorLine(sprintf('No job found with key "%s"', $jobKey));
return;
return 1;
}
if (!$this->isValid($job)) {
$this->errorLine('Job is not valid for some reason. Exit.');
return;
return 1;
}
$this->infoLine(sprintf('Going to import job with key "%s" of type "%s"', $job->key, $job->file_type));
@@ -106,6 +107,8 @@ class Import extends Command
}
$this->infoLine(sprintf('The import has finished. %d transactions have been imported.', $count));
return 0;
}
/**

View File

@@ -54,7 +54,7 @@ class ScanAttachments extends Command
/**
* Execute the console command.
*/
public function handle(): void
public function handle(): int
{
$attachments = Attachment::get();
$disk = Storage::disk('upload');
@@ -82,5 +82,6 @@ class ScanAttachments extends Command
$attachment->save();
$this->line(sprintf('Fixed attachment #%d', $attachment->id));
}
return 0;
}
}

View File

@@ -54,7 +54,6 @@ use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Schema;
use UnexpectedValueException;
@@ -83,7 +82,7 @@ class UpgradeDatabase extends Command
/**
* Execute the console command.
*/
public function handle(): void
public function handle(): int
{
$this->setTransactionIdentifier();
$this->updateAccountCurrencies();
@@ -97,6 +96,8 @@ class UpgradeDatabase extends Command
$this->migrateBillsToRules();
$this->info('Firefly III database is up to date.');
return 0;
}
/**
@@ -110,10 +111,10 @@ class UpgradeDatabase extends Command
{
foreach (User::get() as $user) {
/** @var Preference $lang */
$lang = Preferences::getForUser($user, 'language', 'en_US');
$lang = app('preferences')->getForUser($user, 'language', 'en_US');
$groupName = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data);
$ruleGroup = $user->ruleGroups()->where('title', $groupName)->first();
$currencyPreference = Preferences::getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR'));
$currencyPreference = app('preferences')->getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR'));
if (null === $currencyPreference) {
$this->error('User has no currency preference. Impossible.');
@@ -198,7 +199,7 @@ class UpgradeDatabase extends Command
[
'rule_id' => $rule->id,
'trigger_type' => 'amount_more',
'trigger_value' => round($bill->amount_min, $currency->decimal_places),
'trigger_value' => round((float)$bill->amount_min, $currency->decimal_places),
'active' => 1,
'stop_processing' => 0,
'order' => 4,
@@ -210,7 +211,7 @@ class UpgradeDatabase extends Command
[
'rule_id' => $rule->id,
'trigger_type' => 'amount_exactly',
'trigger_value' => round($bill->amount_min, $currency->decimal_places),
'trigger_value' => round((float)$bill->amount_min, $currency->decimal_places),
'active' => 1,
'stop_processing' => 0,
'order' => 3,
@@ -294,7 +295,7 @@ class UpgradeDatabase extends Command
function (Account $account) use ($repository) {
$repository->setUser($account->user);
// get users preference, fall back to system pref.
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
$defaultCurrencyCode = app('preferences')->getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
$accountCurrency = (int)$repository->getMetaValue($account, 'currency_id');
$openingBalance = $account->getOpeningBalance();

View File

@@ -46,14 +46,15 @@ class UpgradeFireflyInstructions extends Command
/**
* Execute the console command.
*/
public function handle(): void
public function handle(): int
{
if ('update' === $this->argument('task')) {
if ('update' === (string)$this->argument('task')) {
$this->updateInstructions();
}
if ('install' === $this->argument('task')) {
if ('install' === (string)$this->argument('task')) {
$this->installInstructions();
}
return 0;
}
/**

View File

@@ -47,7 +47,7 @@ class UseEncryption extends Command
/**
* Execute the console command.
*/
public function handle(): void
public function handle(): int
{
if (true === config('firefly.encryption')) {
$this->info('Firefly III configuration calls for encrypted data.');
@@ -62,6 +62,7 @@ class UseEncryption extends Command
$this->handleObjects('Category', 'name', 'encrypted');
$this->handleObjects('PiggyBank', 'name', 'encrypted');
$this->handleObjects('TransactionJournal', 'description', 'encrypted');
return 0;
}
/**

View File

@@ -25,7 +25,6 @@ namespace FireflyIII\Console\Commands;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Log;
use Preferences;
/**
* Trait VerifiesAccessToken.
@@ -61,7 +60,7 @@ trait VerifiesAccessToken
return false;
}
$accessToken = Preferences::getForUser($user, 'access_token', null);
$accessToken = app('preferences')->getForUser($user, 'access_token', null);
if (null === $accessToken) {
Log::error(sprintf('User #%d has no access token, so cannot access command line options.', $userId));

View File

@@ -42,7 +42,6 @@ use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Builder;
use Log;
use Preferences;
use Schema;
use stdClass;
@@ -70,11 +69,11 @@ class VerifyDatabase extends Command
/**
* Execute the console command.
*/
public function handle(): void
public function handle(): int
{
// if table does not exist, return false
if (!Schema::hasTable('users')) {
return;
return 1;
}
$this->reportEmptyBudgets();
@@ -95,6 +94,8 @@ class VerifyDatabase extends Command
$this->fixDoubleAmounts();
$this->fixBadMeta();
$this->removeBills();
return 0;
}
/**
@@ -106,10 +107,10 @@ class VerifyDatabase extends Command
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$pref = Preferences::getForUser($user, 'access_token', null);
$pref = app('preferences')->getForUser($user, 'access_token', null);
if (null === $pref) {
$token = $user->generateAccessToken();
Preferences::setForUser($user, 'access_token', $token);
app('preferences')->setForUser($user, 'access_token', $token);
$this->line(sprintf('Generated access token for user %s', $user->email));
++$count;
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* RequestedReportOnJournals.php
* Copyright (c) 2018 thegrumpydictator@gmail.com

View File

@@ -31,7 +31,7 @@ use Exception;
use FireflyIII\Jobs\MailError;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\ValidationException as LaravelValidationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -54,7 +54,7 @@ class Handler extends ExceptionHandler
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ValidationException && $request->expectsJson()) {
if ($exception instanceof LaravelValidationException && $request->expectsJson()) {
// ignore it: controller will handle it.
return parent::render($request, $exception);
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Export\Collector\AttachmentCollector;
use FireflyIII\Export\Collector\UploadCollector;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Export\Exporter\ExporterInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\AccountMeta;
@@ -220,6 +221,7 @@ class ExpandedProcessor implements ProcessorInterface
public function exportJournals(): bool
{
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
/** @var ExporterInterface $exporter */
$exporter = app($exporterClass);
$exporter->setJob($this->job);
$exporter->setEntries($this->exportEntries);

View File

@@ -189,7 +189,7 @@ class AccountFactory
}
if (null === $result) {
/** @var string $type */
$type = (string)config('firefly.accountTypeByIdentifier.' . (string)$accountType);
$type = (string)config('firefly.accountTypeByIdentifier.' . $accountType);
$result = AccountType::whereType($type)->first();
if (null === $result && null !== $accountType) {
// try as full name:

View File

@@ -24,13 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use Exception;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use Log;
/**
* Class AccountMetaFactory
*/
class AccountMetaFactory
{
/**
* @param array $data
*
@@ -41,4 +45,45 @@ class AccountMetaFactory
return AccountMeta::create($data);
}
/**
* Create update or delete meta data.
*
* @param Account $account
* @param string $field
* @param string $value
*
* @return AccountMeta|null
*/
public function crud(Account $account, string $field, string $value): ?AccountMeta
{
/** @var AccountMeta $entry */
$entry = $account->accountMeta()->where('name', $field)->first();
// must not be an empty string:
if ('' !== $value) {
// if $data has field and $entry is null, create new one:
if (null === $entry) {
Log::debug(sprintf('Created meta-field "%s":"%s" for account #%d ("%s") ', $field, $value, $account->id, $account->name));
$this->create(['account_id' => $account->id, 'name' => $field, 'data' => $value]);
}
// if $data has field and $entry is not null, update $entry:
if (null !== $entry) {
$entry->data = $value;
$entry->save();
Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name));
}
}
if ('' === $value && null !== $entry && isset($data[$field])) {
try {
$entry->delete();
} catch (Exception $e) {
Log::debug(sprintf('Could not delete entry: %s', $e->getMessage()));
}
}
return $entry;
}
}

View File

@@ -76,4 +76,4 @@ class AttachmentFactory
$this->user = $user;
}
}
}

View File

@@ -93,4 +93,4 @@ class RecurrenceFactory
$this->user = $user;
}
}
}

View File

@@ -43,7 +43,6 @@ class TransactionCurrencyFactory
*/
public function create(array $data): ?TransactionCurrency
{
$result = null;
try {
/** @var TransactionCurrency $currency */
$result = TransactionCurrency::create(
@@ -55,6 +54,7 @@ class TransactionCurrencyFactory
]
);
} catch (QueryException $e) {
$result = null;
Log::error(sprintf('Could not create new currency: %s', $e->getMessage()));
}

View File

@@ -113,6 +113,10 @@ class TransactionFactory
$destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']);
if (null === $sourceAccount || null === $destinationAccount) {
$debugData = $data;
$debugData['source_type'] = $sourceType;
$debugData['dest_type'] = $destinationType;
Log::error('Info about source/dest:', $debugData);
throw new FireflyException('Could not determine source or destination account.');
}

View File

@@ -104,7 +104,7 @@ class TransactionJournalFactory
// store date meta fields (if present):
$fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date',
'due_date', 'recurrence_id', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'importHashV2',
'external_id'];
'external_id','sepa-batch-id'];
foreach ($fields as $field) {
$this->storeMeta($journal, $data, $field);

View File

@@ -51,6 +51,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
* Generates the report.
*
* @return string
* @throws FireflyException
* @throws \Throwable
*/
public function generate(): string

View File

@@ -29,6 +29,8 @@ use Illuminate\Support\Collection;
/**
* Class Support.
* @method Collection getExpenses()
* @method Collection getIncome()
*/
class Support
{

View File

@@ -71,7 +71,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
*
* @return string
* @throws \Throwable
* @throws \Throwable
*/
public function generate(): string
{

View File

@@ -74,4 +74,4 @@ class APIEventHandler
}
}
}

View File

@@ -63,4 +63,4 @@ class AutomationHandler
// @codeCoverageIgnoreEnd
return true;
}
}
}

View File

@@ -36,7 +36,6 @@ use FireflyIII\User;
use Illuminate\Auth\Events\Login;
use Log;
use Mail;
use Preferences;
/**
* Class UserEventHandler.
@@ -138,7 +137,7 @@ class UserEventHandler
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = Preferences::getForUser($user, 'email_change_confirm_token', 'invalid');
$token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid');
$uri = route('profile.confirm-email-change', [$token->data]);
try {
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
@@ -164,7 +163,7 @@ class UserEventHandler
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = Preferences::getForUser($user, 'email_change_undo_token', 'invalid');
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$uri = route('profile.undo-email-change', [$token->data, hash('sha256', $oldEmail)]);
try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));

View File

@@ -24,14 +24,14 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use Carbon\Carbon;
use FireflyConfig;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Update\UpdateTrait;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Services\Github\Object\Release;
use FireflyIII\Services\Github\Request\UpdateRequest;
use FireflyIII\User;
use Log;
@@ -66,7 +66,6 @@ class VersionCheckEventHandler
return;
}
$permission = FireflyConfig::get('permission_update_check', -1);
$lastCheckTime = FireflyConfig::get('last_update_check', time());
$now = time();
$diff = $now - $lastCheckTime->data;
@@ -80,16 +79,9 @@ class VersionCheckEventHandler
// last check time was more than a week ago.
Log::debug('Have not checked for a new version in a week!');
// have actual permission?
if ($permission->data === -1) {
// never asked before.
//session()->flash('info', (string)trans('firefly.check_for_updates_permission', ['link' => route('admin.update-check')]));
//return;
}
$latestRelease = $this->getLatestRelease();
$versionCheck = $this->versionCheck($latestRelease);
$resultString = $this->parseResult($latestRelease, $versionCheck);
$resultString = $this->parseResult($versionCheck, $latestRelease);
if (0 !== $versionCheck && '' !== $resultString) {
// flash info
session()->flash('info', $resultString);

View File

@@ -77,11 +77,12 @@ class AttachmentHelper implements AttachmentHelperInterface
*/
public function getAttachmentContent(Attachment $attachment): string
{
$content = '';
try {
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('at-%d.data', $attachment->id)));
} catch (DecryptException|FileNotFoundException $e) {
Log::error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage()));
$content = '';
}
return $content;
@@ -166,7 +167,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$attachment->md5 = md5_file($path);
$attachment->mime = $mime;
$attachment->size = \strlen($content);
$attachment->uploaded = 1;
$attachment->uploaded = true;
$attachment->save();
return true;
@@ -249,7 +250,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$attachment->filename = $file->getClientOriginalName();
$attachment->mime = $file->getMimeType();
$attachment->size = $file->getSize();
$attachment->uploaded = 0;
$attachment->uploaded = false;
$attachment->save();
Log::debug('Created attachment:', $attachment->toArray());
@@ -262,7 +263,7 @@ class AttachmentHelper implements AttachmentHelperInterface
// store it:
$this->uploadDisk->put($attachment->fileName(), $encrypted);
$attachment->uploaded = 1; // update attachment
$attachment->uploaded = true; // update attachment
$attachment->save();
$this->attachments->push($attachment);

View File

@@ -359,7 +359,7 @@ class MetaPieChart implements MetaPieChartInterface
$repository->setUser($this->user);
foreach ($array as $objectId => $amount) {
if (!isset($names[$objectId])) {
$object = $repository->find((int)$objectId);
$object = $repository->findNull((int)$objectId);
$names[$objectId] = $object->name ?? $object->tag;
}
$amount = app('steam')->positive($amount);

View File

@@ -51,7 +51,6 @@ use Illuminate\Support\Collection;
use Log;
/**
* TODO rename references to journals to transactions
* Maybe this is a good idea after all...
*
* Class JournalCollector

View File

@@ -82,7 +82,6 @@ interface JournalCollectorInterface
/**
* Get all journals.
* TODO rename me.
*
* @return Collection
*/

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Helpers;
use Carbon\Carbon;
use Preferences;
/**
* Class FiscalHelper.
@@ -38,7 +37,7 @@ class FiscalHelper implements FiscalHelperInterface
*/
public function __construct()
{
$this->useCustomFiscalYear = Preferences::get('customFiscalYear', false)->data;
$this->useCustomFiscalYear = app('preferences')->get('customFiscalYear', false)->data;
}
/**
@@ -72,7 +71,7 @@ class FiscalHelper implements FiscalHelperInterface
// get start mm-dd. Then create a start date in the year passed.
$startDate = clone $date;
if (true === $this->useCustomFiscalYear) {
$prefStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$prefStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data;
[$mth, $day] = explode('-', $prefStartStr);
$startDate->month((int)$mth)->day((int)$day);

View File

@@ -76,7 +76,8 @@ class Help implements HelpInterface
$statusCode = $res->getStatusCode();
$content = trim($res->getBody()->getContents());
} catch (GuzzleException|Exception $e) {
Log::error($e);
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
Log::debug(sprintf('Status code is %d', $statusCode));

View File

@@ -32,7 +32,6 @@ use Log;
/**
* Trait UpdateTrait
*
* @package FireflyIII\Helpers\Update
*/
trait UpdateTrait
{
@@ -67,12 +66,12 @@ trait UpdateTrait
/**
* Parses the version check result in a human readable sentence.
*
* @param Release|null $release
* @param int $versionCheck
* @param Release|null $release
*
* @return string
*/
public function parseResult(Release $release = null, int $versionCheck): string
public function parseResult(int $versionCheck, Release $release = null): string
{
$current = (string)config('firefly.version');
$return = '';
@@ -128,4 +127,4 @@ trait UpdateTrait
return $check;
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
/**
* CreateController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Http\Request;
/**
*
* Class CreateController
*/
class CreateController extends Controller
{
/** @var AccountRepositoryInterface The account repository */
private $repository;
/**
* CreateController constructor.
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Create a new account.
*
* @param Request $request
* @param string|null $what
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create(Request $request, string $what = null)
{
$what = $what ?? 'asset';
$defaultCurrency = app('amount')->getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$subTitle = (string)trans('firefly.make_new_' . $what . '_account');
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
$roles[$role] = (string)trans('firefly.account_role_' . $role);
}
// pre fill some data
$request->session()->flash('preFilled', ['currency_id' => $defaultCurrency->id]);
// put previous url in session if not redirect from store (not "create another").
if (true !== session('accounts.create.fromStore')) {
$this->rememberPreviousUri('accounts.create.uri');
}
$request->session()->forget('accounts.create.fromStore');
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'roles'));
}
/**
* Store the new account.
*
* @param AccountFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function store(AccountFormRequest $request)
{
$data = $request->getAccountData();
$account = $this->repository->store($data);
$request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name]));
app('preferences')->mark();
// update preferences if necessary:
$frontPage = app('preferences')->get('frontPageAccounts', [])->data;
if (AccountType::ASSET === $account->accountType->type && \count($frontPage) > 0) {
// @codeCoverageIgnoreStart
$frontPage[] = $account->id;
app('preferences')->set('frontPageAccounts', $frontPage);
// @codeCoverageIgnoreEnd
}
// redirect to previous URL.
$redirect = redirect($this->getPreviousUri('accounts.create.uri'));
if (1 === (int)$request->get('create_another')) {
// set value so create routine will not overwrite URL:
$request->session()->put('accounts.create.fromStore', true);
$redirect = redirect(route('accounts.create', [$request->input('what')]))->withInput();
}
return $redirect;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* DeleteController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Http\Request;
/**
* Class DeleteController
*/
class DeleteController extends Controller
{
/** @var AccountRepositoryInterface The account repository */
private $repository;
/**
* DeleteController constructor.
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Delete account screen.
*
* @param Account $account
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Account $account)
{
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = (string)trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
$accountList = app('expandedform')->makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
unset($accountList[$account->id]);
// put previous url in session
$this->rememberPreviousUri('accounts.delete.uri');
return view('accounts.delete', compact('account', 'subTitle', 'accountList'));
}
/**
* Delete the account.
*
* @param Request $request
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, Account $account)
{
$type = $account->accountType->type;
$typeName = config('firefly.shortNamesByFullName.' . $type);
$name = $account->name;
$moveTo = $this->repository->findNull((int)$request->get('move_account_before_delete'));
$this->repository->destroy($account, $moveTo);
$request->session()->flash('success', (string)trans('firefly.' . $typeName . '_deleted', ['name' => $name]));
app('preferences')->mark();
return redirect($this->getPreviousUri('accounts.delete.uri'));
}
}

View File

@@ -0,0 +1,151 @@
<?php
/**
* EditController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\Request;
/**
*
* Class EditController
*/
class EditController extends Controller
{
/** @var CurrencyRepositoryInterface The currency repository */
private $currencyRepos;
/** @var AccountRepositoryInterface The account repository */
private $repository;
/**
* EditController constructor.
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Edit account overview.
*
* @param Request $request
* @param Account $account
* @param AccountRepositoryInterface $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function edit(Request $request, Account $account, AccountRepositoryInterface $repository)
{
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = (string)trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
$roles[$role] = (string)trans('firefly.account_role_' . $role);
}
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('accounts.edit.fromUpdate')) {
$this->rememberPreviousUri('accounts.edit.uri');
}
$request->session()->forget('accounts.edit.fromUpdate');
$openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account);
$openingBalanceDate = $repository->getOpeningBalanceDate($account);
$default = app('amount')->getDefaultCurrency();
$currency = $this->currencyRepos->findNull((int)$repository->getMetaValue($account, 'currency_id'));
if (null === $currency) {
$currency = $default;
}
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'accountNumber' => $repository->getMetaValue($account, 'accountNumber'),
'accountRole' => $repository->getMetaValue($account, 'accountRole'),
'ccType' => $repository->getMetaValue($account, 'ccType'),
'ccMonthlyPaymentDate' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'),
'BIC' => $repository->getMetaValue($account, 'BIC'),
'openingBalanceDate' => $openingBalanceDate,
'openingBalance' => $openingBalanceAmount,
'virtualBalance' => $account->virtual_balance,
'currency_id' => $currency->id,
'notes' => $this->repository->getNoteText($account),
'active' => $hasOldInput ? (bool)$request->old('active') : $account->active,
];
$request->session()->flash('preFilled', $preFilled);
return view('accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled'));
}
/**
* Update the account.
*
* @param AccountFormRequest $request
* @param Account $account
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(AccountFormRequest $request, Account $account)
{
$data = $request->getAccountData();
$this->repository->update($account, $data);
$request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name]));
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('accounts.edit.uri'));
if (1 === (int)$request->get('return_to_edit')) {
// set value so edit routine will not overwrite URL:
$request->session()->put('accounts.edit.fromUpdate', true);
$redirect = redirect(route('accounts.edit', [$account->id]))->withInput(['return_to_edit' => 1]);
}
return $redirect;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* IndexController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
/**
*
* Class IndexController
*/
class IndexController extends Controller
{
/** @var AccountRepositoryInterface The account repository */
private $repository;
/**
* IndexController constructor.
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Show list of accounts.
*
* @param Request $request
* @param string $what
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request, string $what)
{
$what = $what ?? 'asset';
$subTitle = (string)trans('firefly.' . $what . '_accounts');
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$types = config('firefly.accountTypesByIdentifier.' . $what);
$collection = $this->repository->getAccountsByType($types);
$total = $collection->count();
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
unset($collection);
/** @var Carbon $start */
$start = clone session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = clone session('end', Carbon::now()->endOfMonth());
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
$activities = app('steam')->getLastActivities($ids);
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances) {
$account->lastActivityDate = $this->isInArray($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);
}
);
// make paginator:
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
$accounts->setPath(route('accounts.index', [$what]));
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}
/**
* Find the ID in a given array. Return '0' of not there (amount).
*
* @param array $array
* @param int $entryId
*
* @return null|mixed
*/
protected function isInArray(array $array, int $entryId)
{
$result = '0';
if (isset($array[$entryId])) {
$result = $array[$entryId];
}
return $result;
}
}

View File

@@ -36,9 +36,7 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Services\Internal\Update\CurrencyUpdateService;
use Log;
use Preferences;
/**
* Class ReconcileController.
@@ -47,15 +45,15 @@ use Preferences;
*/
class ReconcileController extends Controller
{
/** @var CurrencyUpdateService */
/** @var AccountRepositoryInterface The account repository */
private $accountRepos;
/** @var AccountRepositoryInterface */
/** @var CurrencyRepositoryInterface The currency repository */
private $currencyRepos;
/** @var JournalRepositoryInterface */
/** @var JournalRepositoryInterface Journals and transactions overview */
private $repository;
/**
*
* ReconcileController constructor.
*/
public function __construct()
{
@@ -65,7 +63,7 @@ class ReconcileController extends Controller
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(JournalRepositoryInterface::class);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
@@ -76,6 +74,8 @@ class ReconcileController extends Controller
}
/**
* Edit a reconciliation.
*
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
@@ -86,7 +86,7 @@ class ReconcileController extends Controller
return redirect(route('transactions.edit', [$journal->id]));
}
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
$subTitle = (string)trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
// journal related code
$pTransaction = $this->repository->getFirstPosTransaction($journal);
@@ -112,6 +112,8 @@ class ReconcileController extends Controller
}
/**
* Reconciliation overview.
*
* @param Account $account
* @param Carbon|null $start
* @param Carbon|null $end
@@ -127,7 +129,7 @@ class ReconcileController extends Controller
return $this->redirectToOriginalAccount($account);
}
if (AccountType::ASSET !== $account->accountType->type) {
session()->flash('error', trans('firefly.must_be_asset_account'));
session()->flash('error', (string)trans('firefly.must_be_asset_account'));
return redirect(route('accounts.index', [config('firefly.shortNamesByFullName.' . $account->accountType->type)]));
}
@@ -138,14 +140,17 @@ class ReconcileController extends Controller
}
// no start or end:
$range = Preferences::get('viewRange', '1M')->data;
$range = app('preferences')->get('viewRange', '1M')->data;
// get start and end
if (null === $start && null === $end) {
/** @var Carbon $start */
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
}
if (null === $end) {
/** @var Carbon $end */
$end = app('navigation')->endOfPeriod($start, $range);
}
@@ -154,7 +159,7 @@ class ReconcileController extends Controller
$startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places);
$endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$subTitle = trans('firefly.reconcile_account', ['account' => $account->name]);
$subTitle = (string)trans('firefly.reconcile_account', ['account' => $account->name]);
// various links
$transactionsUri = route('accounts.reconcile.transactions', [$account->id, '%start%', '%end%']);
@@ -170,6 +175,8 @@ class ReconcileController extends Controller
}
/**
* Show a single reconciliation.
*
* @param TransactionJournal $journal
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
@@ -195,6 +202,8 @@ class ReconcileController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Submit a new reconciliation.
*
* @param ReconciliationStoreRequest $request
* @param Account $account
* @param Carbon $start
@@ -273,17 +282,22 @@ class ReconcileController extends Controller
}
Log::debug('End of routine.');
app('preferences')->mark();
session()->flash('success', trans('firefly.reconciliation_stored'));
session()->flash('success', (string)trans('firefly.reconciliation_stored'));
return redirect(route('accounts.show', [$account->id]));
}
/**
* Update a reconciliation.
*
* @param ReconciliationUpdateRequest $request
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function update(ReconciliationUpdateRequest $request, TransactionJournal $journal)
{
@@ -291,7 +305,7 @@ class ReconcileController extends Controller
return redirect(route('transactions.show', [$journal->id]));
}
if (0 === bccomp('0', $request->get('amount'))) {
session()->flash('error', trans('firefly.amount_cannot_be_zero'));
session()->flash('error', (string)trans('firefly.amount_cannot_be_zero'));
return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput();
}
@@ -358,6 +372,8 @@ class ReconcileController extends Controller
}
/**
* Redirect user to the original asset account.
*
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector

View File

@@ -0,0 +1,273 @@
<?php
/**
* ShowController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use View;
/**
* Class ShowController
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ShowController extends Controller
{
/** @var CurrencyRepositoryInterface The currency repository */
private $currencyRepos;
/** @var AccountRepositoryInterface The account repository */
private $repository;
/**
* ShowController constructor.
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
return $next($request);
}
);
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Show an account.
*
* @param Request $request
* @param Account $account
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
* @throws FireflyException
*
*/
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account);
}
/** @var Carbon $start */
$start = $start ?? session('start');
/** @var Carbon $end */
$end = $end ?? session('end');
if ($end < $start) {
throw new FireflyException('End is after start!'); // @codeCoverageIgnore
}
$what = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); // used for menu
$today = new Carbon;
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$currencyId = (int)$this->repository->getMetaValue($account, 'currency_id');
$currency = $this->currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$periods = $this->getPeriodOverview($account, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$collector->setRange($start, $end);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
return view(
'accounts.show',
compact('account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri')
);
}
/**
* Show an account.
*
* @param Request $request
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
* @throws FireflyException
*
*/
public function showAll(Request $request, Account $account)
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore
}
$end = new Carbon;
$today = new Carbon;
$start = $this->repository->oldestJournalDate($account);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$currencyId = (int)$this->repository->getMetaValue($account, 'currency_id');
$currency = $this->currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
$subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]);
$periods = new Collection;
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('accounts.show.all', [$account->id]));
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
return view(
'accounts.show',
compact('account', 'showAll', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end')
);
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
* performance reasons.
*
* @param Account $account the account involved
*
* @param Carbon|null $date
*
* @return Collection
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(Account $account, ?Carbon $date): Collection
{
$range = app('preferences')->get('viewRange', '1M')->data;
$start = $this->repository->oldestJournalDate($account);
$end = $date ?? new Carbon;
if ($end < $start) {
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
}
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
// loop dates
foreach ($dates as $currentDate) {
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$earned = (string)$collector->getJournals()->sum('transaction_amount');
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$spent = (string)$collector->getJournals()->sum('transaction_amount');
$dateName = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
/** @noinspection PhpUndefinedMethodInspection */
$entries->push(
[
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'start' => $currentDate['start']->format('Y-m-d'),
'end' => $currentDate['end']->format('Y-m-d'),
]
);
}
$cache->store($entries);
return $entries;
}
/**
* Redirect to the original account.
*
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
private function redirectToOriginalAccount(Account $account)
{
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (null === $transaction) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
}
$journal = $transaction->transactionJournal;
/** @var Transaction $opposingTransaction */
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (null === $opposingTransaction) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore
}
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
}
}

View File

@@ -1,521 +0,0 @@
<?php
/**
* AccountController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Preferences;
use View;
/**
* Class AccountController.
*
*/
class AccountController extends Controller
{
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var AccountRepositoryInterface */
private $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
return $next($request);
}
);
}
/**
* @param Request $request
* @param string|null $what
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create(Request $request, string $what = null)
{
$what = $what ?? 'asset';
$defaultCurrency = app('amount')->getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$subTitle = trans('firefly.make_new_' . $what . '_account');
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
$roles[$role] = (string)trans('firefly.account_role_' . $role);
}
// pre fill some data
$request->session()->flash('preFilled', ['currency_id' => $defaultCurrency->id]);
// put previous url in session if not redirect from store (not "create another").
if (true !== session('accounts.create.fromStore')) {
$this->rememberPreviousUri('accounts.create.uri');
}
$request->session()->forget('accounts.create.fromStore');
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'roles'));
}
/**
* @param Account $account
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Account $account)
{
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
$accountList = ExpandedForm::makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
unset($accountList[$account->id]);
// put previous url in session
$this->rememberPreviousUri('accounts.delete.uri');
return view('accounts.delete', compact('account', 'subTitle', 'accountList'));
}
/**
* @param Request $request
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, Account $account)
{
$type = $account->accountType->type;
$typeName = config('firefly.shortNamesByFullName.' . $type);
$name = $account->name;
$moveTo = $this->repository->findNull((int)$request->get('move_account_before_delete'));
$this->repository->destroy($account, $moveTo);
$request->session()->flash('success', (string)trans('firefly.' . $typeName . '_deleted', ['name' => $name]));
app('preferences')->mark();
return redirect($this->getPreviousUri('accounts.delete.uri'));
}
/**
* @param Request $request
* @param Account $account
* @param AccountRepositoryInterface $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function edit(Request $request, Account $account, AccountRepositoryInterface $repository)
{
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
$roles[$role] = (string)trans('firefly.account_role_' . $role);
}
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('accounts.edit.fromUpdate')) {
$this->rememberPreviousUri('accounts.edit.uri');
}
$request->session()->forget('accounts.edit.fromUpdate');
// pre fill some useful values.
// the opening balance is tricky:
$openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account);
$openingBalanceDate = $repository->getOpeningBalanceDate($account);
$default = app('amount')->getDefaultCurrency();
$currency = $this->currencyRepos->findNull((int)$repository->getMetaValue($account, 'currency_id'));
if (null === $currency) {
$currency = $default;
}
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'accountNumber' => $repository->getMetaValue($account, 'accountNumber'),
'accountRole' => $repository->getMetaValue($account, 'accountRole'),
'ccType' => $repository->getMetaValue($account, 'ccType'),
'ccMonthlyPaymentDate' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'),
'BIC' => $repository->getMetaValue($account, 'BIC'),
'openingBalanceDate' => $openingBalanceDate,
'openingBalance' => $openingBalanceAmount,
'virtualBalance' => $account->virtual_balance,
'currency_id' => $currency->id,
'notes' => '',
'active' => $hasOldInput ? (bool)$request->old('active') : $account->active,
];
/** @var Note $note */
$note = $this->repository->getNote($account);
if (null !== $note) {
$preFilled['notes'] = $note->text;
}
$request->session()->flash('preFilled', $preFilled);
return view('accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled'));
}
/**
* @param Request $request
* @param string $what
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request, string $what)
{
$what = $what ?? 'asset';
$subTitle = trans('firefly.' . $what . '_accounts');
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$types = config('firefly.accountTypesByIdentifier.' . $what);
$collection = $this->repository->getAccountsByType($types);
$total = $collection->count();
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
unset($collection);
/** @var Carbon $start */
$start = clone session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = clone session('end', Carbon::now()->endOfMonth());
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
$activities = app('steam')->getLastActivities($ids);
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances) {
$account->lastActivityDate = $this->isInArray($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);
}
);
// make paginator:
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
$accounts->setPath(route('accounts.index', [$what]));
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Show an account.
*
* @param Request $request
* @param Account $account
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
* @throws FireflyException
*
*/
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account);
}
if (null === $start) {
$start = session('start');
}
if (null === $end) {
$end = session('end');
}
if ($end < $start) {
throw new FireflyException('End is after start!'); // @codeCoverageIgnore
}
$what = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); // used for menu
$today = new Carbon;
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$currencyId = (int)$this->repository->getMetaValue($account, 'currency_id');
$currency = $this->currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$periods = $this->getPeriodOverview($account, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$collector->setRange($start, $end);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
return view(
'accounts.show',
compact('account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri')
);
}
/**
* Show an account.
*
* @param Request $request
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
* @throws FireflyException
*
*/
public function showAll(Request $request, Account $account)
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore
}
$end = new Carbon;
$today = new Carbon;
$start = $this->repository->oldestJournalDate($account);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$currencyId = (int)$this->repository->getMetaValue($account, 'currency_id');
$currency = $this->currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$periods = new Collection;
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('accounts.show.all', [$account->id]));
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
return view(
'accounts.show',
compact('account', 'showAll', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end')
);
}
/**
* @param AccountFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function store(AccountFormRequest $request)
{
$data = $request->getAccountData();
$account = $this->repository->store($data);
$request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name]));
app('preferences')->mark();
// update preferences if necessary:
$frontPage = Preferences::get('frontPageAccounts', [])->data;
if (AccountType::ASSET === $account->accountType->type && \count($frontPage) > 0) {
// @codeCoverageIgnoreStart
$frontPage[] = $account->id;
Preferences::set('frontPageAccounts', $frontPage);
// @codeCoverageIgnoreEnd
}
// redirect to previous URL.
$redirect = redirect($this->getPreviousUri('accounts.create.uri'));
if (1 === (int)$request->get('create_another')) {
// set value so create routine will not overwrite URL:
$request->session()->put('accounts.create.fromStore', true);
$redirect = redirect(route('accounts.create', [$request->input('what')]))->withInput();
}
return $redirect;
}
/**
* @param AccountFormRequest $request
* @param Account $account
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(AccountFormRequest $request, Account $account)
{
$data = $request->getAccountData();
$this->repository->update($account, $data);
$request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name]));
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('accounts.edit.uri'));
if (1 === (int)$request->get('return_to_edit')) {
// set value so edit routine will not overwrite URL:
$request->session()->put('accounts.edit.fromUpdate', true);
$redirect = redirect(route('accounts.edit', [$account->id]))->withInput(['return_to_edit' => 1]);
}
return $redirect;
}
/**
* @param array $array
* @param int $entryId
*
* @return null|mixed
*/
protected function isInArray(array $array, int $entryId)
{
$result = '0';
if (isset($array[$entryId])) {
$result = $array[$entryId];
}
return $result;
}
/**
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
* performance reasons.
*
* @param Account $account the account involved
*
* @param Carbon|null $date
*
* @return Collection
*
*/
private function getPeriodOverview(Account $account, ?Carbon $date): Collection
{
$range = Preferences::get('viewRange', '1M')->data;
$start = $this->repository->oldestJournalDate($account);
$end = $date ?? new Carbon;
if ($end < $start) {
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
}
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
// loop dates
foreach ($dates as $currentDate) {
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$earned = (string)$collector->getJournals()->sum('transaction_amount');
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$spent = (string)$collector->getJournals()->sum('transaction_amount');
$dateName = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
/** @noinspection PhpUndefinedMethodInspection */
$entries->push(
[
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'start' => $currentDate['start']->format('Y-m-d'),
'end' => $currentDate['end']->format('Y-m-d'),
]
);
}
$cache->store($entries);
return $entries;
}
/**
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
private function redirectToOriginalAccount(Account $account)
{
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (null === $transaction) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
}
$journal = $transaction->transactionJournal;
/** @var Transaction $opposingTransaction */
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (null === $opposingTransaction) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore
}
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
}
}

View File

@@ -55,6 +55,8 @@ class ConfigurationController extends Controller
}
/**
* Show configuration index.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
@@ -75,6 +77,8 @@ class ConfigurationController extends Controller
}
/**
* Store new configuration values.
*
* @param ConfigurationRequest $request
*
* @return RedirectResponse

View File

@@ -46,6 +46,8 @@ class HomeController extends Controller
}
/**
* Index of the admin.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
@@ -58,6 +60,8 @@ class HomeController extends Controller
}
/**
* Send a test message to the admin.
*
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector

View File

@@ -36,7 +36,7 @@ use View;
class LinkController extends Controller
{
/**
*
* LinkController constructor.
*/
public function __construct()
{
@@ -54,11 +54,12 @@ class LinkController extends Controller
}
/**
* Make a new link form.
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create()
{
$subTitle = trans('firefly.create_new_link_type');
$subTitle = (string)trans('firefly.create_new_link_type');
$subTitleIcon = 'fa-link';
// put previous url in session if not redirect from store (not "create another").
@@ -70,6 +71,8 @@ class LinkController extends Controller
}
/**
* Delete a link form.
*
* @param Request $request
* @param LinkTypeRepositoryInterface $repository
* @param LinkType $linkType
@@ -84,11 +87,11 @@ class LinkController extends Controller
return redirect(route('admin.links.index'));
}
$subTitle = trans('firefly.delete_link_type', ['name' => $linkType->name]);
$subTitle = (string)trans('firefly.delete_link_type', ['name' => $linkType->name]);
$otherTypes = $repository->get();
$count = $repository->countJournals($linkType);
$moveTo = [];
$moveTo[0] = trans('firefly.do_not_save_connection');
$moveTo[0] = (string)trans('firefly.do_not_save_connection');
/** @var LinkType $otherType */
foreach ($otherTypes as $otherType) {
if ($otherType->id !== $linkType->id) {
@@ -102,6 +105,8 @@ class LinkController extends Controller
}
/**
* Actually destroy the link.
*
* @param Request $request
* @param LinkTypeRepositoryInterface $repository
* @param LinkType $linkType
@@ -121,6 +126,8 @@ class LinkController extends Controller
}
/**
* Edit a link form.
*
* @param Request $request
* @param LinkType $linkType
*
@@ -133,7 +140,7 @@ class LinkController extends Controller
return redirect(route('admin.links.index'));
}
$subTitle = trans('firefly.edit_link_type', ['name' => $linkType->name]);
$subTitle = (string)trans('firefly.edit_link_type', ['name' => $linkType->name]);
$subTitleIcon = 'fa-link';
// put previous url in session if not redirect from store (not "return_to_edit").
@@ -146,13 +153,15 @@ class LinkController extends Controller
}
/**
* Show index of all links.
*
* @param LinkTypeRepositoryInterface $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(LinkTypeRepositoryInterface $repository)
{
$subTitle = trans('firefly.journal_link_configuration');
$subTitle = (string)trans('firefly.journal_link_configuration');
$subTitleIcon = 'fa-link';
$linkTypes = $repository->get();
$linkTypes->each(
@@ -165,13 +174,15 @@ class LinkController extends Controller
}
/**
* Show a single link.
*
* @param LinkType $linkType
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(LinkType $linkType)
{
$subTitle = trans('firefly.overview_for_link', ['name' => $linkType->name]);
$subTitle = (string)trans('firefly.overview_for_link', ['name' => $linkType->name]);
$subTitleIcon = 'fa-link';
$links = $linkType->transactionJournalLinks()->get();
@@ -179,6 +190,8 @@ class LinkController extends Controller
}
/**
* Store the new link.
*
* @param LinkTypeFormRequest $request
* @param LinkTypeRepositoryInterface $repository
*
@@ -206,6 +219,8 @@ class LinkController extends Controller
}
/**
* Update an existing link.
*
* @param LinkTypeFormRequest $request
* @param LinkTypeRepositoryInterface $repository
* @param LinkType $linkType

View File

@@ -56,26 +56,30 @@ class UpdateController extends Controller
}
/**
* Show page with update options.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Psr\Container\NotFoundExceptionInterface
* @throws \Psr\Container\ContainerExceptionInterface
*/
public function index()
{
$subTitle = trans('firefly.update_check_title');
$subTitle = (string)trans('firefly.update_check_title');
$subTitleIcon = 'fa-star';
$permission = app('fireflyconfig')->get('permission_update_check', -1);
$selected = $permission->data;
$options = [
-1 => trans('firefly.updates_ask_me_later'),
0 => trans('firefly.updates_do_not_check'),
1 => trans('firefly.updates_enable_check'),
-1 => (string)trans('firefly.updates_ask_me_later'),
0 => (string)trans('firefly.updates_do_not_check'),
1 => (string)trans('firefly.updates_enable_check'),
];
return view('admin.update.index', compact('subTitle', 'subTitleIcon', 'selected', 'options'));
}
/**
* Post new settings.
*
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
@@ -97,7 +101,7 @@ class UpdateController extends Controller
{
$latestRelease = $this->getLatestRelease();
$versionCheck = $this->versionCheck($latestRelease);
$resultString = $this->parseResult($latestRelease, $versionCheck);
$resultString = $this->parseResult($versionCheck, $latestRelease);
if (0 !== $versionCheck && '' !== $resultString) {
// flash info

View File

@@ -29,7 +29,6 @@ use FireflyIII\Http\Requests\UserFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Log;
use Preferences;
/**
* Class UserController.
@@ -37,7 +36,7 @@ use Preferences;
class UserController extends Controller
{
/**
*
* UserController constructor.
*/
public function __construct()
{
@@ -56,18 +55,22 @@ class UserController extends Controller
}
/**
* Delete a user.
*
* @param User $user
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(User $user)
{
$subTitle = trans('firefly.delete_user', ['email' => $user->email]);
$subTitle = (string)trans('firefly.delete_user', ['email' => $user->email]);
return view('admin.users.delete', compact('user', 'subTitle'));
}
/**
* Destroy a user.
*
* @param User $user
* @param UserRepositoryInterface $repository
*
@@ -82,6 +85,8 @@ class UserController extends Controller
}
/**
* Edit user form.
*
* @param User $user
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -107,6 +112,8 @@ class UserController extends Controller
}
/**
* Show index of user manager.
*
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -121,7 +128,7 @@ class UserController extends Controller
$users->each(
function (User $user) use ($repository) {
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
$preferences = Preferences::getArrayForUser($user, $list);
$preferences = app('preferences')->getArrayForUser($user, $list);
$user->isAdmin = $repository->hasRole($user, 'owner');
$is2faEnabled = 1 === $preferences['twoFactorAuthEnabled'];
$has2faSecret = null !== $preferences['twoFactorAuthSecret'];
@@ -134,6 +141,8 @@ class UserController extends Controller
}
/**
* Show single user.
*
* @param UserRepositoryInterface $repository
* @param User $user
*
@@ -155,6 +164,8 @@ class UserController extends Controller
}
/**
* Update single user.
*
* @param UserFormRequest $request
* @param User $user
* @param UserRepositoryInterface $repository

View File

@@ -36,11 +36,11 @@ use Illuminate\Http\Response as LaravelResponse;
*/
class AttachmentController extends Controller
{
/** @var AttachmentRepositoryInterface */
/** @var AttachmentRepositoryInterface Attachment repository */
private $repository;
/**
*
* AttachmentController constructor.
*/
public function __construct()
{
@@ -50,7 +50,7 @@ class AttachmentController extends Controller
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-paperclip');
app('view')->share('title', trans('firefly.attachments'));
app('view')->share('title', (string)trans('firefly.attachments'));
$this->repository = app(AttachmentRepositoryInterface::class);
return $next($request);
@@ -59,13 +59,15 @@ class AttachmentController extends Controller
}
/**
* Form to delete an attachment.
*
* @param Attachment $attachment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Attachment $attachment)
{
$subTitle = trans('firefly.delete_attachment', ['name' => $attachment->filename]);
$subTitle = (string)trans('firefly.delete_attachment', ['name' => $attachment->filename]);
// put previous url in session
$this->rememberPreviousUri('attachments.delete.uri');
@@ -74,6 +76,8 @@ class AttachmentController extends Controller
}
/**
* Destroy attachment.
*
* @param Request $request
* @param Attachment $attachment
*
@@ -92,6 +96,8 @@ class AttachmentController extends Controller
}
/**
* Download attachment to PC.
*
* @param Attachment $attachment
*
* @return mixed
@@ -123,6 +129,8 @@ class AttachmentController extends Controller
}
/**
* Edit an attachment.
*
* @param Request $request
* @param Attachment $attachment
*
@@ -131,21 +139,24 @@ class AttachmentController extends Controller
public function edit(Request $request, Attachment $attachment)
{
$subTitleIcon = 'fa-pencil';
$subTitle = trans('firefly.edit_attachment', ['name' => $attachment->filename]);
$subTitle = (string)trans('firefly.edit_attachment', ['name' => $attachment->filename]);
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('attachments.edit.fromUpdate')) {
$this->rememberPreviousUri('attachments.edit.uri');
}
$request->session()->forget('attachments.edit.fromUpdate');
$preFilled['notes'] = $this->repository->getNoteText($attachment);
$preFilled = [
'notes' => $this->repository->getNoteText($attachment),
];
$request->session()->flash('preFilled', $preFilled);
return view('attachments.edit', compact('attachment', 'subTitleIcon', 'subTitle'));
}
/**
* Index of all attachments.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
@@ -164,6 +175,8 @@ class AttachmentController extends Controller
}
/**
* Update attachment.
*
* @param AttachmentFormRequest $request
* @param Attachment $attachment
*
@@ -191,6 +204,8 @@ class AttachmentController extends Controller
}
/**
* View attachment in browser.
*
* @param Attachment $attachment
*
* @return LaravelResponse

View File

@@ -36,17 +36,6 @@ use Illuminate\Support\Facades\Password;
*/
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
@@ -75,7 +64,7 @@ class ForgotPasswordController extends Controller
$user = User::where('email', $request->get('email'))->first();
if (null !== $user && $repository->hasRole($user, 'demo')) {
return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
return back()->withErrors(['email' => (string)trans('firefly.cannot_reset_demo_user')]);
}
// We will send the password reset link to this user. Once we have attempted
@@ -93,6 +82,8 @@ class ForgotPasswordController extends Controller
}
/**
* Show form for email recovery.
*
* @codeCoverageIgnore
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View

View File

@@ -32,12 +32,13 @@ use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
/**
* @codeCoverageIgnore
* Class LoginController
*
* This controller handles authenticating users for the application and
* redirecting them to your home screen. The controller uses a trait
* to conveniently provide its functionality to your applications.
*
* @codeCoverageIgnore
*/
class LoginController extends Controller
{
@@ -60,6 +61,8 @@ class LoginController extends Controller
}
/**
* Log in a user.
*
* @param Request $request
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|void

View File

@@ -33,12 +33,13 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
/**
* @codeCoverageIgnore
* Class RegisterController
*
* This controller handles the registration of new users as well as their
* validation and creation. By default this controller uses a trait to
* provide this functionality without requiring any additional code.
*
* @codeCoverageIgnore
*/
class RegisterController extends Controller
{
@@ -87,8 +88,9 @@ class RegisterController extends Controller
session()->flash('success', (string)trans('firefly.registered'));
return $this->registered($request, $user)
?: redirect($this->redirectPath());
$this->registered($request, $user);
return redirect($this->redirectPath());
}
/**

View File

@@ -30,12 +30,13 @@ use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
/**
* @codeCoverageIgnore
* Class ResetPasswordController
*
* This controller is responsible for handling password reset requests
* and uses a simple trait to include this behavior. You're free to
* explore this trait and override any methods you wish to tweak.
*
* @codeCoverageIgnore
*/
class ResetPasswordController extends Controller
{

View File

@@ -29,7 +29,6 @@ use FireflyIII\User;
use Illuminate\Cookie\CookieJar;
use Illuminate\Http\Request;
use Log;
use Preferences;
/**
* Class TwoFactorController.
@@ -37,24 +36,26 @@ use Preferences;
class TwoFactorController extends Controller
{
/**
* Show 2FA screen.
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function index(Request $request)
{
$user = auth()->user();
// to make sure the validator in the next step gets the secret, we push it in session
$secretPreference = Preferences::get('twoFactorAuthSecret', null);
$secretPreference = app('preferences')->get('twoFactorAuthSecret', null);
$secret = null === $secretPreference ? null : $secretPreference->data;
$title = (string)trans('firefly.two_factor_title');
// make sure the user has two factor configured:
$has2FA = Preferences::get('twoFactorAuthEnabled', false)->data;
$has2FA = app('preferences')->get('twoFactorAuthEnabled', false)->data;
if (null === $has2FA || false === $has2FA) {
return redirect(route('index'));
}
@@ -68,6 +69,8 @@ class TwoFactorController extends Controller
}
/**
* What to do if 2FA lost?
*
* @return mixed
*/
public function lostTwoFactor()
@@ -87,6 +90,8 @@ class TwoFactorController extends Controller
}
/**
* Submit 2FA code.
*
* @param TokenFormRequest $request
* @param CookieJar $cookieJar
*

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\BillFormRequest;
@@ -36,25 +37,25 @@ use Illuminate\Support\Collection;
use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\DataArraySerializer;
use Preferences;
use Symfony\Component\HttpFoundation\ParameterBag;
use URL;
use View;
/**
* Class BillController.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class BillController extends Controller
{
/** @var AttachmentHelperInterface Helper for attachments. */
private $attachments;
/** @var BillRepositoryInterface */
/** @var BillRepositoryInterface Bill repository */
private $billRepository;
/** @var RuleGroupRepositoryInterface */
/** @var RuleGroupRepositoryInterface Rule group repository */
private $ruleGroupRepos;
/**
*
* BillController constructor.
*/
public function __construct()
{
@@ -63,11 +64,11 @@ class BillController extends Controller
$maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes(ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize);
View::share('uploadSize', $uploadSize);
app('view')->share('uploadSize', $uploadSize);
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.bills'));
app('view')->share('title', (string)trans('firefly.bills'));
app('view')->share('mainTitleIcon', 'fa-calendar-o');
$this->attachments = app(AttachmentHelperInterface::class);
$this->billRepository = app(BillRepositoryInterface::class);
@@ -79,6 +80,8 @@ class BillController extends Controller
}
/**
* Create a new bill.
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -91,7 +94,7 @@ class BillController extends Controller
foreach ($billPeriods as $current) {
$periods[$current] = strtolower((string)trans('firefly.repeat_freq_' . $current));
}
$subTitle = trans('firefly.create_new_bill');
$subTitle = (string)trans('firefly.create_new_bill');
$defaultCurrency = app('amount')->getDefaultCurrency();
// put previous url in session if not redirect from store (not "create another").
@@ -104,6 +107,8 @@ class BillController extends Controller
}
/**
* Delete a bill.
*
* @param Bill $bill
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -112,12 +117,14 @@ class BillController extends Controller
{
// put previous url in session
$this->rememberPreviousUri('bills.delete.uri');
$subTitle = trans('firefly.delete_bill', ['name' => $bill->name]);
$subTitle = (string)trans('firefly.delete_bill', ['name' => $bill->name]);
return view('bills.delete', compact('bill', 'subTitle'));
}
/**
* Destroy a bill.
*
* @param Request $request
* @param Bill $bill
*
@@ -135,6 +142,8 @@ class BillController extends Controller
}
/**
* Edit a bill.
*
* @param Request $request
* @param Bill $bill
*
@@ -147,10 +156,10 @@ class BillController extends Controller
$billPeriods = config('firefly.bill_periods');
foreach ($billPeriods as $current) {
$periods[$current] = trans('firefly.' . $current);
$periods[$current] = (string)trans('firefly.' . $current);
}
$subTitle = trans('firefly.edit_bill', ['name' => $bill->name]);
$subTitle = (string)trans('firefly.edit_bill', ['name' => $bill->name]);
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('bills.edit.fromUpdate')) {
@@ -158,8 +167,8 @@ class BillController extends Controller
}
$currency = app('amount')->getDefaultCurrency();
$bill->amount_min = round($bill->amount_min, $currency->decimal_places);
$bill->amount_max = round($bill->amount_max, $currency->decimal_places);
$bill->amount_min = round((float)$bill->amount_min, $currency->decimal_places);
$bill->amount_max = round((float)$bill->amount_max, $currency->decimal_places);
$defaultCurrency = app('amount')->getDefaultCurrency();
// code to handle active-checkboxes
@@ -178,13 +187,15 @@ class BillController extends Controller
}
/**
* Show all bills.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
{
$start = session('start');
$end = session('end');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$paginator = $this->billRepository->getPaginator($pageSize);
$parameters = new ParameterBag();
$parameters->set('start', $start);
@@ -218,6 +229,8 @@ class BillController extends Controller
}
/**
* Rescan bills for transactions.
*
* @param Request $request
* @param Bill $bill
*
@@ -253,6 +266,8 @@ class BillController extends Controller
}
/**
* Show a bill.
*
* @param Request $request
* @param Bill $bill
*
@@ -263,11 +278,13 @@ class BillController extends Controller
// add info about rules:
$rules = $this->billRepository->getRulesForBill($bill);
$subTitle = $bill->name;
/** @var Carbon $start */
$start = session('start');
/** @var Carbon $end */
$end = session('end');
$year = $start->year;
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$yearAverage = $this->billRepository->getYearAverage($bill, $start);
$overallAverage = $this->billRepository->getOverallAverage($bill);
$manager = new Manager();
@@ -295,9 +312,14 @@ class BillController extends Controller
/**
* Store a new bill.
*
* @param BillFormRequest $request
*
* @return RedirectResponse
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function store(BillFormRequest $request): RedirectResponse
{
@@ -316,7 +338,6 @@ class BillController extends Controller
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$this->attachments->saveAttachmentsForModel($bill, $files);
// flash messages
if (\count($this->attachments->getMessages()->get('attachments')) > 0) {
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore
}
@@ -348,6 +369,8 @@ class BillController extends Controller
}
/**
* Update a bill.
*
* @param BillFormRequest $request
* @param Bill $bill
*

View File

@@ -0,0 +1,213 @@
<?php
/**
* AmountController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\BudgetIncomeRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
* Class AmountController
*/
class AmountController extends Controller
{
use DateCalculation;
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* AmountController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Set the amount for a single budget in a specific period. Shows a waring when its a lot.
*
* @param Request $request
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function amount(Request $request, BudgetRepositoryInterface $repository, Budget $budget): JsonResponse
{
$amount = (string)$request->get('amount');
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
$currency = app('amount')->getDefaultCurrency();
$left = app('amount')->formatAnything($currency, bcadd($amount, $spent), true);
$largeDiff = false;
$warnText = '';
$leftPerDay = null;
$periodLength = $start->diffInDays($end);
$dayDifference = $this->getDayDifference($start, $end);
// If the user budgets ANY amount per day for this budget (anything but zero) Firefly III calculates how much he could spend per day.
if (1 === bccomp(bcadd($amount, $spent), '0')) {
$leftPerDay = app('amount')->formatAnything($currency, bcdiv(bcadd($amount, $spent), (string)$dayDifference), true);
}
// Get the average amount of money the user budgets for this budget. And calculate the same for the current amount.
// If the difference is very large, give the user a notification.
$average = $this->repository->budgetedPerDay($budget);
$current = bcdiv($amount, (string)$periodLength);
if (bccomp(bcmul('1.1', $average), $current) === -1) {
$largeDiff = true;
$warnText = (string)trans(
'firefly.over_budget_warn',
[
'amount' => app('amount')->formatAnything($currency, $average, false),
'over_amount' => app('amount')->formatAnything($currency, $current, false),
]
);
}
app('preferences')->mark();
return response()->json(
['left' => $left, 'name' => $budget->name, 'limit' => $budgetLimit ? $budgetLimit->id : 0, 'amount' => $amount, 'current' => $current,
'average' => $average, 'large_diff' => $largeDiff, 'left_per_day' => $leftPerDay, 'warn_text' => $warnText,]
);
}
/**
* Shows some basic info about the income and the suggested budget.
*
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function infoIncome(Carbon $start, Carbon $end)
{
$range = app('preferences')->get('viewRange', '1M')->data;
/** @var Carbon $searchBegin */
$searchBegin = app('navigation')->subtractPeriod($start, $range, 3);
$searchEnd = app('navigation')->addPeriod($end, $range, 3);
$daysInPeriod = $start->diffInDays($end);
$daysInSearchPeriod = $searchBegin->diffInDays($searchEnd);
$average = $this->repository->getAverageAvailable($start, $end);
$available = bcmul($average, (string)$daysInPeriod);
// amount earned in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($searchBegin, $searchEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
$earned = (string)$collector->getJournals()->sum('transaction_amount');
// Total amount earned divided by the number of days in the whole search period is the average amount earned per day.
// This is multiplied by the number of days in the current period, showing you the average.
$earnedAverage = bcmul(bcdiv($earned, (string)$daysInSearchPeriod), (string)$daysInPeriod);
// amount spent in period
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($searchBegin, $searchEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$spent = (string)$collector->getJournals()->sum('transaction_amount');
$spentAverage = app('steam')->positive(bcmul(bcdiv($spent, (string)$daysInSearchPeriod), (string)$daysInPeriod));
// the default suggestion is the money the user has spent, on average, over this period.
$suggested = $spentAverage;
// if the user makes less per period, suggest that amount instead.
if (1 === bccomp($spentAverage, $earnedAverage)) {
$suggested = $earnedAverage;
}
$result = ['available' => $available, 'earned' => $earnedAverage, 'spent' => $spentAverage, 'suggested' => $suggested,];
return view('budgets.info', compact('result', 'searchBegin', 'searchEnd', 'start', 'end'));
}
/**
* Store an available budget for the current period.
*
* @param BudgetIncomeRequest $request
*
* @return RedirectResponse
*/
public function postUpdateIncome(BudgetIncomeRequest $request): RedirectResponse
{
$start = Carbon::createFromFormat('Y-m-d', $request->string('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->string('end'));
$defaultCurrency = app('amount')->getDefaultCurrency();
$amount = $request->get('amount');
$page = 0 === $request->integer('page') ? 1 : $request->integer('page');
$this->repository->cleanupBudgets();
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
app('preferences')->mark();
return redirect(route('budgets.index', [$start->format('Y-m-d')]) . '?page=' . $page);
}
/**
* Shows the form to update available budget.
*
* @param Request $request
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function updateIncome(Request $request, Carbon $start, Carbon $end)
{
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$available = round($available, $defaultCurrency->decimal_places);
$page = (int)$request->get('page');
return view('budgets.income', compact('available', 'start', 'end', 'page'));
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* CreateController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\BudgetFormRequest;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
/**
* Class CreateController
*/
class CreateController extends Controller
{
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* CreateController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Form to create a budget.
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create(Request $request)
{
// put previous url in session if not redirect from store (not "create another").
if (true !== session('budgets.create.fromStore')) {
$this->rememberPreviousUri('budgets.create.uri');
}
$request->session()->forget('budgets.create.fromStore');
$subTitle = (string)trans('firefly.create_new_budget');
return view('budgets.create', compact('subTitle'));
}
/**
* Stores a budget.
*
* @param BudgetFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(BudgetFormRequest $request): RedirectResponse
{
$data = $request->getBudgetData();
$budget = $this->repository->store($data);
$this->repository->cleanupBudgets();
$request->session()->flash('success', (string)trans('firefly.stored_new_budget', ['name' => $budget->name]));
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('budgets.create.uri'));
if (1 === (int)$request->get('create_another')) {
// @codeCoverageIgnoreStart
$request->session()->put('budgets.create.fromStore', true);
$redirect = redirect(route('budgets.create'))->withInput();
// @codeCoverageIgnoreEnd
}
return $redirect;
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* DeleteController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Http\Request;
/**
*
* Class DeleteController
*/
class DeleteController extends Controller
{
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* DeleteController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Deletes a budget.
*
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Budget $budget)
{
$subTitle = (string)trans('firefly.delete_budget', ['name' => $budget->name]);
// put previous url in session
$this->rememberPreviousUri('budgets.delete.uri');
return view('budgets.delete', compact('budget', 'subTitle'));
}
/**
* Destroys a budget.
*
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, Budget $budget)
{
$name = $budget->name;
$this->repository->destroy($budget);
$request->session()->flash('success', (string)trans('firefly.deleted_budget', ['name' => $name]));
app('preferences')->mark();
return redirect($this->getPreviousUri('budgets.delete.uri'));
}
}

View File

@@ -0,0 +1,120 @@
<?php
/**
* EditController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\BudgetFormRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
/**
*
* Class EditController
*/
class EditController extends Controller
{
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* EditController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Budget edit form.
*
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function edit(Request $request, Budget $budget)
{
$subTitle = (string)trans('firefly.edit_budget', ['name' => $budget->name]);
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'active' => $hasOldInput ? (bool)$request->old('active') : $budget->active,
];
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('budgets.edit.fromUpdate')) {
$this->rememberPreviousUri('budgets.edit.uri');
}
$request->session()->forget('budgets.edit.fromUpdate');
$request->session()->flash('preFilled', $preFilled);
return view('budgets.edit', compact('budget', 'subTitle'));
}
/**
* Budget update routine.
*
* @param BudgetFormRequest $request
* @param Budget $budget
*
* @return \Illuminate\Http\RedirectResponse
*/
public function update(BudgetFormRequest $request, Budget $budget): RedirectResponse
{
$data = $request->getBudgetData();
$this->repository->update($budget, $data);
$request->session()->flash('success', (string)trans('firefly.updated_budget', ['name' => $budget->name]));
$this->repository->cleanupBudgets();
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('budgets.edit.uri'));
if (1 === (int)$request->get('return_to_edit')) {
// @codeCoverageIgnoreStart
$request->session()->put('budgets.edit.fromUpdate', true);
$redirect = redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
}
return $redirect;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* IndexController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use Carbon\Carbon;
use Exception;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Log;
/**
*
* Class IndexController
*/
class IndexController extends Controller
{
use DateCalculation;
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* IndexController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Show all budgets.
*
* @param Request $request
* @param string|null $moment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function index(Request $request, string $moment = null)
{
/** @var string $range */
$range = app('preferences')->get('viewRange', '1M')->data;
/** @var Carbon $start */
$start = session('start', new Carbon);
/** @var Carbon $end */
$end = session('end', new Carbon);
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$moment = $moment ?? '';
// make date if the data is given.
if ('' !== (string)$moment) {
try {
$start = new Carbon($moment);
/** @var Carbon $end */
$end = app('navigation')->endOfPeriod($start, $range);
} catch (Exception $e) {
// start and end are already defined.
Log::debug(sprintf('start and end are already defined: %s', $e->getMessage()));
}
}
// if today is between start and end, use the diff in days between end and today (days left)
// otherwise, use diff between start and end.
$dayDifference = $this->getDayDifference($start, $end);
$next = clone $end;
$next->addDay();
$prev = clone $start;
$prev->subDay();
$prev = app('navigation')->startOfPeriod($prev, $range);
$this->repository->cleanupBudgets();
$daysPassed = $this->getDaysPassedInPeriod($start, $end);
$allBudgets = $this->repository->getActiveBudgets();
$total = $allBudgets->count();
$budgets = $allBudgets->slice(($page - 1) * $pageSize, $pageSize);
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->repository->collectBudgetInformation($allBudgets, $start, $end);
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
$previousLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($end, $range);
// paginate budgets
$budgets = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
$budgets->setPath(route('budgets.index'));
// display info
$currentMonth = app('navigation')->periodShow($start, $range);
$nextText = app('navigation')->periodShow($next, $range);
$prevText = app('navigation')->periodShow($prev, $range);
return view(
'budgets.index', compact(
'available', 'currentMonth', 'next', 'nextText', 'prev', 'allBudgets', 'prevText', 'periodStart', 'periodEnd', 'dayDifference',
'page',
'budgetInformation', 'daysPassed',
'inactive', 'budgets', 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
)
);
}
}

View File

@@ -0,0 +1,293 @@
<?php
/**
* ShowController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Budget;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
*
* Class ShowController
*/
class ShowController extends Controller
{
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* ShowController constructor.
*/
public function __construct()
{
parent::__construct();
app('view')->share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Show transactions without a budget.
*
* @param Request $request
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function noBudget(Request $request, Carbon $start = null, Carbon $end = null)
{
/** @var Carbon $start */
$start = $start ?? session('start');
/** @var Carbon $end */
$end = $end ?? session('end');
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview();
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
->withoutBudget()->withOpposingAccount();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.no-budget'));
return view('budgets.no-budget', compact('transactions', 'subTitle', 'periods', 'start', 'end'));
}
/**
* Shows ALL transactions without a budget.
*
* @param Request $request
* @param JournalRepositoryInterface $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
public function noBudgetAll(Request $request, JournalRepositoryInterface $repository)
{
$subTitle = (string)trans('firefly.all_journals_without_budget');
$first = $repository->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$moment = 'all';
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
->withoutBudget()->withOpposingAccount();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.no-budget'));
return view('budgets.no-budget', compact('transactions', 'subTitle', 'moment', 'start', 'end'));
}
/**
* Show a single budget.
*
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(Request $request, Budget $budget)
{
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$limits = $this->getLimits($budget, $start, $end);
$repetition = null;
// collector:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.show', [$budget->id]));
$subTitle = (string)trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'transactions', 'subTitle'));
}
/**
* Show a single budget by a budget limit.
*
* @param Request $request
* @param Budget $budget
* @param BudgetLimit $budgetLimit
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws FireflyException
*/
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{
if ($budgetLimit->budget->id !== $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.budget_in_period',
[
'name' => $budget->name,
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
]
);
// collector:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$limits = $this->getLimits($budget, $start, $end);
return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'transactions', 'subTitle'));
}
/**
* Gets all budget limits for a budget.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection
{
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($budget->id);
$cache->addProperty('get-limits');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$set = $this->repository->getBudgetLimits($budget, $start, $end);
$limits = new Collection();
/** @var BudgetLimit $entry */
foreach ($set as $entry) {
$entry->spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection(), $entry->start_date, $entry->end_date);
$limits->push($entry);
}
$cache->store($limits);
return $set;
}
/**
* Gets period overview used for budgets.
*
* @return Collection
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(): Collection
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$first = $repository->firstNull();
$start = null === $first ? new Carbon : $first->date;
$range = app('preferences')->get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-budget-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$dates = app('navigation')->blockPeriods($start, $end, $range);
foreach ($dates as $date) {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutBudget()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$set = $collector->getJournals();
$sum = (string)($set->sum('transaction_amount') ?? '0');
$journals = $set->count();
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(
['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $date['end'],
'start' => $date['start'],
'end' => $date['end'],
]
);
}
$cache->store($entries);
return $entries;
}
}

View File

@@ -1,714 +0,0 @@
<?php
/**
* BudgetController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\BudgetFormRequest;
use FireflyIII\Http\Requests\BudgetIncomeRequest;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use View;
/**
* Class BudgetController.
*
*/
class BudgetController extends Controller
{
/** @var BudgetRepositoryInterface */
private $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
View::share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
);
}
/**
* @param Request $request
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
*
* @return JsonResponse
*/
public function amount(Request $request, BudgetRepositoryInterface $repository, Budget $budget): JsonResponse
{
$amount = (string)$request->get('amount');
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
$largeDiff = false;
$warnText = '';
$days = 0;
$daysInMonth = 0;
if (0 === bccomp($amount, '0')) {
$budgetLimit = null;
}
// if today is between start and end, use the diff in days between end and today (days left)
// otherwise, use diff between start and end.
$today = new Carbon;
Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'), $today->format('Y-m-d')));
if ($today->gte($start) && $today->lte($end)) {
$days = $end->diffInDays($today);
$daysInMonth = $start->diffInDays($today);
}
if ($today->lte($start) || $today->gte($end)) {
$days = $start->diffInDays($end);
$daysInMonth = $start->diffInDays($end);
}
$days = 0 === $days ? 1 : $days;
$daysInMonth = 0 === $daysInMonth ? 1 : $daysInMonth;
// calculate left in budget:
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
$currency = app('amount')->getDefaultCurrency();
$left = app('amount')->formatAnything($currency, bcadd($amount, $spent), true);
$leftPerDay = 'none';
// is user has money left, calculate.
if (1 === bccomp(bcadd($amount, $spent), '0')) {
$leftPerDay = app('amount')->formatAnything($currency, bcdiv(bcadd($amount, $spent), (string)$days), true);
}
// over or under budgeting, compared to previous budgets?
$average = $this->repository->budgetedPerDay($budget);
// current average per day:
$diff = $start->diffInDays($end);
$current = $amount;
if ($diff > 0) {
$current = bcdiv($amount, (string)$diff);
}
if (bccomp(bcmul('1.1', $average), $current) === -1) {
$largeDiff = true;
$warnText = (string)trans(
'firefly.over_budget_warn',
[
'amount' => app('amount')->formatAnything($currency, $average, false),
'over_amount' => app('amount')->formatAnything($currency, $current, false),
]
);
}
app('preferences')->mark();
return response()->json(
[
'left' => $left,
'name' => $budget->name,
'limit' => $budgetLimit ? $budgetLimit->id : 0,
'amount' => $amount,
'current' => $current,
'average' => $average,
'large_diff' => $largeDiff,
'left_per_day' => $leftPerDay,
'warn_text' => $warnText,
'daysInMonth' => $daysInMonth,
]
);
}
/**
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create(Request $request)
{
// put previous url in session if not redirect from store (not "create another").
if (true !== session('budgets.create.fromStore')) {
$this->rememberPreviousUri('budgets.create.uri');
}
$request->session()->forget('budgets.create.fromStore');
$subTitle = (string)trans('firefly.create_new_budget');
return view('budgets.create', compact('subTitle'));
}
/**
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Budget $budget)
{
$subTitle = trans('firefly.delete_budget', ['name' => $budget->name]);
// put previous url in session
$this->rememberPreviousUri('budgets.delete.uri');
return view('budgets.delete', compact('budget', 'subTitle'));
}
/**
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, Budget $budget)
{
$name = $budget->name;
$this->repository->destroy($budget);
$request->session()->flash('success', (string)trans('firefly.deleted_budget', ['name' => $name]));
app('preferences')->mark();
return redirect($this->getPreviousUri('budgets.delete.uri'));
}
/**
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function edit(Request $request, Budget $budget)
{
$subTitle = trans('firefly.edit_budget', ['name' => $budget->name]);
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'active' => $hasOldInput ? (bool)$request->old('active') : $budget->active,
];
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('budgets.edit.fromUpdate')) {
$this->rememberPreviousUri('budgets.edit.uri');
}
$request->session()->forget('budgets.edit.fromUpdate');
$request->session()->flash('preFilled', $preFilled);
return view('budgets.edit', compact('budget', 'subTitle'));
}
/**
* @param Request $request
* @param string|null $moment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request, string $moment = null)
{
$range = Preferences::get('viewRange', '1M')->data;
$start = session('start', new Carbon);
$end = session('end', new Carbon);
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$days = 0;
$daysInMonth = 0;
// make date if present:
if (null !== $moment || '' !== (string)$moment) {
try {
$start = new Carbon($moment);
$end = app('navigation')->endOfPeriod($start, $range);
} catch (Exception $e) {
// start and end are already defined.
Log::debug(sprintf('start and end are already defined: %s', $e->getMessage()));
}
}
// if today is between start and end, use the diff in days between end and today (days left)
// otherwise, use diff between start and end.
$today = new Carbon;
if ($today->gte($start) && $today->lte($end)) {
$days = $end->diffInDays($today);
$daysInMonth = $start->diffInDays($today);
}
if ($today->lte($start) || $today->gte($end)) {
$days = $start->diffInDays($end);
$daysInMonth = $start->diffInDays($end);
}
$days = 0 === $days ? 1 : $days;
$daysInMonth = 0 === $daysInMonth ? 1 : $daysInMonth;
$next = clone $end;
$next->addDay();
$prev = clone $start;
$prev->subDay();
$prev = app('navigation')->startOfPeriod($prev, $range);
$this->repository->cleanupBudgets();
$allBudgets = $this->repository->getActiveBudgets();
$total = $allBudgets->count();
$budgets = $allBudgets->slice(($page - 1) * $pageSize, $pageSize);
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->repository->collectBudgetInformation($allBudgets, $start, $end);
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
// paginate budgets
$budgets = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
$budgets->setPath(route('budgets.index'));
// select thing for last 12 periods:
$previousLoop = [];
/** @var Carbon $previousDate */
$previousDate = clone $start;
$count = 0;
while ($count < 12) {
$previousDate->subDay();
$previousDate = app('navigation')->startOfPeriod($previousDate, $range);
$format = $previousDate->format('Y-m-d');
$previousLoop[$format] = app('navigation')->periodShow($previousDate, $range);
++$count;
}
// select thing for next 12 periods:
$nextLoop = [];
/** @var Carbon $nextDate */
$nextDate = clone $end;
$nextDate->addDay();
$count = 0;
while ($count < 12) {
$format = $nextDate->format('Y-m-d');
$nextLoop[$format] = app('navigation')->periodShow($nextDate, $range);
$nextDate = app('navigation')->endOfPeriod($nextDate, $range);
++$count;
$nextDate->addDay();
}
// display info
$currentMonth = app('navigation')->periodShow($start, $range);
$nextText = app('navigation')->periodShow($next, $range);
$prevText = app('navigation')->periodShow($prev, $range);
return view(
'budgets.index', compact(
'available', 'currentMonth', 'next', 'nextText', 'prev', 'allBudgets', 'prevText', 'periodStart', 'periodEnd', 'days', 'page',
'budgetInformation', 'daysInMonth',
'inactive', 'budgets', 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
)
);
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function infoIncome(Carbon $start, Carbon $end)
{
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('info-income');
Log::debug(sprintf('infoIncome start is %s', $start->format('Y-m-d')));
Log::debug(sprintf('infoIncome end is %s', $end->format('Y-m-d')));
if ($cache->has()) {
// @codeCoverageIgnoreStart
$result = $cache->get();
return view('budgets.info', compact('result'));
// @codeCoverageIgnoreEnd
}
$result = [
'available' => '0',
'earned' => '0',
'suggested' => '0',
];
$currency = app('amount')->getDefaultCurrency();
$range = Preferences::get('viewRange', '1M')->data;
/** @var Carbon $begin */
$begin = app('navigation')->subtractPeriod($start, $range, 3);
Log::debug(sprintf('Range is %s', $range));
Log::debug(sprintf('infoIncome begin is %s', $begin->format('Y-m-d')));
// get average amount available.
$total = '0';
$count = 0;
$currentStart = clone $begin;
while ($currentStart < $start) {
Log::debug(sprintf('Loop: currentStart is %s', $currentStart->format('Y-m-d')));
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
$total = bcadd($total, $this->repository->getAvailableBudget($currency, $currentStart, $currentEnd));
$currentStart = app('navigation')->addPeriod($currentStart, $range, 0);
++$count;
}
Log::debug('Loop end');
if (0 === $count) {
$count = 1;
}
$result['available'] = bcdiv($total, (string)$count);
// amount earned in this period:
$subDay = clone $end;
$subDay->subDay();
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
$result['earned'] = bcdiv((string)$collector->getJournals()->sum('transaction_amount'), (string)$count);
// amount spent in period
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$result['spent'] = bcdiv((string)$collector->getJournals()->sum('transaction_amount'), (string)$count);
// suggestion starts with the amount spent
$result['suggested'] = bcmul($result['spent'], '-1');
$result['suggested'] = 1 === bccomp($result['suggested'], $result['earned']) ? $result['earned'] : $result['suggested'];
// unless it's more than you earned. So min() of suggested/earned
$cache->store($result);
return view('budgets.info', compact('result', 'begin', 'currentEnd'));
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string|null $moment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = null)
{
// default values:
$moment = $moment ?? '';
$range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_without_budget');
$first = $repository->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
}
// prep for "specific date" view.
if ('all' !== $moment && \strlen($moment) > 0) {
$start = new Carbon($moment);
/** @var Carbon $end */
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview();
}
// prep for current period
if ('' === $moment) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview();
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
->withoutBudget()->withOpposingAccount();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.no-budget'));
return view('budgets.no-budget', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end'));
}
/**
* @param BudgetIncomeRequest $request
*
* @return RedirectResponse
*/
public function postUpdateIncome(BudgetIncomeRequest $request): RedirectResponse
{
$start = Carbon::createFromFormat('Y-m-d', $request->string('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->string('end'));
$defaultCurrency = app('amount')->getDefaultCurrency();
$amount = $request->get('amount');
$page = 0 === $request->integer('page') ? 1 : $request->integer('page');
$this->repository->cleanupBudgets();
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
app('preferences')->mark();
return redirect(route('budgets.index', [$start->format('Y-m-d')]) . '?page=' . $page);
}
/**
* @param Request $request
* @param Budget $budget
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(Request $request, Budget $budget)
{
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$limits = $this->getLimits($budget, $start, $end);
$repetition = null;
// collector:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.show', [$budget->id]));
$subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'transactions', 'subTitle'));
}
/**
* @param Request $request
* @param Budget $budget
* @param BudgetLimit $budgetLimit
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws FireflyException
*/
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{
if ($budgetLimit->budget->id !== $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.budget_in_period',
[
'name' => $budget->name,
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
]
);
// collector:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$limits = $this->getLimits($budget, $start, $end);
return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'transactions', 'subTitle'));
}
/**
* @param BudgetFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(BudgetFormRequest $request): RedirectResponse
{
$data = $request->getBudgetData();
$budget = $this->repository->store($data);
$this->repository->cleanupBudgets();
$request->session()->flash('success', (string)trans('firefly.stored_new_budget', ['name' => $budget->name]));
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('budgets.create.uri'));
if (1 === (int)$request->get('create_another')) {
// @codeCoverageIgnoreStart
$request->session()->put('budgets.create.fromStore', true);
$redirect = redirect(route('budgets.create'))->withInput();
// @codeCoverageIgnoreEnd
}
return $redirect;
}
/**
* @param BudgetFormRequest $request
* @param Budget $budget
*
* @return \Illuminate\Http\RedirectResponse
*/
public function update(BudgetFormRequest $request, Budget $budget): RedirectResponse
{
$data = $request->getBudgetData();
$this->repository->update($budget, $data);
$request->session()->flash('success', (string)trans('firefly.updated_budget', ['name' => $budget->name]));
$this->repository->cleanupBudgets();
app('preferences')->mark();
$redirect = redirect($this->getPreviousUri('budgets.edit.uri'));
if (1 === (int)$request->get('return_to_edit')) {
// @codeCoverageIgnoreStart
$request->session()->put('budgets.edit.fromUpdate', true);
$redirect = redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
}
return $redirect;
}
/**
* @param Request $request
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function updateIncome(Request $request, Carbon $start, Carbon $end)
{
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$available = round($available, $defaultCurrency->decimal_places);
$page = (int)$request->get('page');
return view('budgets.income', compact('available', 'start', 'end', 'page'));
}
/**
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection
{
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($budget->id);
$cache->addProperty('get-limits');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$set = $this->repository->getBudgetLimits($budget, $start, $end);
$limits = new Collection();
/** @var BudgetLimit $entry */
foreach ($set as $entry) {
$entry->spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection(), $entry->start_date, $entry->end_date);
$limits->push($entry);
}
$cache->store($limits);
return $set;
}
/**
* @return Collection
*/
private function getPeriodOverview(): Collection
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$first = $repository->firstNull();
$start = null === $first ? new Carbon : $first->date;
$range = Preferences::get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-budget-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$dates = app('navigation')->blockPeriods($start, $end, $range);
foreach ($dates as $date) {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutBudget()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$set = $collector->getJournals();
$sum = (string)($set->sum('transaction_amount') ?? '0');
$journals = $set->count();
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $date['end']]);
}
$cache->store($entries);
return $entries;
}
}

View File

@@ -0,0 +1,230 @@
<?php
/**
* NoCategoryController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Category;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Log;
/**
*
* Class NoCategoryController
*/
class NoCategoryController extends Controller
{
/** @var JournalRepositoryInterface Journals and transactions overview */
private $journalRepos;
/**
* CategoryController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
$this->journalRepos = app(JournalRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Show transactions without a category.
*
* @param Request $request
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(Request $request, Carbon $start = null, Carbon $end = null)
{
Log::debug('Start of noCategory()');
/** @var Carbon $start */
$start = $start ?? session('start');
/** @var Carbon $end */
$end = $end ?? session('end');
$moment = '';
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview($start);
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount()
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('categories.no-category'));
return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end'));
}
/**
* Show all transactions without a category.
*
* @param Request $request
* @param string|null $moment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showAll(Request $request, string $moment = null)
{
// default values:
$moment = $moment ?? '';
$start = null;
$end = null;
$periods = new Collection;
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
Log::debug('Start of noCategory()');
$subTitle = (string)trans('firefly.all_journals_without_category');
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount()
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('categories.no-category'));
return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end'));
}
/**
* Show period overview for no category view.
*
* @param Carbon $theDate
*
* @return Collection
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getNoCategoryPeriodOverview(Carbon $theDate): Collection
{
Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
$range = app('preferences')->get('viewRange', '1M')->data;
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = $theDate ?? new Carbon;
Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-category-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount'));
// amount spent
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$spent = $collector->getJournals()->sum('transaction_amount');
// amount earned
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::DEPOSIT]
);
$earned = $collector->getJournals()->sum('transaction_amount');
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'count' => $count,
'spent' => $spent,
'earned' => $earned,
'transferred' => $transferred,
'start' => clone $date['start'],
'end' => clone $date['end'],
]
);
}
Log::debug('End of loops');
$cache->store($entries);
return $entries;
}
}

View File

@@ -0,0 +1,226 @@
<?php
/**
* ShowController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Category;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Log;
/**
*
* Class ShowController
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ShowController extends Controller
{
/** @var AccountRepositoryInterface The account repository */
private $accountRepos;
/** @var JournalRepositoryInterface Journals and transactions overview */
private $journalRepos;
/** @var CategoryRepositoryInterface The category repository */
private $repository;
/**
* CategoryController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
$this->journalRepos = app(JournalRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
$this->accountRepos = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Show a single category.
*
* @param Request $request
* @param Category $category
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null)
{
Log::debug('Now in show()');
/** @var Carbon $start */
$start = $start ?? session('start', Carbon::create()->startOfMonth());
/** @var Carbon $end */
$end = $end ?? session('end', Carbon::create()->startOfMonth());
$subTitleIcon = 'fa-bar-chart';
$moment = '';
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$periods = $this->getPeriodOverview($category, $start);
$path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath($path);
Log::debug('End of show()');
return view('categories.show', compact('category', 'transactions', 'moment', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
}
/**
* Show all transactions within a category.
*
* @param Request $request
* @param Category $category
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showAll(Request $request, Category $category)
{
// default values:
$subTitleIcon = 'fa-bar-chart';
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$start = null;
$end = null;
$periods = new Collection;
$moment = 'all';
$subTitle = (string)trans('firefly.all_journals_for_category', ['name' => $category->name]);
$first = $this->repository->firstUseDate($category);
/** @var Carbon $start */
$start = $first ?? new Carbon;
$end = new Carbon;
$path = route('categories.show-all', [$category->id]);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath($path);
return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
}
/**
* Get a period overview for category.
*
* @param Category $category
*
* @param Carbon $date
*
* @return Collection
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(Category $category, Carbon $date): Collection
{
$range = app('preferences')->get('viewRange', '1M')->data;
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = $date ?? new Carbon;
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// properties for entries with their amounts.
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($range);
$cache->addProperty('categories.entries');
$cache->addProperty($category->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $currentDate) {
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $currentDate['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount'));
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'sum' => bcadd($earned, $spent),
'transferred' => $transferred,
'start' => clone $currentDate['start'],
'end' => clone $currentDate['end'],
]
);
}
$cache->store($entries);
return $entries;
}
}

View File

@@ -22,33 +22,19 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Http\Requests\CategoryFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Preferences;
/**
* Class CategoryController.
*/
class CategoryController extends Controller
{
/** @var AccountRepositoryInterface */
private $accountRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var CategoryRepositoryInterface */
/** @var CategoryRepositoryInterface The category repository */
private $repository;
/**
@@ -60,11 +46,9 @@ class CategoryController extends Controller
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.categories'));
app('view')->share('title', (string)trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
$this->journalRepos = app(JournalRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
return $next($request);
}
@@ -72,6 +56,8 @@ class CategoryController extends Controller
}
/**
* Create category.
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -82,19 +68,21 @@ class CategoryController extends Controller
$this->rememberPreviousUri('categories.create.uri');
}
$request->session()->forget('categories.create.fromStore');
$subTitle = trans('firefly.create_new_category');
$subTitle = (string)trans('firefly.create_new_category');
return view('categories.create', compact('subTitle'));
}
/**
* Delete a category.
*
* @param Category $category
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function delete(Category $category)
{
$subTitle = trans('firefly.delete_category', ['name' => $category->name]);
$subTitle = (string)trans('firefly.delete_category', ['name' => $category->name]);
// put previous url in session
$this->rememberPreviousUri('categories.delete.uri');
@@ -103,6 +91,8 @@ class CategoryController extends Controller
}
/**
* Destroy a category.
*
* @param Request $request
* @param Category $category
*
@@ -120,6 +110,8 @@ class CategoryController extends Controller
}
/**
* Edit a category.
*
* @param Request $request
* @param Category $category
*
@@ -127,7 +119,7 @@ class CategoryController extends Controller
*/
public function edit(Request $request, Category $category)
{
$subTitle = trans('firefly.edit_category', ['name' => $category->name]);
$subTitle = (string)trans('firefly.edit_category', ['name' => $category->name]);
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('categories.edit.fromUpdate')) {
@@ -139,6 +131,8 @@ class CategoryController extends Controller
}
/**
* Show all categories.
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -146,7 +140,7 @@ class CategoryController extends Controller
public function index(Request $request)
{
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$collection = $this->repository->getCategories();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
@@ -164,141 +158,10 @@ class CategoryController extends Controller
return view('categories.index', compact('categories'));
}
/**
* @param Request $request
* @param string|null $moment
* Store new category.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function noCategory(Request $request, string $moment = null)
{
// default values:
$moment = $moment ?? '';
$range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
Log::debug('Start of noCategory()');
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_without_category');
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
Log::debug('Moment is all()');
}
// prep for "specific date" view.
if ('all' !== $moment && \strlen($moment) > 0) {
/** @var Carbon $start */
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
/** @var Carbon $end */
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview($start);
}
// prep for current period
if ('' === $moment) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getNoCategoryPeriodOverview($start);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount()
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('categories.no-category'));
return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end'));
}
/**
* @param Request $request
* @param Category $category
* @param string|null $moment
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function show(Request $request, Category $category, string $moment = null)
{
// default values:
$moment = $moment ?? '';
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
$path = route('categories.show', [$category->id]);
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]);
$first = $this->repository->firstUseDate($category);
/** @var Carbon $start */
$start = $first ?? new Carbon;
$end = new Carbon;
$path = route('categories.show', [$category->id, 'all']);
}
// prep for "specific date" view.
if ('all' !== $moment && \strlen($moment) > 0) {
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
/** @var Carbon $end */
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
$periods = $this->getPeriodOverview($category, $start);
$path = route('categories.show', [$category->id, $moment]);
}
// prep for current period
if ('' === $moment) {
/** @var Carbon $start */
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category, $start);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath($path);
return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
}
/**
* @param CategoryFormRequest $request
* @param CategoryRepositoryInterface $repository
*
@@ -326,6 +189,8 @@ class CategoryController extends Controller
/**
* Update category.
*
* @param CategoryFormRequest $request
* @param CategoryRepositoryInterface $repository
* @param Category $category
@@ -354,149 +219,4 @@ class CategoryController extends Controller
}
/**
* @param Carbon $theDate
*
* @return Collection
*/
private function getNoCategoryPeriodOverview(Carbon $theDate): Collection
{
Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = $theDate ?? new Carbon;
Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-category-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount'));
// amount spent
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$spent = $collector->getJournals()->sum('transaction_amount');
// amount earned
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::DEPOSIT]
);
$earned = $collector->getJournals()->sum('transaction_amount');
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'count' => $count,
'spent' => $spent,
'earned' => $earned,
'transferred' => $transferred,
'date' => clone $date['end'],
]
);
}
Log::debug('End of loops');
$cache->store($entries);
return $entries;
}
/**
* @param Category $category
*
* @param Carbon $date
*
* @return Collection
*/
private function getPeriodOverview(Category $category, Carbon $date): Collection
{
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = $date ?? new Carbon;
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// properties for entries with their amounts.
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($range);
$cache->addProperty('categories.entries');
$cache->addProperty($category->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $currentDate) {
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
/** @noinspection PhpUndefinedMethodInspection */
$dateStr = $currentDate['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount'));
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'sum' => bcadd($earned, $spent),
'transferred' => $transferred,
'date' => clone $currentDate['end'],
]
);
}
$cache->store($entries);
return $entries;
}
}

View File

@@ -36,21 +36,27 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Log;
use Preferences;
/** checked
/**
* Class AccountController.
*
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class AccountController extends Controller
{
/** @var GeneratorInterface */
use DateCalculation;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
/**
*
* AccountController constructor.
*/
public function __construct()
{
@@ -68,7 +74,9 @@ class AccountController extends Controller
*/
public function expenseAccounts(AccountRepositoryInterface $repository): JsonResponse
{
/** @var Carbon $start */
$start = clone session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = clone session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
@@ -103,6 +111,8 @@ class AccountController extends Controller
/**
* Expenses per budget, as shown on account overview.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
@@ -146,6 +156,8 @@ class AccountController extends Controller
}
/**
* Expenses per budget for all time, as shown on account overview.
*
* @param AccountRepositoryInterface $repository
* @param Account $account
*
@@ -161,6 +173,8 @@ class AccountController extends Controller
/**
* Expenses per category for one single account.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
@@ -204,6 +218,8 @@ class AccountController extends Controller
}
/**
* Expenses grouped by category for account.
*
* @param AccountRepositoryInterface $repository
* @param Account $account
*
@@ -231,7 +247,7 @@ class AccountController extends Controller
$end = clone session('end', Carbon::now()->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
Log::debug('Default set is ', $defaultSet);
$frontPage = Preferences::get('frontPageAccounts', $defaultSet);
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
Log::debug('Frontpage preference set is ', $frontPage->data);
@@ -247,6 +263,8 @@ class AccountController extends Controller
/**
* Shows all income per account for each category.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
@@ -290,6 +308,8 @@ class AccountController extends Controller
}
/**
* Shows the income grouped by category for an account, in all time.
*
* @param AccountRepositoryInterface $repository
* @param Account $account
*
@@ -305,12 +325,16 @@ class AccountController extends Controller
/**
* Shows overview of account during a single period.
*
* @param Account $account
* @param Carbon $start
*
* @param Carbon $end
*
* @return JsonResponse
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function period(Account $account, Carbon $start, Carbon $end): JsonResponse
{
@@ -322,18 +346,8 @@ class AccountController extends Controller
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
}
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W'; // @codeCoverageIgnore
}
if ($months > 24) {
$step = '1M'; // @codeCoverageIgnore
}
if ($months > 100) {
$step = '1Y'; // @codeCoverageIgnore
}
$step = $this->calculateStep($start, $end);
$chartData = [];
$current = clone $start;
switch ($step) {
@@ -429,11 +443,16 @@ class AccountController extends Controller
/**
* Shows an overview of the account balances for a set of accounts.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array
{
@@ -450,10 +469,14 @@ class AccountController extends Controller
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$default = app('amount')->getDefaultCurrency();
$chartData = [];
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$default = app('amount')->getDefaultCurrency();
$chartData = [];
/** @var Account $account */
foreach ($accounts as $account) {
$currency = $repository->findNull((int)$account->getMeta('currency_id'));
$currency = $repository->findNull((int)$accountRepos->getMetaValue($account, 'currency_id'));
if (null === $currency) {
$currency = $default;
}
@@ -483,6 +506,8 @@ class AccountController extends Controller
}
/**
* Get the budget names from a set of budget ID's.
*
* @param array $budgetIds
*
* @return array
@@ -499,13 +524,13 @@ class AccountController extends Controller
$return[$budgetId] = $grouped[$budgetId][0]['name'];
}
}
$return[0] = trans('firefly.no_budget');
$return[0] = (string)trans('firefly.no_budget');
return $return;
}
/**
* Small helper function for some of the charts.
* Get the category names from a set of category ID's. Small helper function for some of the charts.
*
* @param array $categoryIds
*
@@ -523,7 +548,7 @@ class AccountController extends Controller
$return[$categoryId] = $grouped[$categoryId][0]['name'];
}
}
$return[0] = trans('firefly.noCategory');
$return[0] = (string)trans('firefly.noCategory');
return $return;
}

View File

@@ -38,11 +38,11 @@ use Illuminate\Support\Collection;
*/
class BillController extends Controller
{
/** @var GeneratorInterface */
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
/**
* checked.
* BillController constructor.
*/
public function __construct()
{
@@ -85,6 +85,8 @@ class BillController extends Controller
/**
* Shows overview for a single bill.
*
* @param JournalCollectorInterface $collector
* @param Bill $bill
*
@@ -99,16 +101,16 @@ class BillController extends Controller
return response()->json($cache->get()); // @codeCoverageIgnore
}
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
$results = $results->sortBy(
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
$results = $results->sortBy(
function (Transaction $transaction) {
return $transaction->date->format('U');
}
);
$chartData = [
['type' => 'bar', 'label' => trans('firefly.min-amount'), 'entries' => []],
['type' => 'bar', 'label' => trans('firefly.max-amount'), 'entries' => []],
['type' => 'line', 'label' => trans('firefly.journal-amount'), 'entries' => []],
['type' => 'bar', 'label' => (string)trans('firefly.min-amount'), 'entries' => []],
['type' => 'bar', 'label' => (string)trans('firefly.max-amount'), 'entries' => []],
['type' => 'line', 'label' => (string)trans('firefly.journal-amount'), 'entries' => []],
];
/** @var Transaction $entry */
@@ -116,7 +118,13 @@ class BillController extends Controller
$date = $entry->date->formatLocalized((string)trans('config.month_and_day'));
$chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill
$chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill
$chartData[2]['entries'][$date] = bcmul($entry->transaction_amount, '-1'); // amount of journal
// append amount because there are more than one per moment:
if (!isset($chartData[2]['entries'][$date])) {
$chartData[2]['entries'][$date] = '0';
}
$amount = bcmul($entry->transaction_amount, '-1');
$chartData[2]['entries'][$date] = bcadd($chartData[2]['entries'][$date], $amount); // amount of journal
}
$data = $this->generator->multiSet($chartData);

View File

@@ -36,19 +36,24 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class BudgetController.
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*
*/
class BudgetController extends Controller
{
/** @var GeneratorInterface */
use DateCalculation;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
/** @var BudgetRepositoryInterface */
/** @var BudgetRepositoryInterface The budget repository */
protected $repository;
/**
@@ -70,13 +75,17 @@ class BudgetController extends Controller
/**
* Shows overview of a single budget.
*
* @param Budget $budget
*
* @return JsonResponse
*/
public function budget(Budget $budget): JsonResponse
{
$start = $this->repository->firstUseDate($budget);
/** @var Carbon $start */
$start = $this->repository->firstUseDate($budget) ?? session('start', new Carbon);
/** @var Carbon $end */
$end = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($start);
@@ -88,23 +97,11 @@ class BudgetController extends Controller
return response()->json($cache->get()); // @codeCoverageIgnore
}
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W';
}
if ($months > 24) {
$step = '1M';
}
if ($months > 60) {
$step = '1Y'; // @codeCoverageIgnore
}
$step = $this->calculateStep($start, $end); // depending on diff, do something with range of chart.
$budgetCollection = new Collection([$budget]);
$chartData = [];
$current = clone $start;
$current = app('navigation')->startOfPeriod($current, $step);
while ($end >= $current) {
/** @var Carbon $currentEnd */
$currentEnd = app('navigation')->endOfPeriod($current, $step);
@@ -129,7 +126,6 @@ class BudgetController extends Controller
/**
* Shows the amount left in a specific budget limit.
*
*
* @param Budget $budget
* @param BudgetLimit $budgetLimit
*
@@ -175,10 +171,14 @@ class BudgetController extends Controller
/**
* Shows how much is spent per asset account.
*
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit): JsonResponse
{
@@ -221,10 +221,14 @@ class BudgetController extends Controller
/**
* Shows how much is spent per category.
*
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit): JsonResponse
{
@@ -260,7 +264,6 @@ class BudgetController extends Controller
foreach ($result as $categoryId => $amount) {
$chartData[$names[$categoryId]] = $amount;
}
$data = $this->generator->pieChart($chartData);
$cache->store($data);
@@ -269,10 +272,14 @@ class BudgetController extends Controller
/**
* Shows how much is spent per expense account.
*
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit): JsonResponse
{
@@ -319,6 +326,9 @@ class BudgetController extends Controller
* Shows a budget list with spent/left/overspent.
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function frontpage(): \Symfony\Component\HttpFoundation\Response
{
@@ -368,6 +378,7 @@ class BudgetController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Shows a budget overview chart (spent and budgeted).
*
* @param Budget $budget
* @param Carbon $start
@@ -413,6 +424,8 @@ class BudgetController extends Controller
/**
* Shows a chart for transactions without a budget.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
@@ -449,6 +462,8 @@ class BudgetController extends Controller
}
/**
* Get the account names belonging to a bunch of account ID's.
*
* @param array $accountIds
*
* @return array
@@ -471,6 +486,8 @@ class BudgetController extends Controller
}
/**
* Get the amount of money budgeted in a period.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
@@ -499,7 +516,7 @@ class BudgetController extends Controller
}
/**
* Small helper function for some of the charts.
* Small helper function for some of the charts. Extracts category names from a bunch of ID's.
*
* @param array $categoryIds
*
@@ -517,13 +534,14 @@ class BudgetController extends Controller
$return[$categoryId] = $grouped[$categoryId][0]['name'];
}
}
$return[0] = trans('firefly.noCategory');
$return[0] = (string)trans('firefly.noCategory');
return $return;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Get the expenses for a budget in a date range.
*
* @param Collection $limits
* @param Budget $budget
@@ -531,6 +549,8 @@ class BudgetController extends Controller
* @param Carbon $end
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array
{
@@ -571,6 +591,9 @@ class BudgetController extends Controller
* @param Collection $limits
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
private function spentInPeriodMulti(Budget $budget, Collection $limits): array
{
@@ -591,18 +614,12 @@ 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;
$leftInLimit = bcsub($amount, $expenses);
$hasOverspent = bccomp($leftInLimit, '0') === -1;
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
$spent = $hasOverspent ? $amount : $expenses;
$overspent = $hasOverspent ? app('steam')->positive($leftInLimit) : '0';
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
$spent = $hasOverspent ? $amount : $expenses;
$overspent = $hasOverspent ? app('steam')->positive($leftInLimit) : '0';
$return[$name] = [
'left' => $left,

View File

@@ -43,16 +43,18 @@ use Illuminate\Support\Collection;
* Separate controller because many helper functions are shared.
*
* Class BudgetReportController
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class BudgetReportController extends Controller
{
/** @var BudgetRepositoryInterface */
/** @var BudgetRepositoryInterface The budget repository */
private $budgetRepository;
/** @var GeneratorInterface */
/** @var GeneratorInterface Chart generation methods. */
private $generator;
/**
*
* BudgetReportController constructor.
*/
public function __construct()
{
@@ -69,6 +71,8 @@ class BudgetReportController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Chart that groups expenses by the account.
*
* @param Collection $accounts
* @param Collection $budgets
* @param Carbon $start
@@ -76,6 +80,8 @@ class BudgetReportController extends Controller
* @param string $others
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function accountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others): JsonResponse
{
@@ -94,6 +100,8 @@ class BudgetReportController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Chart that groups the expenses by budget.
*
* @param Collection $accounts
* @param Collection $budgets
* @param Carbon $start
@@ -101,6 +109,8 @@ class BudgetReportController extends Controller
* @param string $others
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others): JsonResponse
{
@@ -119,12 +129,17 @@ class BudgetReportController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Main overview of a budget in the budget report.
*
* @param Collection $accounts
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return JsonResponse
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function mainChart(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse
{
@@ -233,6 +248,8 @@ class BudgetReportController extends Controller
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* Helper function that collects expenses for the given budgets.
*
* @param Collection $accounts
* @param Collection $budgets
* @param Carbon $start
@@ -255,6 +272,8 @@ class BudgetReportController extends Controller
}
/**
* Helper function that groups expenses.
*
* @param Collection $set
*
* @return array

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