From 1b52147a05b37307c1186a342e63b619c30eb5a2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 29 May 2023 13:56:55 +0200 Subject: [PATCH 01/30] chore: code cleanup. --- .ci/php-cs-fixer/composer.lock | 24 +- .ci/phpcs.sh | 3 + .../Autocomplete/AccountController.php | 12 +- app/Api/V1/Controllers/Controller.php | 81 +- .../V1/Controllers/Data/DestroyController.php | 170 +- .../Data/Export/ExportController.php | 56 +- .../V1/Controllers/Data/PurgeController.php | 1 + .../Models/Account/UpdateController.php | 2 +- .../Models/Attachment/StoreController.php | 2 +- .../Models/Transaction/ShowController.php | 30 +- .../Models/Transaction/StoreController.php | 2 +- .../Models/Transaction/UpdateController.php | 2 +- .../TransactionCurrency/UpdateController.php | 58 +- .../Controllers/Search/AccountController.php | 3 +- .../Controllers/Summary/BasicController.php | 48 +- .../System/ConfigurationController.php | 72 +- .../V1/Controllers/System/CronController.php | 2 +- .../V1/Controllers/Webhook/ShowController.php | 2 + .../Data/Bulk/MoveTransactionsRequest.php | 1 + .../Requests/Data/Bulk/TransactionRequest.php | 2 +- .../V1/Requests/Insight/GenericRequest.php | 216 +-- .../Requests/Models/Account/UpdateRequest.php | 2 +- .../V1/Requests/Models/Bill/StoreRequest.php | 6 +- .../Models/Recurrence/StoreRequest.php | 118 +- .../Models/Recurrence/UpdateRequest.php | 128 +- .../V1/Requests/Models/Rule/StoreRequest.php | 154 +- .../V1/Requests/Models/Rule/TestRequest.php | 38 +- .../Requests/Models/Rule/TriggerRequest.php | 36 +- .../V1/Requests/Models/Rule/UpdateRequest.php | 164 +- .../Requests/Models/RuleGroup/TestRequest.php | 36 +- .../Models/RuleGroup/TriggerRequest.php | 22 +- .../Models/Transaction/StoreRequest.php | 196 +-- .../Models/Transaction/UpdateRequest.php | 5 +- .../TransactionLinkType/UpdateRequest.php | 6 +- .../Autocomplete/AccountController.php | 10 +- .../Controllers/Chart/AccountController.php | 2 + app/Api/V2/Controllers/Controller.php | 93 +- .../Controllers/Model/Bill/SumController.php | 3 + .../Model/Budget/ListController.php | 3 + .../Model/Budget/ShowController.php | 1 + .../Model/Budget/SumController.php | 4 + .../Model/BudgetLimit/ListController.php | 2 + app/Api/V2/Controllers/NetWorthController.php | 3 + .../System/PreferencesController.php | 2 + .../Transaction/List/AccountController.php | 1 + .../Autocomplete/AutocompleteRequest.php | 1 + app/Api/V2/Response/Sum/AutoSum.php | 2 + .../Commands/Correction/CorrectAmounts.php | 9 + .../Commands/Correction/CorrectDatabase.php | 1 + .../CorrectOpeningBalanceCurrencies.php | 1 + app/Console/Commands/Correction/FixIbans.php | 9 +- .../Correction/TriggerCreditCalculation.php | 2 + .../Integrity/CreateGroupMemberships.php | 1 + .../Integrity/UpdateGroupInformation.php | 3 + .../Commands/System/ForceDecimalSize.php | 75 +- .../Commands/System/ForceMigration.php | 3 + .../Commands/System/VerifySecurityAlerts.php | 1 + app/Console/Commands/Tools/Cron.php | 1 + .../Commands/Upgrade/DecryptDatabase.php | 1 + .../Upgrade/UpgradeLiabilitiesEight.php | 10 + app/Exceptions/GracefulNotFoundHandler.php | 78 +- app/Exceptions/Handler.php | 34 +- app/Factory/AccountFactory.php | 174 +- app/Factory/AccountMetaFactory.php | 20 +- app/Factory/BillFactory.php | 2 +- app/Factory/CategoryFactory.php | 20 +- app/Factory/PiggyBankEventFactory.php | 1 - app/Factory/RecurrenceFactory.php | 2 +- app/Factory/TagFactory.php | 68 +- app/Factory/TransactionFactory.php | 178 +- app/Factory/TransactionGroupFactory.php | 2 +- app/Factory/TransactionJournalFactory.php | 502 +++--- .../Report/Account/MonthReportGenerator.php | 20 +- .../Report/Audit/MonthReportGenerator.php | 2 +- .../Report/Budget/MonthReportGenerator.php | 56 +- .../Report/Category/MonthReportGenerator.php | 56 +- .../Webhook/StandardMessageGenerator.php | 148 +- app/Handlers/Events/APIEventHandler.php | 2 +- app/Handlers/Events/AutomationHandler.php | 2 +- .../Events/Model/BudgetLimitHandler.php | 24 +- app/Handlers/Events/UserEventHandler.php | 2 +- app/Helpers/Attachments/AttachmentHelper.php | 104 +- .../Collector/Extensions/MetaCollection.php | 256 +-- .../Collector/Extensions/TimeCollection.php | 24 +- app/Helpers/Collector/GroupCollector.php | 612 +++---- app/Helpers/Fiscal/FiscalHelper.php | 1 - app/Helpers/Update/UpdateTrait.php | 1 - .../Webhook/Sha3SignatureGenerator.php | 2 +- .../Controllers/Account/CreateController.php | 2 +- .../Controllers/Account/IndexController.php | 2 +- .../Account/ReconcileController.php | 2 +- .../Admin/ConfigurationController.php | 2 +- app/Http/Controllers/Admin/HomeController.php | 2 +- app/Http/Controllers/Admin/LinkController.php | 2 +- app/Http/Controllers/Admin/UserController.php | 2 +- .../Auth/ForgotPasswordController.php | 2 +- app/Http/Controllers/Auth/LoginController.php | 64 +- .../Controllers/Auth/RegisterController.php | 50 +- .../Controllers/Auth/TwoFactorController.php | 52 +- app/Http/Controllers/Bill/IndexController.php | 122 +- .../Budget/BudgetLimitController.php | 3 +- .../Controllers/Budget/IndexController.php | 46 +- .../Category/NoCategoryController.php | 2 +- .../Controllers/Chart/AccountController.php | 168 +- .../Chart/BudgetReportController.php | 46 +- .../Controllers/Chart/CategoryController.php | 150 +- .../Chart/CategoryReportController.php | 50 +- .../Chart/DoubleReportController.php | 100 +- .../Controllers/Chart/ReportController.php | 2 +- .../Controllers/Chart/TagReportController.php | 50 +- app/Http/Controllers/CurrencyController.php | 2 +- app/Http/Controllers/HomeController.php | 4 +- .../Controllers/Json/ReconcileController.php | 82 +- .../PiggyBank/AmountController.php | 2 +- .../Controllers/PiggyBank/IndexController.php | 46 +- .../Controllers/PreferencesController.php | 2 +- app/Http/Controllers/ProfileController.php | 182 +-- .../Controllers/Report/BudgetController.php | 2 +- .../Controllers/Report/CategoryController.php | 2 +- .../Controllers/Report/DoubleController.php | 52 +- app/Http/Controllers/Report/TagController.php | 2 +- app/Http/Controllers/ReportController.php | 2 +- app/Http/Controllers/Rule/EditController.php | 54 +- app/Http/Controllers/SearchController.php | 2 +- .../System/HealthcheckController.php | 2 +- .../Controllers/System/InstallController.php | 48 +- app/Http/Controllers/TagController.php | 38 +- .../Transaction/BulkController.php | 38 +- .../Transaction/ConvertController.php | 256 +-- .../Transaction/LinkController.php | 2 +- .../Transaction/MassController.php | 136 +- .../Transaction/ShowController.php | 60 +- app/Http/Kernel.php | 33 +- app/Http/Middleware/Installer.php | 48 +- app/Http/Middleware/InterestingMessage.php | 188 +-- app/Http/Middleware/Range.php | 64 +- app/Http/Requests/RecurrenceFormRequest.php | 104 +- app/Http/Requests/RuleFormRequest.php | 122 +- app/Jobs/CreateAutoBudgetLimits.php | 248 +-- app/Jobs/CreateRecurringTransactions.php | 426 ++--- app/Jobs/DownloadExchangeRates.php | 48 +- app/Jobs/WarnAboutBills.php | 60 +- app/Models/Account.php | 22 +- app/Models/Attachment.php | 16 +- app/Models/AutoBudget.php | 5 +- app/Models/AvailableBudget.php | 8 +- app/Models/Bill.php | 16 +- app/Models/Budget.php | 16 +- app/Models/BudgetLimit.php | 8 +- app/Models/Category.php | 16 +- app/Models/InvitedUser.php | 16 +- app/Models/ObjectGroup.php | 16 +- app/Models/Recurrence.php | 16 +- app/Models/Rule.php | 16 +- app/Models/RuleGroup.php | 16 +- app/Models/Tag.php | 16 +- app/Models/Transaction.php | 46 +- app/Models/TransactionGroup.php | 16 +- app/Models/TransactionJournal.php | 64 +- app/Models/UserGroup.php | 26 +- app/Providers/AccountServiceProvider.php | 6 +- app/Providers/EventServiceProvider.php | 11 +- app/Providers/FireflySessionProvider.php | 26 +- app/Providers/JournalServiceProvider.php | 76 +- .../Account/AccountRepository.php | 274 ++-- app/Repositories/Account/AccountTasker.php | 90 +- .../Account/OperationsRepository.php | 318 ++-- .../Account/AccountRepository.php | 10 +- .../Attachment/AttachmentRepository.php | 64 +- app/Repositories/Bill/BillRepository.php | 236 +-- .../Budget/AvailableBudgetRepository.php | 62 +- .../Budget/BudgetLimitRepository.php | 52 +- app/Repositories/Budget/BudgetRepository.php | 512 +++--- .../Budget/NoBudgetRepository.php | 20 +- .../Budget/OperationsRepository.php | 22 +- .../Category/CategoryRepository.php | 314 ++-- .../Category/NoCategoryRepository.php | 20 +- .../Category/OperationsRepository.php | 40 +- .../Currency/CurrencyRepository.php | 98 +- .../Journal/JournalRepository.php | 24 +- .../LinkType/LinkTypeRepository.php | 184 +-- .../ObjectGroup/CreatesObjectGroups.php | 20 +- .../ObjectGroup/ObjectGroupRepository.php | 78 +- .../PiggyBank/ModifiesPiggyBanks.php | 196 +-- .../PiggyBank/PiggyBankRepository.php | 110 +- .../Recurring/RecurringRepository.php | 172 +- app/Repositories/Rule/RuleRepository.php | 372 ++--- .../RuleGroup/RuleGroupRepository.php | 272 ++-- app/Repositories/Tag/OperationsRepository.php | 44 +- app/Repositories/Tag/TagRepository.php | 54 +- .../TransactionGroupRepository.php | 276 ++-- .../TransactionTypeRepository.php | 24 +- app/Repositories/User/UserRepository.php | 174 +- app/Rules/BelongsUser.php | 148 +- app/Rules/IsValidAttachmentModel.php | 28 +- app/Rules/UniqueAccountNumber.php | 44 +- app/Rules/UniqueIban.php | 51 +- app/Rules/ValidRecurrenceRepetitionValue.php | 2 +- .../FireflyIIIOrg/Update/UpdateRequest.php | 2 +- .../Destroy/AccountDestroyService.php | 126 +- .../Internal/Destroy/BudgetDestroyService.php | 2 +- .../Destroy/RecurrenceDestroyService.php | 28 +- .../Internal/Support/AccountServiceTrait.php | 724 ++++----- .../Support/CreditRecalculateService.php | 134 +- .../Internal/Support/JournalServiceTrait.php | 468 +++--- .../Support/RecurringTransactionTrait.php | 182 +-- .../Internal/Update/AccountUpdateService.php | 214 +-- .../Internal/Update/BillUpdateService.php | 70 +- .../Internal/Update/CategoryUpdateService.php | 112 +- .../Internal/Update/GroupCloneService.php | 26 +- .../Internal/Update/GroupUpdateService.php | 80 +- .../Internal/Update/JournalUpdateService.php | 4 +- .../Update/RecurrenceUpdateService.php | 140 +- .../Webhook/StandardWebhookSender.php | 2 +- app/Support/Amount.php | 202 +-- .../Authentication/RemoteUserGuard.php | 34 +- app/Support/CacheProperties.php | 16 +- .../Chart/Budget/FrontpageChartGenerator.php | 98 +- .../Category/FrontpageChartGenerator.php | 32 +- app/Support/Export/ExportDataGenerator.php | 240 +-- app/Support/FireflyConfig.php | 82 +- app/Support/Form/AccountForm.php | 60 +- app/Support/Form/CurrencyForm.php | 184 +-- app/Support/Form/FormSupport.php | 74 +- .../Http/Api/ConvertsExchangeRates.php | 248 +-- .../Http/Controllers/ChartGeneration.php | 2 +- app/Support/Http/Controllers/CreateStuff.php | 2 +- app/Support/Http/Controllers/CronRunner.php | 130 +- .../Http/Controllers/PeriodOverview.php | 220 +-- .../Http/Controllers/RequestInformation.php | 36 +- app/Support/Logging/AuditProcessor.php | 2 +- app/Support/Navigation.php | 176 +- app/Support/ParseDateString.php | 418 ++--- app/Support/Preferences.php | 222 +-- .../Report/Budget/BudgetReportGenerator.php | 300 ++-- .../Category/CategoryReportGenerator.php | 108 +- .../Administration/AdministrationTrait.php | 21 +- app/Support/Request/AppendsLocationData.php | 94 +- app/Support/Request/ConvertsDataTypes.php | 170 +- app/Support/Search/OperatorQuerySearch.php | 1446 ++++++++--------- app/Support/Steam.php | 262 +-- app/Support/System/OAuthKeys.php | 90 +- app/Support/Twig/AmountFormat.php | 74 +- app/Support/Twig/General.php | 432 ++--- app/Support/Twig/Rule.php | 48 +- app/Support/Twig/TransactionGroupTwig.php | 390 ++--- .../Actions/AppendNotesToDescription.php | 2 +- .../Actions/ConvertToDeposit.php | 122 +- .../Actions/ConvertToTransfer.php | 144 +- .../Actions/ConvertToWithdrawal.php | 37 +- .../Actions/UpdatePiggybank.php | 86 +- .../Engine/RuleEngineInterface.php | 12 +- .../Engine/SearchRuleEngine.php | 436 ++--- app/Transformers/AccountTransformer.php | 84 +- app/Transformers/BillTransformer.php | 94 +- app/Transformers/RecurrenceTransformer.php | 138 +- app/Transformers/RuleTransformer.php | 52 +- .../TransactionGroupTransformer.php | 846 +++++----- app/Transformers/V2/AccountTransformer.php | 26 +- .../V2/TransactionGroupTransformer.php | 102 +- .../WebhookMessageTransformer.php | 2 +- app/Validation/Account/DepositValidation.php | 38 +- app/Validation/Account/OBValidation.php | 18 +- app/Validation/Account/TransferValidation.php | 30 +- .../Account/WithdrawalValidation.php | 38 +- app/Validation/AccountValidator.php | 86 +- app/Validation/CurrencyValidation.php | 16 +- app/Validation/FireflyValidator.php | 257 ++- app/Validation/GroupValidation.php | 18 +- app/Validation/RecurrenceValidation.php | 2 +- app/Validation/TransactionValidation.php | 1058 ++++++------ composer.lock | 96 +- config/app.php | 31 +- config/database.php | 8 +- config/mail.php | 2 +- .../2016_10_22_075804_changes_for_v410.php | 2 +- .../2017_04_13_163623_changes_for_v440.php | 2 +- .../2017_06_02_105232_changes_for_v450.php | 8 +- ...1_000001_create_oauth_auth_codes_table.php | 2 +- ...00002_create_oauth_access_tokens_table.php | 2 +- ...0003_create_oauth_refresh_tokens_table.php | 2 +- ...1_01_000004_create_oauth_clients_table.php | 2 +- ...te_oauth_personal_access_clients_table.php | 2 +- .../2018_03_19_141348_changes_for_v472.php | 8 +- .../2018_04_07_210913_changes_for_v473.php | 8 +- .../2018_09_05_195147_changes_for_v477.php | 4 +- .../2018_11_06_172532_changes_for_v479.php | 4 +- ...19_03_11_223700_fix_ldap_configuration.php | 4 +- .../2019_03_22_183214_changes_for_v480.php | 12 +- .../2020_06_30_202620_changes_for_v530a.php | 4 +- .../2020_07_24_162820_changes_for_v540.php | 14 +- .../2020_11_12_070604_changes_for_v550.php | 8 +- .../2021_03_12_061213_changes_for_v550b2.php | 4 +- ...064644_add_ldap_columns_to_users_table.php | 4 +- database/seeders/ExchangeRateSeeder.php | 44 +- 295 files changed, 12418 insertions(+), 12324 deletions(-) diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock index 42f25e31ad..01f3389111 100644 --- a/.ci/php-cs-fixer/composer.lock +++ b/.ci/php-cs-fixer/composer.lock @@ -744,16 +744,16 @@ }, { "name": "symfony/console", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f" + "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f", + "url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77", + "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77", "shasum": "" }, "require": { @@ -820,7 +820,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.10" + "source": "https://github.com/symfony/console/tree/v6.2.11" }, "funding": [ { @@ -836,7 +836,7 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:37:43+00:00" + "time": "2023-05-26T08:16:21+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1755,16 +1755,16 @@ }, { "name": "symfony/process", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e" + "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", + "url": "https://api.github.com/repos/symfony/process/zipball/97ae9721bead9d1a39b5650e2f4b7834b93b539c", + "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c", "shasum": "" }, "require": { @@ -1796,7 +1796,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.10" + "source": "https://github.com/symfony/process/tree/v6.2.11" }, "funding": [ { @@ -1812,7 +1812,7 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:56:57+00:00" + "time": "2023-05-19T07:42:48+00:00" }, { "name": "symfony/service-contracts", diff --git a/.ci/phpcs.sh b/.ci/phpcs.sh index 6cbc9f4fc2..ef57a468f8 100755 --- a/.ci/phpcs.sh +++ b/.ci/phpcs.sh @@ -32,7 +32,10 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd $SCRIPT_DIR/php-cs-fixer composer update rm -f .php-cs-fixer.cache +echo 'Removed cache...' +echo 'Running...' PHP_CS_FIXER_IGNORE_ENV=true ./vendor/bin/php-cs-fixer fix --config $SCRIPT_DIR/php-cs-fixer/.php-cs-fixer.php --allow-risky=yes +echo 'Done!' cd $SCRIPT_DIR/.. exit 0 diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php index 841dcd0c38..d82110febf 100644 --- a/app/Api/V1/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -92,7 +92,11 @@ class AccountController extends Controller if (in_array($account->accountType->type, $this->balanceTypes, true)) { $balance = app('steam')->balance($account, $date); - $nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false)); + $nameWithBalance = sprintf( + '%s (%s)', + $account->name, + app('amount')->formatAnything($currency, $balance, false) + ); } $return[] = [ @@ -113,10 +117,10 @@ class AccountController extends Controller usort( $return, function ($a, $b) use ($order) { - $pos_a = array_search($a['type'], $order, true); - $pos_b = array_search($b['type'], $order, true); + $posA = array_search($a['type'], $order, true); + $posB = array_search($b['type'], $order, true); - return $pos_a - $pos_b; + return $posA - $posB; } ); diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 18e8462f02..d0c534b167 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -74,6 +74,42 @@ abstract class Controller extends BaseController ); } + /** + * Method to help build URL's. + * + * @return string + */ + final protected function buildParams(): string + { + $return = '?'; + $params = []; + foreach ($this->parameters as $key => $value) { + if ('page' === $key) { + continue; + } + if ($value instanceof Carbon) { + $params[$key] = $value->format('Y-m-d'); + continue; + } + $params[$key] = $value; + } + + return $return.http_build_query($params); + } + + /** + * @return Manager + */ + final protected function getManager(): Manager + { + // create some objects: + $manager = new Manager(); + $baseUrl = request()->getSchemeAndHttpHost().'/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + return $manager; + } + /** * Method to grab all parameters from the URL. * @@ -110,7 +146,13 @@ abstract class Controller extends BaseController $obj = Carbon::parse($date); } catch (InvalidDateException|InvalidFormatException $e) { // don't care - app('log')->warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr($date, 0, 20), $e->getMessage())); + app('log')->warning( + sprintf( + 'Ignored invalid date "%s" in API controller parameter check: %s', + substr($date, 0, 20), + $e->getMessage() + ) + ); } } $bag->set($field, $obj); @@ -169,41 +211,4 @@ abstract class Controller extends BaseController return $bag; } - - - /** - * Method to help build URL's. - * - * @return string - */ - final protected function buildParams(): string - { - $return = '?'; - $params = []; - foreach ($this->parameters as $key => $value) { - if ('page' === $key) { - continue; - } - if ($value instanceof Carbon) { - $params[$key] = $value->format('Y-m-d'); - continue; - } - $params[$key] = $value; - } - - return $return.http_build_query($params); - } - - /** - * @return Manager - */ - final protected function getManager(): Manager - { - // create some objects: - $manager = new Manager(); - $baseUrl = request()->getSchemeAndHttpHost().'/api/v1'; - $manager->setSerializer(new JsonApiSerializer($baseUrl)); - - return $manager; - } } diff --git a/app/Api/V1/Controllers/Data/DestroyController.php b/app/Api/V1/Controllers/Data/DestroyController.php index 7da833a4b4..b5dda35ef6 100644 --- a/app/Api/V1/Controllers/Data/DestroyController.php +++ b/app/Api/V1/Controllers/Data/DestroyController.php @@ -201,91 +201,6 @@ class DestroyController extends Controller return response()->json([], 204); } - /** - * - */ - private function destroyBudgets(): void - { - /** @var AvailableBudgetRepositoryInterface $abRepository */ - $abRepository = app(AvailableBudgetRepositoryInterface::class); - $abRepository->destroyAll(); - - /** @var BudgetLimitRepositoryInterface $blRepository */ - $blRepository = app(BudgetLimitRepositoryInterface::class); - $blRepository->destroyAll(); - - /** @var BudgetRepositoryInterface $budgetRepository */ - $budgetRepository = app(BudgetRepositoryInterface::class); - $budgetRepository->destroyAll(); - } - - /** - * - */ - private function destroyBills(): void - { - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $repository->destroyAll(); - } - - /** - * - */ - private function destroyPiggyBanks(): void - { - /** @var PiggyBankRepositoryInterface $repository */ - $repository = app(PiggyBankRepositoryInterface::class); - $repository->destroyAll(); - } - - /** - * - */ - private function destroyRules(): void - { - /** @var RuleGroupRepositoryInterface $repository */ - $repository = app(RuleGroupRepositoryInterface::class); - $repository->destroyAll(); - } - - /** - * - */ - private function destroyRecurringTransactions(): void - { - /** @var RecurringRepositoryInterface $repository */ - $repository = app(RecurringRepositoryInterface::class); - $repository->destroyAll(); - } - - /** - * - */ - private function destroyCategories(): void - { - /** @var CategoryRepositoryInterface $categoryRepos */ - $categoryRepos = app(CategoryRepositoryInterface::class); - $categoryRepos->destroyAll(); - } - - /** - * - */ - private function destroyTags(): void - { - /** @var TagRepositoryInterface $tagRepository */ - $tagRepository = app(TagRepositoryInterface::class); - $tagRepository->destroyAll(); - } - - private function destroyObjectGroups(): void - { - /** @var ObjectGroupRepositoryInterface $repository */ - $repository = app(ObjectGroupRepositoryInterface::class); - $repository->deleteAll(); - } - /** * @param array $types */ @@ -311,6 +226,91 @@ class DestroyController extends Controller } } + /** + * + */ + private function destroyBills(): void + { + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $repository->destroyAll(); + } + + /** + * + */ + private function destroyBudgets(): void + { + /** @var AvailableBudgetRepositoryInterface $abRepository */ + $abRepository = app(AvailableBudgetRepositoryInterface::class); + $abRepository->destroyAll(); + + /** @var BudgetLimitRepositoryInterface $blRepository */ + $blRepository = app(BudgetLimitRepositoryInterface::class); + $blRepository->destroyAll(); + + /** @var BudgetRepositoryInterface $budgetRepository */ + $budgetRepository = app(BudgetRepositoryInterface::class); + $budgetRepository->destroyAll(); + } + + /** + * + */ + private function destroyCategories(): void + { + /** @var CategoryRepositoryInterface $categoryRepos */ + $categoryRepos = app(CategoryRepositoryInterface::class); + $categoryRepos->destroyAll(); + } + + private function destroyObjectGroups(): void + { + /** @var ObjectGroupRepositoryInterface $repository */ + $repository = app(ObjectGroupRepositoryInterface::class); + $repository->deleteAll(); + } + + /** + * + */ + private function destroyPiggyBanks(): void + { + /** @var PiggyBankRepositoryInterface $repository */ + $repository = app(PiggyBankRepositoryInterface::class); + $repository->destroyAll(); + } + + /** + * + */ + private function destroyRecurringTransactions(): void + { + /** @var RecurringRepositoryInterface $repository */ + $repository = app(RecurringRepositoryInterface::class); + $repository->destroyAll(); + } + + /** + * + */ + private function destroyRules(): void + { + /** @var RuleGroupRepositoryInterface $repository */ + $repository = app(RuleGroupRepositoryInterface::class); + $repository->destroyAll(); + } + + /** + * + */ + private function destroyTags(): void + { + /** @var TagRepositoryInterface $tagRepository */ + $tagRepository = app(TagRepositoryInterface::class); + $tagRepository->destroyAll(); + } + /** * @param array $types */ diff --git a/app/Api/V1/Controllers/Data/Export/ExportController.php b/app/Api/V1/Controllers/Data/Export/ExportController.php index 0363438b09..2949be621d 100644 --- a/app/Api/V1/Controllers/Data/Export/ExportController.php +++ b/app/Api/V1/Controllers/Data/Export/ExportController.php @@ -69,34 +69,6 @@ class ExportController extends Controller return $this->returnExport('accounts'); } - /** - * @param string $key - * - * @return LaravelResponse - * @throws FireflyException - */ - private function returnExport(string $key): LaravelResponse - { - $date = date('Y-m-d-H-i-s'); - $fileName = sprintf('%s-export-%s.csv', $date, $key); - $data = $this->exporter->export(); - - /** @var LaravelResponse $response */ - $response = response($data[$key]); - $response - ->header('Content-Description', 'File Transfer') - ->header('Content-Type', 'application/octet-stream') - ->header('Content-Disposition', 'attachment; filename='.$fileName) - ->header('Content-Transfer-Encoding', 'binary') - ->header('Connection', 'Keep-Alive') - ->header('Expires', '0') - ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') - ->header('Pragma', 'public') - ->header('Content-Length', (string)strlen($data[$key])); - - return $response; - } - /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/data/exportBills @@ -228,4 +200,32 @@ class ExportController extends Controller return $this->returnExport('transactions'); } + + /** + * @param string $key + * + * @return LaravelResponse + * @throws FireflyException + */ + private function returnExport(string $key): LaravelResponse + { + $date = date('Y-m-d-H-i-s'); + $fileName = sprintf('%s-export-%s.csv', $date, $key); + $data = $this->exporter->export(); + + /** @var LaravelResponse $response */ + $response = response($data[$key]); + $response + ->header('Content-Description', 'File Transfer') + ->header('Content-Type', 'application/octet-stream') + ->header('Content-Disposition', 'attachment; filename='.$fileName) + ->header('Content-Transfer-Encoding', 'binary') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', (string)strlen($data[$key])); + + return $response; + } } diff --git a/app/Api/V1/Controllers/Data/PurgeController.php b/app/Api/V1/Controllers/Data/PurgeController.php index 15023bca3b..9b4f8ae738 100644 --- a/app/Api/V1/Controllers/Data/PurgeController.php +++ b/app/Api/V1/Controllers/Data/PurgeController.php @@ -43,6 +43,7 @@ class PurgeController extends Controller * TODO cleanup and use repositories. * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/data/purgeData + * * @return JsonResponse */ public function purge(): JsonResponse diff --git a/app/Api/V1/Controllers/Models/Account/UpdateController.php b/app/Api/V1/Controllers/Models/Account/UpdateController.php index f897d9d04b..c80093adfe 100644 --- a/app/Api/V1/Controllers/Models/Account/UpdateController.php +++ b/app/Api/V1/Controllers/Models/Account/UpdateController.php @@ -29,8 +29,8 @@ use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Transformers\AccountTransformer; use Illuminate\Http\JsonResponse; -use League\Fractal\Resource\Item; use Illuminate\Support\Facades\Log; +use League\Fractal\Resource\Item; use Preferences; /** diff --git a/app/Api/V1/Controllers/Models/Attachment/StoreController.php b/app/Api/V1/Controllers/Models/Attachment/StoreController.php index a1b8338449..6c56d46565 100644 --- a/app/Api/V1/Controllers/Models/Attachment/StoreController.php +++ b/app/Api/V1/Controllers/Models/Attachment/StoreController.php @@ -34,8 +34,8 @@ use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use League\Fractal\Resource\Item; use Illuminate\Support\Facades\Log; +use League\Fractal\Resource\Item; /** * Class StoreController diff --git a/app/Api/V1/Controllers/Models/Transaction/ShowController.php b/app/Api/V1/Controllers/Models/Transaction/ShowController.php index d2f796392b..217ad9e53b 100644 --- a/app/Api/V1/Controllers/Models/Transaction/ShowController.php +++ b/app/Api/V1/Controllers/Models/Transaction/ShowController.php @@ -97,21 +97,6 @@ class ShowController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } - /** - * This endpoint is documented at: - * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransactionByJournal - * - * Show a single transaction, by transaction journal. - * - * @param TransactionJournal $transactionJournal - * - * @return JsonResponse - */ - public function showJournal(TransactionJournal $transactionJournal): JsonResponse - { - return $this->show($transactionJournal->transactionGroup); - } - /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransaction @@ -148,4 +133,19 @@ class ShowController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } + + /** + * This endpoint is documented at: + * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransactionByJournal + * + * Show a single transaction, by transaction journal. + * + * @param TransactionJournal $transactionJournal + * + * @return JsonResponse + */ + public function showJournal(TransactionJournal $transactionJournal): JsonResponse + { + return $this->show($transactionJournal->transactionGroup); + } } diff --git a/app/Api/V1/Controllers/Models/Transaction/StoreController.php b/app/Api/V1/Controllers/Models/Transaction/StoreController.php index fcf0c9a52e..3134996e7d 100644 --- a/app/Api/V1/Controllers/Models/Transaction/StoreController.php +++ b/app/Api/V1/Controllers/Models/Transaction/StoreController.php @@ -35,9 +35,9 @@ use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; use League\Fractal\Resource\Item; -use Illuminate\Support\Facades\Log; use Validator; /** diff --git a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php index d5d6720573..678c324850 100644 --- a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php +++ b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php @@ -32,8 +32,8 @@ use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; -use League\Fractal\Resource\Item; use Illuminate\Support\Facades\Log; +use League\Fractal\Resource\Item; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php index 2789cd0df2..c03a20aa56 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php @@ -97,6 +97,35 @@ class UpdateController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } + /** + * This endpoint is documented at: + * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/enableCurrency + * + * Enable a currency. + * + * @param TransactionCurrency $currency + * + * @return JsonResponse + * @throws FireflyException + * @throws JsonException + */ + public function enable(TransactionCurrency $currency): JsonResponse + { + $this->repository->enable($currency); + $manager = $this->getManager(); + + $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); + $this->parameters->set('defaultCurrency', $defaultCurrency); + + /** @var CurrencyTransformer $transformer */ + $transformer = app(CurrencyTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new Item($currency, $transformer, 'currencies'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); + } + /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/defaultCurrency @@ -128,35 +157,6 @@ class UpdateController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } - /** - * This endpoint is documented at: - * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/enableCurrency - * - * Enable a currency. - * - * @param TransactionCurrency $currency - * - * @return JsonResponse - * @throws FireflyException - * @throws JsonException - */ - public function enable(TransactionCurrency $currency): JsonResponse - { - $this->repository->enable($currency); - $manager = $this->getManager(); - - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); - - /** @var CurrencyTransformer $transformer */ - $transformer = app(CurrencyTransformer::class); - $transformer->setParameters($this->parameters); - - $resource = new Item($currency, $transformer, 'currencies'); - - return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); - } - /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/updateCurrency diff --git a/app/Api/V1/Controllers/Search/AccountController.php b/app/Api/V1/Controllers/Search/AccountController.php index ef636a3029..ac9c138c3c 100644 --- a/app/Api/V1/Controllers/Search/AccountController.php +++ b/app/Api/V1/Controllers/Search/AccountController.php @@ -32,10 +32,10 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\Log; use JsonException; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; -use Illuminate\Support\Facades\Log; /** * Class AccountController @@ -61,6 +61,7 @@ class AccountController extends Controller /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/search/searchAccounts + * * @param Request $request * * @return JsonResponse|Response diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php index 21f44fd12c..ab7f56a8cb 100644 --- a/app/Api/V1/Controllers/Summary/BasicController.php +++ b/app/Api/V1/Controllers/Summary/BasicController.php @@ -121,6 +121,30 @@ class BasicController extends Controller return response()->json($return); } + /** + * Check if date is outside session range. + * + * @param Carbon $date + * + * @param Carbon $start + * @param Carbon $end + * + * @return bool + */ + protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference + { + $result = false; + if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { + $result = true; + } + // start and end in the past? use $end + if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { + $result = true; + } + + return $result; + } + /** * @param Carbon $start * @param Carbon $end @@ -399,28 +423,4 @@ class BasicController extends Controller return $return; } - - /** - * Check if date is outside session range. - * - * @param Carbon $date - * - * @param Carbon $start - * @param Carbon $end - * - * @return bool - */ - protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference - { - $result = false; - if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { - $result = true; - } - // start and end in the past? use $end - if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { - $result = true; - } - - return $result; - } } diff --git a/app/Api/V1/Controllers/System/ConfigurationController.php b/app/Api/V1/Controllers/System/ConfigurationController.php index daca5c4dbd..727660c2af 100644 --- a/app/Api/V1/Controllers/System/ConfigurationController.php +++ b/app/Api/V1/Controllers/System/ConfigurationController.php @@ -94,42 +94,6 @@ class ConfigurationController extends Controller return response()->json($return); } - /** - * Get all config values. - * - * @return array - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - private function getDynamicConfiguration(): array - { - $isDemoSite = app('fireflyconfig')->get('is_demo_site'); - $updateCheck = app('fireflyconfig')->get('permission_update_check'); - $lastCheck = app('fireflyconfig')->get('last_update_check'); - $singleUser = app('fireflyconfig')->get('single_user_mode'); - - return [ - 'is_demo_site' => $isDemoSite?->data, - 'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data, - 'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data, - 'single_user_mode' => $singleUser?->data, - ]; - } - - /** - * @return array - */ - private function getStaticConfiguration(): array - { - $list = EitherConfigKey::$static; - $return = []; - foreach ($list as $key) { - $return[$key] = config($key); - } - - return $return; - } - /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/configuration/getSingleConfiguration @@ -200,4 +164,40 @@ class ConfigurationController extends Controller return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE); } + + /** + * Get all config values. + * + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function getDynamicConfiguration(): array + { + $isDemoSite = app('fireflyconfig')->get('is_demo_site'); + $updateCheck = app('fireflyconfig')->get('permission_update_check'); + $lastCheck = app('fireflyconfig')->get('last_update_check'); + $singleUser = app('fireflyconfig')->get('single_user_mode'); + + return [ + 'is_demo_site' => $isDemoSite?->data, + 'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data, + 'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data, + 'single_user_mode' => $singleUser?->data, + ]; + } + + /** + * @return array + */ + private function getStaticConfiguration(): array + { + $list = EitherConfigKey::$static; + $return = []; + foreach ($list as $key) { + $return[$key] = config($key); + } + + return $return; + } } diff --git a/app/Api/V1/Controllers/System/CronController.php b/app/Api/V1/Controllers/System/CronController.php index 8faa85a521..fbe211dc9c 100644 --- a/app/Api/V1/Controllers/System/CronController.php +++ b/app/Api/V1/Controllers/System/CronController.php @@ -57,7 +57,7 @@ class CronController extends Controller if (true === config('cer.enabled')) { $return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']); } - $return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']); + $return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']); return response()->json($return); } diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index 9857b2e074..0fc63fccab 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -122,6 +122,7 @@ class ShowController extends Controller * * @param Webhook $webhook * @param TransactionGroup $group + * * @return JsonResponse */ public function triggerTransaction(Webhook $webhook, TransactionGroup $group): JsonResponse @@ -141,6 +142,7 @@ class ShowController extends Controller // trigger event to send them: event(new RequestedSendWebhookMessages()); + return response()->json([], 204); } } diff --git a/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php index 2f73415dd2..dfba30b9bc 100644 --- a/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php +++ b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php @@ -83,6 +83,7 @@ class MoveTransactionsRequest extends FormRequest /** * @param Validator $validator + * * @return void */ private function validateMove(Validator $validator): void diff --git a/app/Api/V1/Requests/Data/Bulk/TransactionRequest.php b/app/Api/V1/Requests/Data/Bulk/TransactionRequest.php index 50599ab848..926b51e308 100644 --- a/app/Api/V1/Requests/Data/Bulk/TransactionRequest.php +++ b/app/Api/V1/Requests/Data/Bulk/TransactionRequest.php @@ -30,9 +30,9 @@ use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Validation\Api\Data\Bulk\ValidatesBulkTransactionQuery; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\Validator; use JsonException; -use Illuminate\Support\Facades\Log; /** * Class TransactionRequest diff --git a/app/Api/V1/Requests/Insight/GenericRequest.php b/app/Api/V1/Requests/Insight/GenericRequest.php index 0eb22bc584..2ca1f8a58b 100644 --- a/app/Api/V1/Requests/Insight/GenericRequest.php +++ b/app/Api/V1/Requests/Insight/GenericRequest.php @@ -82,28 +82,6 @@ class GenericRequest extends FormRequest return $return; } - /** - * - */ - private function parseAccounts(): void - { - if (0 !== $this->accounts->count()) { - return; - } - $repository = app(AccountRepositoryInterface::class); - $repository->setUser(auth()->user()); - $array = $this->get('accounts'); - if (is_array($array)) { - foreach ($array as $accountId) { - $accountId = (int)$accountId; - $account = $repository->find($accountId); - if (null !== $account) { - $this->accounts->push($account); - } - } - } - } - /** * @return Collection */ @@ -114,28 +92,6 @@ class GenericRequest extends FormRequest return $this->bills; } - /** - * - */ - private function parseBills(): void - { - if (0 !== $this->bills->count()) { - return; - } - $repository = app(BillRepositoryInterface::class); - $repository->setUser(auth()->user()); - $array = $this->get('bills'); - if (is_array($array)) { - foreach ($array as $billId) { - $billId = (int)$billId; - $bill = $repository->find($billId); - if (null !== $bill) { - $this->bills->push($bill); - } - } - } - } - /** * @return Collection */ @@ -146,28 +102,6 @@ class GenericRequest extends FormRequest return $this->budgets; } - /** - * - */ - private function parseBudgets(): void - { - if (0 !== $this->budgets->count()) { - return; - } - $repository = app(BudgetRepositoryInterface::class); - $repository->setUser(auth()->user()); - $array = $this->get('budgets'); - if (is_array($array)) { - foreach ($array as $budgetId) { - $budgetId = (int)$budgetId; - $budget = $repository->find($budgetId); - if (null !== $budget) { - $this->budgets->push($budget); - } - } - } - } - /** * @return Collection */ @@ -178,28 +112,6 @@ class GenericRequest extends FormRequest return $this->categories; } - /** - * - */ - private function parseCategories(): void - { - if (0 !== $this->categories->count()) { - return; - } - $repository = app(CategoryRepositoryInterface::class); - $repository->setUser(auth()->user()); - $array = $this->get('categories'); - if (is_array($array)) { - foreach ($array as $categoryId) { - $categoryId = (int)$categoryId; - $category = $repository->find($categoryId); - if (null !== $category) { - $this->categories->push($category); - } - } - } - } - /** * @return Carbon */ @@ -268,6 +180,114 @@ class GenericRequest extends FormRequest return $this->tags; } + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + // this is cheating, but it works to initialize the collections. + $this->accounts = new Collection(); + $this->budgets = new Collection(); + $this->categories = new Collection(); + $this->bills = new Collection(); + $this->tags = new Collection(); + + return [ + 'start' => 'required|date', + 'end' => 'required|date|after_or_equal:start', + ]; + } + + /** + * + */ + private function parseAccounts(): void + { + if (0 !== $this->accounts->count()) { + return; + } + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $array = $this->get('accounts'); + if (is_array($array)) { + foreach ($array as $accountId) { + $accountId = (int)$accountId; + $account = $repository->find($accountId); + if (null !== $account) { + $this->accounts->push($account); + } + } + } + } + + /** + * + */ + private function parseBills(): void + { + if (0 !== $this->bills->count()) { + return; + } + $repository = app(BillRepositoryInterface::class); + $repository->setUser(auth()->user()); + $array = $this->get('bills'); + if (is_array($array)) { + foreach ($array as $billId) { + $billId = (int)$billId; + $bill = $repository->find($billId); + if (null !== $bill) { + $this->bills->push($bill); + } + } + } + } + + /** + * + */ + private function parseBudgets(): void + { + if (0 !== $this->budgets->count()) { + return; + } + $repository = app(BudgetRepositoryInterface::class); + $repository->setUser(auth()->user()); + $array = $this->get('budgets'); + if (is_array($array)) { + foreach ($array as $budgetId) { + $budgetId = (int)$budgetId; + $budget = $repository->find($budgetId); + if (null !== $budget) { + $this->budgets->push($budget); + } + } + } + } + + /** + * + */ + private function parseCategories(): void + { + if (0 !== $this->categories->count()) { + return; + } + $repository = app(CategoryRepositoryInterface::class); + $repository->setUser(auth()->user()); + $array = $this->get('categories'); + if (is_array($array)) { + foreach ($array as $categoryId) { + $categoryId = (int)$categoryId; + $category = $repository->find($categoryId); + if (null !== $category) { + $this->categories->push($category); + } + } + } + } + /** * */ @@ -289,24 +309,4 @@ class GenericRequest extends FormRequest } } } - - /** - * The rules that the incoming request must be matched against. - * - * @return array - */ - public function rules(): array - { - // this is cheating, but it works to initialize the collections. - $this->accounts = new Collection(); - $this->budgets = new Collection(); - $this->categories = new Collection(); - $this->bills = new Collection(); - $this->tags = new Collection(); - - return [ - 'start' => 'required|date', - 'end' => 'required|date|after_or_equal:start', - ]; - } } diff --git a/app/Api/V1/Requests/Models/Account/UpdateRequest.php b/app/Api/V1/Requests/Models/Account/UpdateRequest.php index ac3ecd8171..08ba545aa8 100644 --- a/app/Api/V1/Requests/Models/Account/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Account/UpdateRequest.php @@ -33,7 +33,6 @@ use FireflyIII\Support\Request\AppendsLocationData; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Support\Facades\Log; /** * Class UpdateRequest @@ -77,6 +76,7 @@ class UpdateRequest extends FormRequest 'liability_start_date' => ['liability_start_date', 'date'], ]; $data = $this->getAllData($fields); + return $this->appendLocationData($data, null); } diff --git a/app/Api/V1/Requests/Models/Bill/StoreRequest.php b/app/Api/V1/Requests/Models/Bill/StoreRequest.php index 856e2c029f..8d3b990f7b 100644 --- a/app/Api/V1/Requests/Models/Bill/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Bill/StoreRequest.php @@ -28,8 +28,8 @@ use FireflyIII\Rules\IsBoolean; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Class StoreRequest @@ -105,8 +105,8 @@ class StoreRequest extends FormRequest $validator->after( static function (Validator $validator) { $data = $validator->getData(); - $min = (string) ($data['amount_min'] ?? '0'); - $max = (string) ($data['amount_max'] ?? '0'); + $min = (string)($data['amount_min'] ?? '0'); + $max = (string)($data['amount_max'] ?? '0'); if (1 === bccomp($min, $max)) { $validator->errors()->add('amount_min', (string)trans('validation.amount_min_over_max')); diff --git a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php index fa33102464..22502451f9 100644 --- a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php @@ -73,65 +73,6 @@ class StoreRequest extends FormRequest ]; } - /** - * Returns the transaction data as it is found in the submitted data. It's a complex method according to code - * standards but it just has a lot of ??-statements because of the fields that may or may not exist. - * - * @return array - */ - private function getTransactionData(): array - { - $return = []; - // transaction data: - /** @var array|null $transactions */ - $transactions = $this->get('transactions'); - if (null === $transactions) { - return []; - } - /** @var array $transaction */ - foreach ($transactions as $transaction) { - $return[] = $this->getSingleTransactionData($transaction); - } - - return $return; - } - - /** - * Returns the repetition data as it is found in the submitted data. - * - * @return array - */ - private function getRepetitionData(): array - { - $return = []; - // repetition data: - /** @var array|null $repetitions */ - $repetitions = $this->get('repetitions'); - if (null === $repetitions) { - return []; - } - /** @var array $repetition */ - foreach ($repetitions as $repetition) { - $current = []; - if (array_key_exists('type', $repetition)) { - $current['type'] = $repetition['type']; - } - if (array_key_exists('moment', $repetition)) { - $current['moment'] = $repetition['moment']; - } - if (array_key_exists('skip', $repetition)) { - $current['skip'] = (int)$repetition['skip']; - } - if (array_key_exists('weekend', $repetition)) { - $current['weekend'] = (int)$repetition['weekend']; - } - - $return[] = $current; - } - - return $return; - } - /** * The rules that the incoming request must be matched against. * @@ -198,4 +139,63 @@ class StoreRequest extends FormRequest } ); } + + /** + * Returns the repetition data as it is found in the submitted data. + * + * @return array + */ + private function getRepetitionData(): array + { + $return = []; + // repetition data: + /** @var array|null $repetitions */ + $repetitions = $this->get('repetitions'); + if (null === $repetitions) { + return []; + } + /** @var array $repetition */ + foreach ($repetitions as $repetition) { + $current = []; + if (array_key_exists('type', $repetition)) { + $current['type'] = $repetition['type']; + } + if (array_key_exists('moment', $repetition)) { + $current['moment'] = $repetition['moment']; + } + if (array_key_exists('skip', $repetition)) { + $current['skip'] = (int)$repetition['skip']; + } + if (array_key_exists('weekend', $repetition)) { + $current['weekend'] = (int)$repetition['weekend']; + } + + $return[] = $current; + } + + return $return; + } + + /** + * Returns the transaction data as it is found in the submitted data. It's a complex method according to code + * standards but it just has a lot of ??-statements because of the fields that may or may not exist. + * + * @return array + */ + private function getTransactionData(): array + { + $return = []; + // transaction data: + /** @var array|null $transactions */ + $transactions = $this->get('transactions'); + if (null === $transactions) { + return []; + } + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = $this->getSingleTransactionData($transaction); + } + + return $return; + } } diff --git a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php index a922abef1d..fcd7e21900 100644 --- a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php @@ -80,70 +80,6 @@ class UpdateRequest extends FormRequest return $return; } - /** - * Returns the repetition data as it is found in the submitted data. - * - * @return array|null - */ - private function getRepetitionData(): ?array - { - $return = []; - // repetition data: - /** @var array|null $repetitions */ - $repetitions = $this->get('repetitions'); - if (null === $repetitions) { - return null; - } - /** @var array $repetition */ - foreach ($repetitions as $repetition) { - $current = []; - if (array_key_exists('type', $repetition)) { - $current['type'] = $repetition['type']; - } - - if (array_key_exists('moment', $repetition)) { - $current['moment'] = (string)$repetition['moment']; - } - - if (array_key_exists('skip', $repetition)) { - $current['skip'] = (int)$repetition['skip']; - } - - if (array_key_exists('weekend', $repetition)) { - $current['weekend'] = (int)$repetition['weekend']; - } - $return[] = $current; - } - if (0 === count($return)) { - return null; - } - - return $return; - } - - /** - * Returns the transaction data as it is found in the submitted data. It's a complex method according to code - * standards but it just has a lot of ??-statements because of the fields that may or may not exist. - * - * @return array|null - */ - private function getTransactionData(): ?array - { - $return = []; - // transaction data: - /** @var array|null $transactions */ - $transactions = $this->get('transactions'); - if (null === $transactions) { - return null; - } - /** @var array $transaction */ - foreach ($transactions as $transaction) { - $return[] = $this->getSingleTransactionData($transaction); - } - - return $return; - } - /** * The rules that the incoming request must be matched against. * @@ -212,4 +148,68 @@ class UpdateRequest extends FormRequest } ); } + + /** + * Returns the repetition data as it is found in the submitted data. + * + * @return array|null + */ + private function getRepetitionData(): ?array + { + $return = []; + // repetition data: + /** @var array|null $repetitions */ + $repetitions = $this->get('repetitions'); + if (null === $repetitions) { + return null; + } + /** @var array $repetition */ + foreach ($repetitions as $repetition) { + $current = []; + if (array_key_exists('type', $repetition)) { + $current['type'] = $repetition['type']; + } + + if (array_key_exists('moment', $repetition)) { + $current['moment'] = (string)$repetition['moment']; + } + + if (array_key_exists('skip', $repetition)) { + $current['skip'] = (int)$repetition['skip']; + } + + if (array_key_exists('weekend', $repetition)) { + $current['weekend'] = (int)$repetition['weekend']; + } + $return[] = $current; + } + if (0 === count($return)) { + return null; + } + + return $return; + } + + /** + * Returns the transaction data as it is found in the submitted data. It's a complex method according to code + * standards but it just has a lot of ??-statements because of the fields that may or may not exist. + * + * @return array|null + */ + private function getTransactionData(): ?array + { + $return = []; + // transaction data: + /** @var array|null $transactions */ + $transactions = $this->get('transactions'); + if (null === $transactions) { + return null; + } + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = $this->getSingleTransactionData($transaction); + } + + return $return; + } } diff --git a/app/Api/V1/Requests/Models/Rule/StoreRequest.php b/app/Api/V1/Requests/Models/Rule/StoreRequest.php index a6087c9a17..578df69405 100644 --- a/app/Api/V1/Requests/Models/Rule/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Rule/StoreRequest.php @@ -67,48 +67,6 @@ class StoreRequest extends FormRequest return $data; } - /** - * @return array - */ - private function getRuleTriggers(): array - { - $triggers = $this->get('triggers'); - $return = []; - if (is_array($triggers)) { - foreach ($triggers as $trigger) { - $return[] = [ - 'type' => $trigger['type'], - 'value' => $trigger['value'], - 'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')), - 'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')), - ]; - } - } - - return $return; - } - - /** - * @return array - */ - private function getRuleActions(): array - { - $actions = $this->get('actions'); - $return = []; - if (is_array($actions)) { - foreach ($actions as $action) { - $return[] = [ - 'type' => $action['type'], - 'value' => $action['value'], - 'active' => $this->convertBoolean((string)($action['active'] ?? 'true')), - 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), - ]; - } - } - - return $return; - } - /** * The rules that the incoming request must be matched against. * @@ -162,21 +120,6 @@ class StoreRequest extends FormRequest ); } - /** - * Adds an error to the validator when there are no triggers in the array of data. - * - * @param Validator $validator - */ - protected function atLeastOneTrigger(Validator $validator): void - { - $data = $validator->getData(); - $triggers = $data['triggers'] ?? []; - // need at least one trigger - if (!is_countable($triggers) || 0 === count($triggers)) { - $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger')); - } - } - /** * Adds an error to the validator when there are no repetitions in the array of data. * @@ -192,6 +135,35 @@ class StoreRequest extends FormRequest } } + /** + * Adds an error to the validator when there are no ACTIVE actions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneActiveAction(Validator $validator): void + { + $data = $validator->getData(); + $actions = $data['actions'] ?? []; + // need at least one trigger + if (!is_countable($actions) || 0 === count($actions)) { + return; + } + $allInactive = true; + $inactiveIndex = 0; + foreach ($actions as $index => $action) { + $active = array_key_exists('active', $action) ? $action['active'] : true; // assume true + if (true === $active) { + $allInactive = false; + } + if (false === $active) { + $inactiveIndex = $index; + } + } + if (true === $allInactive) { + $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); + } + } + /** * Adds an error to the validator when there are no ACTIVE triggers in the array of data. * @@ -222,31 +194,59 @@ class StoreRequest extends FormRequest } /** - * Adds an error to the validator when there are no ACTIVE actions in the array of data. + * Adds an error to the validator when there are no triggers in the array of data. * * @param Validator $validator */ - protected function atLeastOneActiveAction(Validator $validator): void + protected function atLeastOneTrigger(Validator $validator): void { - $data = $validator->getData(); - $actions = $data['actions'] ?? []; + $data = $validator->getData(); + $triggers = $data['triggers'] ?? []; // need at least one trigger - if (!is_countable($actions) || 0 === count($actions)) { - return; - } - $allInactive = true; - $inactiveIndex = 0; - foreach ($actions as $index => $action) { - $active = array_key_exists('active', $action) ? $action['active'] : true; // assume true - if (true === $active) { - $allInactive = false; - } - if (false === $active) { - $inactiveIndex = $index; - } - } - if (true === $allInactive) { - $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); + if (!is_countable($triggers) || 0 === count($triggers)) { + $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger')); } } + + /** + * @return array + */ + private function getRuleActions(): array + { + $actions = $this->get('actions'); + $return = []; + if (is_array($actions)) { + foreach ($actions as $action) { + $return[] = [ + 'type' => $action['type'], + 'value' => $action['value'], + 'active' => $this->convertBoolean((string)($action['active'] ?? 'true')), + 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), + ]; + } + } + + return $return; + } + + /** + * @return array + */ + private function getRuleTriggers(): array + { + $triggers = $this->get('triggers'); + $return = []; + if (is_array($triggers)) { + foreach ($triggers as $trigger) { + $return[] = [ + 'type' => $trigger['type'], + 'value' => $trigger['value'], + 'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')), + 'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')), + ]; + } + } + + return $return; + } } diff --git a/app/Api/V1/Requests/Models/Rule/TestRequest.php b/app/Api/V1/Requests/Models/Rule/TestRequest.php index b4708f3df6..b392a370cb 100644 --- a/app/Api/V1/Requests/Models/Rule/TestRequest.php +++ b/app/Api/V1/Requests/Models/Rule/TestRequest.php @@ -52,11 +52,24 @@ class TestRequest extends FormRequest } /** - * @return int + * @return array */ - private function getPage(): int + public function rules(): array { - return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + 'accounts' => '', + 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', + ]; + } + + /** + * @return array + */ + private function getAccounts(): array + { + return $this->get('accounts'); } /** @@ -70,23 +83,10 @@ class TestRequest extends FormRequest } /** - * @return array + * @return int */ - private function getAccounts(): array + private function getPage(): int { - return $this->get('accounts'); - } - - /** - * @return array - */ - public function rules(): array - { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - 'accounts' => '', - 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', - ]; + return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); } } diff --git a/app/Api/V1/Requests/Models/Rule/TriggerRequest.php b/app/Api/V1/Requests/Models/Rule/TriggerRequest.php index ce5cc1b9a0..88b39b02b7 100644 --- a/app/Api/V1/Requests/Models/Rule/TriggerRequest.php +++ b/app/Api/V1/Requests/Models/Rule/TriggerRequest.php @@ -49,24 +49,6 @@ class TriggerRequest extends FormRequest ]; } - /** - * @param string $field - * - * @return Carbon|null - */ - private function getDate(string $field): ?Carbon - { - return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($this->query($field), 0, 10)); - } - - /** - * @return array - */ - private function getAccounts(): array - { - return $this->get('accounts') ?? []; - } - /** * @return array */ @@ -79,4 +61,22 @@ class TriggerRequest extends FormRequest 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', ]; } + + /** + * @return array + */ + private function getAccounts(): array + { + return $this->get('accounts') ?? []; + } + + /** + * @param string $field + * + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($this->query($field), 0, 10)); + } } diff --git a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php index 29449397b0..ec951d8a7f 100644 --- a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php @@ -73,56 +73,6 @@ class UpdateRequest extends FormRequest return $return; } - /** - * @return array|null - */ - private function getRuleTriggers(): ?array - { - if (!$this->has('triggers')) { - return null; - } - $triggers = $this->get('triggers'); - $return = []; - if (is_array($triggers)) { - foreach ($triggers as $trigger) { - $active = array_key_exists('active', $trigger) ? $trigger['active'] : true; - $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false; - $return[] = [ - 'type' => $trigger['type'], - 'value' => $trigger['value'], - 'active' => $active, - 'stop_processing' => $stopProcessing, - ]; - } - } - - return $return; - } - - /** - * @return array|null - */ - private function getRuleActions(): ?array - { - if (!$this->has('actions')) { - return null; - } - $actions = $this->get('actions'); - $return = []; - if (is_array($actions)) { - foreach ($actions as $action) { - $return[] = [ - 'type' => $action['type'], - 'value' => $action['value'], - 'active' => $this->convertBoolean((string)($action['active'] ?? 'false')), - 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), - ]; - } - } - - return $return; - } - /** * The rules that the incoming request must be matched against. * @@ -180,6 +130,21 @@ class UpdateRequest extends FormRequest ); } + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneAction(Validator $validator): void + { + $data = $validator->getData(); + $actions = $data['actions'] ?? null; + // need at least one action + if (is_array($actions) && 0 === count($actions)) { + $validator->errors()->add('title', (string)trans('validation.at_least_one_action')); + } + } + /** * Adds an error to the validator when there are no repetitions in the array of data. * @@ -195,6 +160,36 @@ class UpdateRequest extends FormRequest } } + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneValidAction(Validator $validator): void + { + $data = $validator->getData(); + $actions = $data['actions'] ?? []; + $allInactive = true; + $inactiveIndex = 0; + // need at least one action + if (is_array($actions) && 0 === count($actions)) { + return; + } + + foreach ($actions as $index => $action) { + $active = array_key_exists('active', $action) ? $action['active'] : true; // assume true + if (true === $active) { + $allInactive = false; + } + if (false === $active) { + $inactiveIndex = $index; + } + } + if (true === $allInactive) { + $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); + } + } + /** * Adds an error to the validator when there are no repetitions in the array of data. * @@ -225,47 +220,52 @@ class UpdateRequest extends FormRequest } /** - * Adds an error to the validator when there are no repetitions in the array of data. - * - * @param Validator $validator + * @return array|null */ - protected function atLeastOneAction(Validator $validator): void + private function getRuleActions(): ?array { - $data = $validator->getData(); - $actions = $data['actions'] ?? null; - // need at least one action - if (is_array($actions) && 0 === count($actions)) { - $validator->errors()->add('title', (string)trans('validation.at_least_one_action')); + if (!$this->has('actions')) { + return null; } + $actions = $this->get('actions'); + $return = []; + if (is_array($actions)) { + foreach ($actions as $action) { + $return[] = [ + 'type' => $action['type'], + 'value' => $action['value'], + 'active' => $this->convertBoolean((string)($action['active'] ?? 'false')), + 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), + ]; + } + } + + return $return; } /** - * Adds an error to the validator when there are no repetitions in the array of data. - * - * @param Validator $validator + * @return array|null */ - protected function atLeastOneValidAction(Validator $validator): void + private function getRuleTriggers(): ?array { - $data = $validator->getData(); - $actions = $data['actions'] ?? []; - $allInactive = true; - $inactiveIndex = 0; - // need at least one action - if (is_array($actions) && 0 === count($actions)) { - return; + if (!$this->has('triggers')) { + return null; + } + $triggers = $this->get('triggers'); + $return = []; + if (is_array($triggers)) { + foreach ($triggers as $trigger) { + $active = array_key_exists('active', $trigger) ? $trigger['active'] : true; + $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false; + $return[] = [ + 'type' => $trigger['type'], + 'value' => $trigger['value'], + 'active' => $active, + 'stop_processing' => $stopProcessing, + ]; + } } - foreach ($actions as $index => $action) { - $active = array_key_exists('active', $action) ? $action['active'] : true; // assume true - if (true === $active) { - $allInactive = false; - } - if (false === $active) { - $inactiveIndex = $index; - } - } - if (true === $allInactive) { - $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); - } + return $return; } } diff --git a/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php b/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php index b48c050cba..15837e5678 100644 --- a/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php +++ b/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php @@ -49,24 +49,6 @@ class TestRequest extends FormRequest ]; } - /** - * @param string $field - * - * @return Carbon|null - */ - private function getDate(string $field): ?Carbon - { - return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); - } - - /** - * @return array - */ - private function getAccounts(): array - { - return $this->get('accounts'); - } - /** * @return array */ @@ -79,4 +61,22 @@ class TestRequest extends FormRequest 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', ]; } + + /** + * @return array + */ + private function getAccounts(): array + { + return $this->get('accounts'); + } + + /** + * @param string $field + * + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + } } diff --git a/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php b/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php index cd84f3fe2e..b057efc783 100644 --- a/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php +++ b/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php @@ -50,13 +50,14 @@ class TriggerRequest extends FormRequest } /** - * @param string $field - * - * @return Carbon|null + * @return array */ - private function getDate(string $field): ?Carbon + public function rules(): array { - return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + ]; } /** @@ -68,13 +69,12 @@ class TriggerRequest extends FormRequest } /** - * @return array + * @param string $field + * + * @return Carbon|null */ - public function rules(): array + private function getDate(string $field): ?Carbon { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - ]; + return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); } } diff --git a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php index d63cffcfcb..e9ebda90f0 100644 --- a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php @@ -35,8 +35,8 @@ use FireflyIII\Validation\CurrencyValidation; use FireflyIII\Validation\GroupValidation; use FireflyIII\Validation\TransactionValidation; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Class StoreRequest @@ -69,103 +69,6 @@ class StoreRequest extends FormRequest // TODO include location and ability to process it. } - /** - * Get transaction data. - * - * @return array - */ - private function getTransactionData(): array - { - $return = []; - /** - * @var array $transaction - */ - foreach ($this->get('transactions') as $transaction) { - $object = new NullArrayObject($transaction); - $return[] = [ - 'type' => $this->clearString($object['type'], false), - 'date' => $this->dateFromValue($object['date']), - 'order' => $this->integerFromValue((string)$object['order']), - - 'currency_id' => $this->integerFromValue((string)$object['currency_id']), - 'currency_code' => $this->clearString((string)$object['currency_code'], false), - - // foreign currency info: - 'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']), - 'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code'], false), - - // amount and foreign amount. Cannot be 0. - 'amount' => $this->clearString((string)$object['amount'], false), - 'foreign_amount' => $this->clearString((string)$object['foreign_amount'], false), - - // description. - 'description' => $this->clearString($object['description'], false), - - // source of transaction. If everything is null, assume cash account. - 'source_id' => $this->integerFromValue((string)$object['source_id']), - 'source_name' => $this->clearString((string)$object['source_name'], false), - 'source_iban' => $this->clearString((string)$object['source_iban'], false), - 'source_number' => $this->clearString((string)$object['source_number'], false), - 'source_bic' => $this->clearString((string)$object['source_bic'], false), - - // destination of transaction. If everything is null, assume cash account. - 'destination_id' => $this->integerFromValue((string)$object['destination_id']), - 'destination_name' => $this->clearString((string)$object['destination_name'], false), - 'destination_iban' => $this->clearString((string)$object['destination_iban'], false), - 'destination_number' => $this->clearString((string)$object['destination_number'], false), - 'destination_bic' => $this->clearString((string)$object['destination_bic'], false), - - // budget info - 'budget_id' => $this->integerFromValue((string)$object['budget_id']), - 'budget_name' => $this->clearString((string)$object['budget_name'], false), - - // category info - 'category_id' => $this->integerFromValue((string)$object['category_id']), - 'category_name' => $this->clearString((string)$object['category_name'], false), - - // journal bill reference. Optional. Will only work for withdrawals - 'bill_id' => $this->integerFromValue((string)$object['bill_id']), - 'bill_name' => $this->clearString((string)$object['bill_name'], false), - - // piggy bank reference. Optional. Will only work for transfers - 'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']), - 'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name'], false), - - // some other interesting properties - 'reconciled' => $this->convertBoolean((string)$object['reconciled']), - 'notes' => $this->clearString((string)$object['notes']), - 'tags' => $this->arrayFromValue($object['tags']), - - // all custom fields: - 'internal_reference' => $this->clearString((string)$object['internal_reference'], false), - 'external_id' => $this->clearString((string)$object['external_id'], false), - 'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')), - 'recurrence_id' => $this->integerFromValue($object['recurrence_id']), - 'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id'], false), - 'external_url' => $this->clearString((string)$object['external_url'], false), - - 'sepa_cc' => $this->clearString((string)$object['sepa_cc'], false), - 'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op'], false), - 'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id'], false), - 'sepa_db' => $this->clearString((string)$object['sepa_db'], false), - 'sepa_country' => $this->clearString((string)$object['sepa_country'], false), - 'sepa_ep' => $this->clearString((string)$object['sepa_ep'], false), - 'sepa_ci' => $this->clearString((string)$object['sepa_ci'], false), - 'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id'], false), - // custom date fields. Must be Carbon objects. Presence is optional. - 'interest_date' => $this->dateFromValue($object['interest_date']), - 'book_date' => $this->dateFromValue($object['book_date']), - 'process_date' => $this->dateFromValue($object['process_date']), - 'due_date' => $this->dateFromValue($object['due_date']), - 'payment_date' => $this->dateFromValue($object['payment_date']), - 'invoice_date' => $this->dateFromValue($object['invoice_date']), - - ]; - } - - return $return; - } - /** * The rules that the incoming request must be matched against. * @@ -294,4 +197,101 @@ class StoreRequest extends FormRequest } ); } + + /** + * Get transaction data. + * + * @return array + */ + private function getTransactionData(): array + { + $return = []; + /** + * @var array $transaction + */ + foreach ($this->get('transactions') as $transaction) { + $object = new NullArrayObject($transaction); + $return[] = [ + 'type' => $this->clearString($object['type'], false), + 'date' => $this->dateFromValue($object['date']), + 'order' => $this->integerFromValue((string)$object['order']), + + 'currency_id' => $this->integerFromValue((string)$object['currency_id']), + 'currency_code' => $this->clearString((string)$object['currency_code'], false), + + // foreign currency info: + 'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']), + 'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code'], false), + + // amount and foreign amount. Cannot be 0. + 'amount' => $this->clearString((string)$object['amount'], false), + 'foreign_amount' => $this->clearString((string)$object['foreign_amount'], false), + + // description. + 'description' => $this->clearString($object['description'], false), + + // source of transaction. If everything is null, assume cash account. + 'source_id' => $this->integerFromValue((string)$object['source_id']), + 'source_name' => $this->clearString((string)$object['source_name'], false), + 'source_iban' => $this->clearString((string)$object['source_iban'], false), + 'source_number' => $this->clearString((string)$object['source_number'], false), + 'source_bic' => $this->clearString((string)$object['source_bic'], false), + + // destination of transaction. If everything is null, assume cash account. + 'destination_id' => $this->integerFromValue((string)$object['destination_id']), + 'destination_name' => $this->clearString((string)$object['destination_name'], false), + 'destination_iban' => $this->clearString((string)$object['destination_iban'], false), + 'destination_number' => $this->clearString((string)$object['destination_number'], false), + 'destination_bic' => $this->clearString((string)$object['destination_bic'], false), + + // budget info + 'budget_id' => $this->integerFromValue((string)$object['budget_id']), + 'budget_name' => $this->clearString((string)$object['budget_name'], false), + + // category info + 'category_id' => $this->integerFromValue((string)$object['category_id']), + 'category_name' => $this->clearString((string)$object['category_name'], false), + + // journal bill reference. Optional. Will only work for withdrawals + 'bill_id' => $this->integerFromValue((string)$object['bill_id']), + 'bill_name' => $this->clearString((string)$object['bill_name'], false), + + // piggy bank reference. Optional. Will only work for transfers + 'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']), + 'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name'], false), + + // some other interesting properties + 'reconciled' => $this->convertBoolean((string)$object['reconciled']), + 'notes' => $this->clearString((string)$object['notes']), + 'tags' => $this->arrayFromValue($object['tags']), + + // all custom fields: + 'internal_reference' => $this->clearString((string)$object['internal_reference'], false), + 'external_id' => $this->clearString((string)$object['external_id'], false), + 'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')), + 'recurrence_id' => $this->integerFromValue($object['recurrence_id']), + 'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id'], false), + 'external_url' => $this->clearString((string)$object['external_url'], false), + + 'sepa_cc' => $this->clearString((string)$object['sepa_cc'], false), + 'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op'], false), + 'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id'], false), + 'sepa_db' => $this->clearString((string)$object['sepa_db'], false), + 'sepa_country' => $this->clearString((string)$object['sepa_country'], false), + 'sepa_ep' => $this->clearString((string)$object['sepa_ep'], false), + 'sepa_ci' => $this->clearString((string)$object['sepa_ci'], false), + 'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id'], false), + // custom date fields. Must be Carbon objects. Presence is optional. + 'interest_date' => $this->dateFromValue($object['interest_date']), + 'book_date' => $this->dateFromValue($object['book_date']), + 'process_date' => $this->dateFromValue($object['process_date']), + 'due_date' => $this->dateFromValue($object['due_date']), + 'payment_date' => $this->dateFromValue($object['payment_date']), + 'invoice_date' => $this->dateFromValue($object['invoice_date']), + + ]; + } + + return $return; + } } diff --git a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php index f1cb4383e5..9fd68335dd 100644 --- a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php @@ -62,7 +62,6 @@ class UpdateRequest extends FormRequest */ public function getAll(): array { - Log::debug(sprintf('Now in %s', __METHOD__)); $this->integerFields = [ 'order', @@ -165,7 +164,7 @@ class UpdateRequest extends FormRequest /** @var array $transaction */ foreach ($this->get('transactions') as $transaction) { - if(!is_array($transaction)) { + if (!is_array($transaction)) { throw new FireflyException('Invalid data submitted: transaction is not array.'); } // default response is to update nothing in the transaction: @@ -292,6 +291,7 @@ class UpdateRequest extends FormRequest /** * @param array $current * @param array $transaction + * * @return array */ private function getFloatData(array $current, array $transaction): array @@ -319,6 +319,7 @@ class UpdateRequest extends FormRequest public function rules(): array { Log::debug(sprintf('Now in %s', __METHOD__)); + return [ // basic fields for group: 'group_title' => 'between:1,1000', diff --git a/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php b/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php index b4b3866747..ed3d1e7c95 100644 --- a/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php @@ -64,9 +64,9 @@ class UpdateRequest extends FormRequest $linkType = $this->route()->parameter('linkType'); return [ - 'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1','max:1024'], - 'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1','max:1024'], - 'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1','max:1024'], + 'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1', 'max:1024'], + 'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1', 'max:1024'], + 'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1', 'max:1024'], ]; } } diff --git a/app/Api/V2/Controllers/Autocomplete/AccountController.php b/app/Api/V2/Controllers/Autocomplete/AccountController.php index b05425a549..33232c9754 100644 --- a/app/Api/V2/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V2/Controllers/Autocomplete/AccountController.php @@ -29,8 +29,8 @@ use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -43,9 +43,9 @@ class AccountController extends Controller { use AccountFilter; - private array $balanceTypes; private AdminAccountRepositoryInterface $adminRepository; - private AccountRepositoryInterface $repository; + private array $balanceTypes; + private AccountRepositoryInterface $repository; /** * AccountController constructor. @@ -56,8 +56,8 @@ class AccountController extends Controller $this->middleware( function ($request, $next) { /** @var User $user */ - $user = auth()->user(); - $this->repository = app(AccountRepositoryInterface::class); + $user = auth()->user(); + $this->repository = app(AccountRepositoryInterface::class); $this->adminRepository = app(AdminAccountRepositoryInterface::class); return $next($request); diff --git a/app/Api/V2/Controllers/Chart/AccountController.php b/app/Api/V2/Controllers/Chart/AccountController.php index e8d42a6595..7ae5182c25 100644 --- a/app/Api/V2/Controllers/Chart/AccountController.php +++ b/app/Api/V2/Controllers/Chart/AccountController.php @@ -53,6 +53,7 @@ class AccountController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(AccountRepositoryInterface::class); + return $next($request); } ); @@ -63,6 +64,7 @@ class AccountController extends Controller * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/charts/getChartAccountOverview * * @param DateRequest $request + * * @return JsonResponse * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface diff --git a/app/Api/V2/Controllers/Controller.php b/app/Api/V2/Controllers/Controller.php index e36a16f263..c00ad133b8 100644 --- a/app/Api/V2/Controllers/Controller.php +++ b/app/Api/V2/Controllers/Controller.php @@ -64,6 +64,54 @@ class Controller extends BaseController } } + /** + * @param string $key + * @param LengthAwarePaginator $paginator + * @param AbstractTransformer $transformer + * + * @return array + */ + final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array + { + $manager = new Manager(); + $baseUrl = request()->getSchemeAndHttpHost().'/api/v2'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $objects = $paginator->getCollection(); + + // the transformer, at this point, needs to collect information that ALL items in the collection + // require, like meta data and stuff like that, and save it for later. + $transformer->collectMetaData($objects); + + $resource = new FractalCollection($objects, $transformer, $key); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return $manager->createData($resource)->toArray(); + } + + /** + * Returns a JSON API object and returns it. + * + * @param string $key + * @param Model $object + * @param AbstractTransformer $transformer + * + * @return array + */ + final protected function jsonApiObject(string $key, Model $object, AbstractTransformer $transformer): array + { + // create some objects: + $manager = new Manager(); + $baseUrl = request()->getSchemeAndHttpHost().'/api/v2'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $transformer->collectMetaData(new Collection([$object])); + + $resource = new Item($object, $transformer, $key); + + return $manager->createData($resource)->toArray(); + } + /** * TODO duplicate from V1 controller * Method to grab all parameters from the URL. @@ -131,49 +179,4 @@ class Controller extends BaseController return $bag; } - - /** - * @param string $key - * @param LengthAwarePaginator $paginator - * @param AbstractTransformer $transformer - * @return array - */ - final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array - { - $manager = new Manager(); - $baseUrl = request()->getSchemeAndHttpHost().'/api/v2'; - $manager->setSerializer(new JsonApiSerializer($baseUrl)); - - $objects = $paginator->getCollection(); - - // the transformer, at this point, needs to collect information that ALL items in the collection - // require, like meta data and stuff like that, and save it for later. - $transformer->collectMetaData($objects); - - $resource = new FractalCollection($objects, $transformer, $key); - $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - - return $manager->createData($resource)->toArray(); - } - - /** - * Returns a JSON API object and returns it. - * - * @param string $key - * @param Model $object - * @param AbstractTransformer $transformer - * @return array - */ - final protected function jsonApiObject(string $key, Model $object, AbstractTransformer $transformer): array - { - // create some objects: - $manager = new Manager(); - $baseUrl = request()->getSchemeAndHttpHost().'/api/v2'; - $manager->setSerializer(new JsonApiSerializer($baseUrl)); - - $transformer->collectMetaData(new Collection([$object])); - - $resource = new Item($object, $transformer, $key); - return $manager->createData($resource)->toArray(); - } } diff --git a/app/Api/V2/Controllers/Model/Bill/SumController.php b/app/Api/V2/Controllers/Model/Bill/SumController.php index eacb1513d7..879f501278 100644 --- a/app/Api/V2/Controllers/Model/Bill/SumController.php +++ b/app/Api/V2/Controllers/Model/Bill/SumController.php @@ -48,6 +48,7 @@ class SumController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(BillRepositoryInterface::class); + return $next($request); } ); @@ -58,6 +59,7 @@ class SumController extends Controller * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsPaidTrSum * * @param DateRequest $request + * * @return JsonResponse */ public function paid(DateRequest $request): JsonResponse @@ -75,6 +77,7 @@ class SumController extends Controller * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsUnpaidTrSum * * @param DateRequest $request + * * @return JsonResponse */ public function unpaid(DateRequest $request): JsonResponse diff --git a/app/Api/V2/Controllers/Model/Budget/ListController.php b/app/Api/V2/Controllers/Model/Budget/ListController.php index b3f0cf53d1..65a7836833 100644 --- a/app/Api/V2/Controllers/Model/Budget/ListController.php +++ b/app/Api/V2/Controllers/Model/Budget/ListController.php @@ -41,6 +41,7 @@ class ListController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(BudgetRepositoryInterface::class); + return $next($request); } ); @@ -51,6 +52,7 @@ class ListController extends Controller * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/budgets/listBudgets * * @param Request $request + * * @return JsonResponse */ public function index(Request $request): JsonResponse @@ -61,6 +63,7 @@ class ListController extends Controller $paginator = new LengthAwarePaginator($collection, $total, $this->pageSize, $this->parameters->get('page')); $transformer = new BudgetTransformer(); + return response() ->api($this->jsonApiList('budgets', $paginator, $transformer)) ->header('Content-Type', self::CONTENT_TYPE); diff --git a/app/Api/V2/Controllers/Model/Budget/ShowController.php b/app/Api/V2/Controllers/Model/Budget/ShowController.php index bf079e4242..226fa451be 100644 --- a/app/Api/V2/Controllers/Model/Budget/ShowController.php +++ b/app/Api/V2/Controllers/Model/Budget/ShowController.php @@ -48,6 +48,7 @@ class ShowController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(BudgetRepositoryInterface::class); + return $next($request); } ); diff --git a/app/Api/V2/Controllers/Model/Budget/SumController.php b/app/Api/V2/Controllers/Model/Budget/SumController.php index 9f7b34dd56..80c39ac5df 100644 --- a/app/Api/V2/Controllers/Model/Budget/SumController.php +++ b/app/Api/V2/Controllers/Model/Budget/SumController.php @@ -48,6 +48,7 @@ class SumController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(BudgetRepositoryInterface::class); + return $next($request); } ); @@ -58,6 +59,7 @@ class SumController extends Controller * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/budgets/getBudgetedForBudget * * @param DateRequest $request + * * @return JsonResponse */ public function budgeted(DateRequest $request): JsonResponse @@ -72,7 +74,9 @@ class SumController extends Controller /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/budgets/getSpentForBudget + * * @param DateRequest $request + * * @return JsonResponse */ public function spent(DateRequest $request): JsonResponse diff --git a/app/Api/V2/Controllers/Model/BudgetLimit/ListController.php b/app/Api/V2/Controllers/Model/BudgetLimit/ListController.php index 591108da33..5c910ca77d 100644 --- a/app/Api/V2/Controllers/Model/BudgetLimit/ListController.php +++ b/app/Api/V2/Controllers/Model/BudgetLimit/ListController.php @@ -42,6 +42,7 @@ class ListController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(BudgetLimitRepositoryInterface::class); + return $next($request); } ); @@ -60,6 +61,7 @@ class ListController extends Controller $paginator = new LengthAwarePaginator($collection, $total, $this->pageSize, $this->parameters->get('page')); $transformer = new BudgetLimitTransformer(); + return response() ->api($this->jsonApiList('budget_limits', $paginator, $transformer)) ->header('Content-Type', self::CONTENT_TYPE); diff --git a/app/Api/V2/Controllers/NetWorthController.php b/app/Api/V2/Controllers/NetWorthController.php index e7dabc8133..b842a9a744 100644 --- a/app/Api/V2/Controllers/NetWorthController.php +++ b/app/Api/V2/Controllers/NetWorthController.php @@ -48,6 +48,7 @@ class NetWorthController extends Controller function ($request, $next) { $this->netWorth = app(NetWorthInterface::class); $this->netWorth->setUser(auth()->user()); + return $next($request); } ); @@ -56,7 +57,9 @@ class NetWorthController extends Controller /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/net-worth/getNetWorth + * * @param SingleDateRequest $request + * * @return JsonResponse */ public function get(SingleDateRequest $request): JsonResponse diff --git a/app/Api/V2/Controllers/System/PreferencesController.php b/app/Api/V2/Controllers/System/PreferencesController.php index 46b11a1a81..64d8aaf967 100644 --- a/app/Api/V2/Controllers/System/PreferencesController.php +++ b/app/Api/V2/Controllers/System/PreferencesController.php @@ -37,7 +37,9 @@ class PreferencesController extends Controller /** * This endpoint is documented at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/preferences/getPreference + * * @param Preference $preference + * * @return JsonResponse */ public function get(Preference $preference): JsonResponse diff --git a/app/Api/V2/Controllers/Transaction/List/AccountController.php b/app/Api/V2/Controllers/Transaction/List/AccountController.php index b85f88b4ac..324f541c90 100644 --- a/app/Api/V2/Controllers/Transaction/List/AccountController.php +++ b/app/Api/V2/Controllers/Transaction/List/AccountController.php @@ -46,6 +46,7 @@ class AccountController extends Controller * * @param ListRequest $request * @param Account $account + * * @return JsonResponse */ public function listTransactions(ListRequest $request, Account $account): JsonResponse diff --git a/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php b/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php index 8a382c6792..74d8eaf801 100644 --- a/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php +++ b/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php @@ -60,6 +60,7 @@ class AutocompleteRequest extends FormRequest $array = array_diff($array, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION]); /** @var User $user */ $user = auth()->user(); + return [ 'types' => $array, 'query' => $this->convertString('query'), diff --git a/app/Api/V2/Response/Sum/AutoSum.php b/app/Api/V2/Response/Sum/AutoSum.php index 7b5ad63ada..8303e9da1d 100644 --- a/app/Api/V2/Response/Sum/AutoSum.php +++ b/app/Api/V2/Response/Sum/AutoSum.php @@ -31,6 +31,7 @@ use Illuminate\Support\Collection; /** * Class AutoSum + * * @deprecated */ class AutoSum @@ -39,6 +40,7 @@ class AutoSum * @param Collection $objects * @param Closure $getCurrency * @param Closure $getSum + * * @return array */ public function autoSum(Collection $objects, Closure $getCurrency, Closure $getSum): array diff --git a/app/Console/Commands/Correction/CorrectAmounts.php b/app/Console/Commands/Correction/CorrectAmounts.php index b95abf7b97..462c9847a8 100644 --- a/app/Console/Commands/Correction/CorrectAmounts.php +++ b/app/Console/Commands/Correction/CorrectAmounts.php @@ -91,6 +91,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All auto budget amounts are positive.'); + return; } /** @var AutoBudget $item */ @@ -110,6 +111,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All available budget amounts are positive.'); + return; } /** @var AvailableBudget $item */ @@ -129,6 +131,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All bill amounts are positive.'); + return; } /** @var Bill $item */ @@ -148,6 +151,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All budget limit amounts are positive.'); + return; } /** @var BudgetLimit $item */ @@ -167,6 +171,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All currency exchange rates are positive.'); + return; } /** @var BudgetLimit $item */ @@ -186,6 +191,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All piggy bank amounts are positive.'); + return; } /** @var PiggyBankRepetition $item */ @@ -207,6 +213,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All recurring transaction amounts are positive.'); + return; } /** @var PiggyBankRepetition $item */ @@ -227,6 +234,7 @@ class CorrectAmounts extends Command $count = $set->count(); if (0 === $count) { $this->info('Correct: All piggy bank repetition amounts are positive.'); + return; } /** @var PiggyBankRepetition $item */ @@ -255,6 +263,7 @@ class CorrectAmounts extends Command } if (0 === $fixed) { $this->info('Correct: All rule trigger amounts are positive.'); + return; } $this->line(sprintf('Corrected %d rule trigger amount(s).', $fixed)); diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php index 87dd43e36f..633fcd6df9 100644 --- a/app/Console/Commands/Correction/CorrectDatabase.php +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -56,6 +56,7 @@ class CorrectDatabase extends Command // if table does not exist, return false if (!Schema::hasTable('users')) { $this->error('No "users"-table, will not continue.'); + return 1; } $commands = [ diff --git a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php index 3953eba431..f94ebdaefb 100644 --- a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php +++ b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php @@ -130,6 +130,7 @@ class CorrectOpeningBalanceCurrencies extends Command return $account; } } + return null; } diff --git a/app/Console/Commands/Correction/FixIbans.php b/app/Console/Commands/Correction/FixIbans.php index a465259e94..4b3e70a289 100644 --- a/app/Console/Commands/Correction/FixIbans.php +++ b/app/Console/Commands/Correction/FixIbans.php @@ -63,6 +63,7 @@ class FixIbans extends Command /** * @param Collection $accounts + * * @return void */ private function countAndCorrectIbans(Collection $accounts): void @@ -70,9 +71,9 @@ class FixIbans extends Command $set = []; /** @var Account $account */ foreach ($accounts as $account) { - $userId = (int)$account->user_id; + $userId = (int)$account->user_id; $set[$userId] = $set[$userId] ?? []; - $iban = (string)$account->iban; + $iban = (string)$account->iban; if ('' === $iban) { continue; } @@ -83,7 +84,8 @@ class FixIbans extends Command if (array_key_exists($iban, $set[$userId])) { // iban already in use! two exceptions exist: if ( - !(AccountType::EXPENSE === $set[$userId][$iban] && AccountType::REVENUE === $type) && // allowed combination + !(AccountType::EXPENSE === $set[$userId][$iban] && AccountType::REVENUE === $type) + && // allowed combination !(AccountType::REVENUE === $set[$userId][$iban] && AccountType::EXPENSE === $type) // also allowed combination. ) { $this->line( @@ -108,6 +110,7 @@ class FixIbans extends Command /** * @param Collection $accounts + * * @return void */ private function filterIbans(Collection $accounts): void diff --git a/app/Console/Commands/Correction/TriggerCreditCalculation.php b/app/Console/Commands/Correction/TriggerCreditCalculation.php index 14f86dd29f..0b781f3ad5 100644 --- a/app/Console/Commands/Correction/TriggerCreditCalculation.php +++ b/app/Console/Commands/Correction/TriggerCreditCalculation.php @@ -36,11 +36,13 @@ class TriggerCreditCalculation extends Command public function handle(): int { $this->processAccounts(); + return 0; } /** * @param Account $account + * * @return void */ private function processAccount(Account $account): void diff --git a/app/Console/Commands/Integrity/CreateGroupMemberships.php b/app/Console/Commands/Integrity/CreateGroupMemberships.php index 768df35247..336d194152 100644 --- a/app/Console/Commands/Integrity/CreateGroupMemberships.php +++ b/app/Console/Commands/Integrity/CreateGroupMemberships.php @@ -53,6 +53,7 @@ class CreateGroupMemberships extends Command /** * TODO move to helper. + * * @param User $user * * @throws FireflyException diff --git a/app/Console/Commands/Integrity/UpdateGroupInformation.php b/app/Console/Commands/Integrity/UpdateGroupInformation.php index a373f9eb0b..1fc377d7f8 100644 --- a/app/Console/Commands/Integrity/UpdateGroupInformation.php +++ b/app/Console/Commands/Integrity/UpdateGroupInformation.php @@ -85,6 +85,7 @@ class UpdateGroupInformation extends Command $group = $user->userGroup; if (null === $group) { $this->warn(sprintf('User "%s" has no group.', $user->email)); + return; } $set = [ @@ -112,6 +113,7 @@ class UpdateGroupInformation extends Command * @param User $user * @param UserGroup $group * @param string $className + * * @return void */ private function updateGroupInfoForObject(User $user, UserGroup $group, string $className): void @@ -120,6 +122,7 @@ class UpdateGroupInformation extends Command $result = $className::where('user_id', $user->id)->where('user_group_id', null)->update(['user_group_id' => $group->id]); } catch (QueryException $e) { $this->error(sprintf('Could not update group information for "%s" because of error "%s"', $className, $e->getMessage())); + return; } if (0 !== $result) { diff --git a/app/Console/Commands/System/ForceDecimalSize.php b/app/Console/Commands/System/ForceDecimalSize.php index 295174d201..41bf0c7928 100644 --- a/app/Console/Commands/System/ForceDecimalSize.php +++ b/app/Console/Commands/System/ForceDecimalSize.php @@ -54,35 +54,37 @@ class ForceDecimalSize extends Command protected $description = 'This command resizes DECIMAL columns in MySQL or PostgreSQL and correct amounts (only MySQL).'; protected $signature = 'firefly-iii:force-decimal-size'; private string $cast; - private array $classes = [ - 'accounts' => Account::class, - 'auto_budgets' => AutoBudget::class, - 'available_budgets' => AvailableBudget::class, - 'bills' => Bill::class, - 'budget_limits' => BudgetLimit::class, - 'piggy_bank_events' => PiggyBankEvent::class, - 'piggy_bank_repetitions' => PiggyBankRepetition::class, - 'piggy_banks' => PiggyBank::class, - 'recurrences_transactions' => RecurrenceTransaction::class, - 'transactions' => Transaction::class, - ]; + private array $classes + = [ + 'accounts' => Account::class, + 'auto_budgets' => AutoBudget::class, + 'available_budgets' => AvailableBudget::class, + 'bills' => Bill::class, + 'budget_limits' => BudgetLimit::class, + 'piggy_bank_events' => PiggyBankEvent::class, + 'piggy_bank_repetitions' => PiggyBankRepetition::class, + 'piggy_banks' => PiggyBank::class, + 'recurrences_transactions' => RecurrenceTransaction::class, + 'transactions' => Transaction::class, + ]; private string $operator; private string $regularExpression; - private array $tables = [ - 'accounts' => ['virtual_balance'], - 'auto_budgets' => ['amount'], - 'available_budgets' => ['amount'], - 'bills' => ['amount_min', 'amount_max'], - 'budget_limits' => ['amount'], - 'currency_exchange_rates' => ['rate', 'user_rate'], - 'limit_repetitions' => ['amount'], - 'piggy_bank_events' => ['amount'], - 'piggy_bank_repetitions' => ['currentamount'], - 'piggy_banks' => ['targetamount'], - 'recurrences_transactions' => ['amount', 'foreign_amount'], - 'transactions' => ['amount', 'foreign_amount'], - ]; + private array $tables + = [ + 'accounts' => ['virtual_balance'], + 'auto_budgets' => ['amount'], + 'available_budgets' => ['amount'], + 'bills' => ['amount_min', 'amount_max'], + 'budget_limits' => ['amount'], + 'currency_exchange_rates' => ['rate', 'user_rate'], + 'limit_repetitions' => ['amount'], + 'piggy_bank_events' => ['amount'], + 'piggy_bank_repetitions' => ['currentamount'], + 'piggy_banks' => ['targetamount'], + 'recurrences_transactions' => ['amount', 'foreign_amount'], + 'transactions' => ['amount', 'foreign_amount'], + ]; /** * Execute the console command. @@ -102,6 +104,7 @@ class ForceDecimalSize extends Command $this->updateDecimals(); } $this->line('Done!'); + return 0; } @@ -110,6 +113,7 @@ class ForceDecimalSize extends Command * * @param TransactionCurrency $currency * @param array $fields + * * @return void */ private function correctAccountAmounts(TransactionCurrency $currency, array $fields): void @@ -134,6 +138,7 @@ class ForceDecimalSize extends Command $result = $query->get(['accounts.*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All accounts in %s', $currency->code)); + return; } /** @var Account $account */ @@ -164,12 +169,14 @@ class ForceDecimalSize extends Command DB::connection()->getPdo()->sqliteCreateFunction('REGEXP', function ($pattern, $value) { mb_regex_encoding('UTF-8'); $pattern = trim($pattern, '"'); - return (false !== mb_ereg($pattern, (string) $value)) ? 1 : 0; + + return (false !== mb_ereg($pattern, (string)$value)) ? 1 : 0; }); } if (!in_array((string)config('database.default'), ['mysql', 'pgsql', 'sqlite'], true)) { $this->line(sprintf('Skip correcting amounts, does not support "%s"...', (string)config('database.default'))); + return; } $this->correctAmountsByCurrency(); @@ -195,6 +202,7 @@ class ForceDecimalSize extends Command * This method loops the available tables that may need fixing, and calls for the right method that can fix them. * * @param TransactionCurrency $currency + * * @return void * @throws FireflyException */ @@ -243,8 +251,10 @@ class ForceDecimalSize extends Command /** * This method fixes all auto budgets in currency $currency. + * * @param TransactionCurrency $currency * @param string $table + * * @return void */ private function correctGeneric(TransactionCurrency $currency, string $table): void @@ -271,6 +281,7 @@ class ForceDecimalSize extends Command $result = $query->get(['*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All %s in %s', $table, $currency->code)); + return; } /** @var Model $item */ @@ -294,6 +305,7 @@ class ForceDecimalSize extends Command * * @param TransactionCurrency $currency * @param array $fields + * * @return void */ private function correctPiggyAmounts(TransactionCurrency $currency, array $fields): void @@ -320,6 +332,7 @@ class ForceDecimalSize extends Command $result = $query->get(['piggy_banks.*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All piggy banks in %s', $currency->code)); + return; } /** @var PiggyBank $item */ @@ -340,8 +353,10 @@ class ForceDecimalSize extends Command /** * This method fixes all piggy bank events in currency $currency. + * * @param TransactionCurrency $currency * @param array $fields + * * @return void */ private function correctPiggyEventAmounts(TransactionCurrency $currency, array $fields): void @@ -369,6 +384,7 @@ class ForceDecimalSize extends Command $result = $query->get(['piggy_bank_events.*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All piggy bank events in %s', $currency->code)); + return; } /** @var PiggyBankEvent $item */ @@ -392,6 +408,7 @@ class ForceDecimalSize extends Command * * @param TransactionCurrency $currency * @param array $fields + * * @return void */ private function correctPiggyRepetitionAmounts(TransactionCurrency $currency, array $fields) @@ -419,6 +436,7 @@ class ForceDecimalSize extends Command $result = $query->get(['piggy_bank_repetitions.*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All piggy bank repetitions in %s', $currency->code)); + return; } /** @var PiggyBankRepetition $item */ @@ -441,6 +459,7 @@ class ForceDecimalSize extends Command * This method fixes all transactions in currency $currency. * * @param TransactionCurrency $currency + * * @return void */ private function correctTransactionAmounts(TransactionCurrency $currency): void @@ -482,6 +501,7 @@ class ForceDecimalSize extends Command $result = $query->get(['*']); if (0 === $result->count()) { $this->line(sprintf('Correct: All transactions in foreign currency %s', $currency->code)); + return; } /** @var Transaction $item */ @@ -534,6 +554,7 @@ class ForceDecimalSize extends Command switch ($type) { default: $this->error(sprintf('Cannot handle database type "%s".', $type)); + return; case 'pgsql': $query = sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE DECIMAL(32,12);', $name, $field); diff --git a/app/Console/Commands/System/ForceMigration.php b/app/Console/Commands/System/ForceMigration.php index dc28c322c0..7abd2f2da7 100644 --- a/app/Console/Commands/System/ForceMigration.php +++ b/app/Console/Commands/System/ForceMigration.php @@ -52,6 +52,7 @@ class ForceMigration extends Command /** * Execute the console command. + * * @throws FireflyException */ public function handle(): int @@ -69,8 +70,10 @@ class ForceMigration extends Command $user = $this->getUser(); Log::channel('audit')->info(sprintf('User #%d ("%s") forced migrations.', $user->id, $user->email)); $this->forceMigration(); + return 0; } + return 0; } diff --git a/app/Console/Commands/System/VerifySecurityAlerts.php b/app/Console/Commands/System/VerifySecurityAlerts.php index fdc3333f2c..8ea6b7f4b7 100644 --- a/app/Console/Commands/System/VerifySecurityAlerts.php +++ b/app/Console/Commands/System/VerifySecurityAlerts.php @@ -124,6 +124,7 @@ class VerifySecurityAlerts extends Command /** * @param array $array + * * @return void */ private function saveSecurityAdvisory(array $array): void diff --git a/app/Console/Commands/Tools/Cron.php b/app/Console/Commands/Tools/Cron.php index 83c9d1f222..559034fcd5 100644 --- a/app/Console/Commands/Tools/Cron.php +++ b/app/Console/Commands/Tools/Cron.php @@ -153,6 +153,7 @@ class Cron extends Command /** * @param bool $force * @param Carbon|null $date + * * @throws FireflyException * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface diff --git a/app/Console/Commands/Upgrade/DecryptDatabase.php b/app/Console/Commands/Upgrade/DecryptDatabase.php index f7fba6b891..443300be09 100644 --- a/app/Console/Commands/Upgrade/DecryptDatabase.php +++ b/app/Console/Commands/Upgrade/DecryptDatabase.php @@ -165,6 +165,7 @@ class DecryptDatabase extends Command /** * @param string $table * @param array $fields + * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ diff --git a/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php b/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php index 46f5c19283..7acb7df97b 100644 --- a/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php +++ b/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php @@ -85,6 +85,7 @@ class UpgradeLiabilitiesEight extends Command /** * @param Account $account + * * @return void */ private function deleteCreditTransaction(Account $account): void @@ -100,6 +101,7 @@ class UpgradeLiabilitiesEight extends Command $service = new TransactionGroupDestroyService(); $service->destroy($group); Log::debug(sprintf('Deleted liability credit group #%d', $group->id)); + return; } Log::debug('No liability credit journal found.'); @@ -107,6 +109,7 @@ class UpgradeLiabilitiesEight extends Command /** * @param $account + * * @return int */ private function deleteTransactions($account): int @@ -152,11 +155,13 @@ class UpgradeLiabilitiesEight extends Command $count++; } } + return $count; } /** * @param Account $account + * * @return bool */ private function hasBadOpening(Account $account): bool @@ -169,6 +174,7 @@ class UpgradeLiabilitiesEight extends Command ->first(['transaction_journals.*']); if (null === $openingJournal) { Log::debug('Account has no opening balance and can be skipped.'); + return false; } $liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') @@ -177,10 +183,12 @@ class UpgradeLiabilitiesEight extends Command ->first(['transaction_journals.*']); if (null === $liabilityJournal) { Log::debug('Account has no liability credit and can be skipped.'); + return false; } if (!$openingJournal->date->isSameDay($liabilityJournal->date)) { Log::debug('Account has opening/credit not on the same day.'); + return false; } Log::debug('Account has bad opening balance data.'); @@ -213,6 +221,7 @@ class UpgradeLiabilitiesEight extends Command /** * @param Account $account + * * @return void */ private function reverseOpeningBalance(Account $account): void @@ -235,6 +244,7 @@ class UpgradeLiabilitiesEight extends Command $source->save(); $dest->save(); Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id)); + return; } Log::warning('Did not find opening balance.'); diff --git a/app/Exceptions/GracefulNotFoundHandler.php b/app/Exceptions/GracefulNotFoundHandler.php index 3d2c97bbc9..cd78d542cd 100644 --- a/app/Exceptions/GracefulNotFoundHandler.php +++ b/app/Exceptions/GracefulNotFoundHandler.php @@ -168,45 +168,6 @@ class GracefulNotFoundHandler extends ExceptionHandler return redirect(route('accounts.index', [$shortType])); } - /** - * @param Request $request - * @param Throwable $exception - * - * @return Response - * @throws Throwable - */ - private function handleGroup(Request $request, Throwable $exception) - { - Log::debug('404 page is probably a deleted group. Redirect to overview of group types.'); - /** @var User $user */ - $user = auth()->user(); - $route = $request->route(); - $groupId = (int)$route->parameter('transactionGroup'); - - /** @var TransactionGroup|null $group */ - $group = $user->transactionGroups()->withTrashed()->find($groupId); - if (null === $group) { - Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId)); - - return parent::render($request, $exception); - } - /** @var TransactionJournal|null $journal */ - $journal = $group->transactionJournals()->withTrashed()->first(); - if (null === $journal) { - Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId)); - - return parent::render($request, $exception); - } - $type = $journal->transactionType->type; - $request->session()->reflash(); - - if (TransactionType::RECONCILIATION === $type) { - return redirect(route('accounts.index', ['asset'])); - } - - return redirect(route('transactions.index', [strtolower($type)])); - } - /** * @param Request $request * @param Throwable $exception @@ -250,4 +211,43 @@ class GracefulNotFoundHandler extends ExceptionHandler return parent::render($request, $exception); } + + /** + * @param Request $request + * @param Throwable $exception + * + * @return Response + * @throws Throwable + */ + private function handleGroup(Request $request, Throwable $exception) + { + Log::debug('404 page is probably a deleted group. Redirect to overview of group types.'); + /** @var User $user */ + $user = auth()->user(); + $route = $request->route(); + $groupId = (int)$route->parameter('transactionGroup'); + + /** @var TransactionGroup|null $group */ + $group = $user->transactionGroups()->withTrashed()->find($groupId); + if (null === $group) { + Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId)); + + return parent::render($request, $exception); + } + /** @var TransactionJournal|null $journal */ + $journal = $group->transactionJournals()->withTrashed()->first(); + if (null === $journal) { + Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId)); + + return parent::render($request, $exception); + } + $type = $journal->transactionType->type; + $request->session()->reflash(); + + if (TransactionType::RECONCILIATION === $type) { + return redirect(route('accounts.index', ['asset'])); + } + + return redirect(route('transactions.index', [strtolower($type)])); + } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index ade308c320..43ce8627ed 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -225,23 +225,6 @@ class Handler extends ExceptionHandler parent::report($e); } - /** - * @param Throwable $e - * - * @return bool - */ - private function shouldntReportLocal(Throwable $e): bool - { - return !is_null( - Arr::first( - $this->dontReport, - function ($type) use ($e) { - return $e instanceof $type; - } - ) - ); - } - /** * Convert a validation exception into a response. * @@ -280,4 +263,21 @@ class Handler extends ExceptionHandler return null !== $previousHost && $previousHost === $safeHost ? $previous : $safe; } + + /** + * @param Throwable $e + * + * @return bool + */ + private function shouldntReportLocal(Throwable $e): bool + { + return !is_null( + Arr::first( + $this->dontReport, + function ($type) use ($e) { + return $e instanceof $type; + } + ) + ); + } } diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index c126156134..42b8a309d5 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -33,8 +33,8 @@ use FireflyIII\Services\Internal\Support\AccountServiceTrait; use FireflyIII\Services\Internal\Support\LocationServiceTrait; use FireflyIII\Services\Internal\Update\AccountUpdateService; use FireflyIII\User; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Factory to create or return accounts. @@ -69,6 +69,47 @@ class AccountFactory $this->validFields = config('firefly.valid_account_fields'); } + /** + * @param array $data + * + * @return Account + * @throws FireflyException + * @throws JsonException + */ + public function create(array $data): Account + { + Log::debug('Now in AccountFactory::create()'); + $type = $this->getAccountType($data); + $data['iban'] = $this->filterIban($data['iban'] ?? null); + + // account may exist already: + $return = $this->find($data['name'], $type->type); + + if (null !== $return) { + return $return; + } + + $return = $this->createAccount($type, $data); + + event(new StoredAccount($return)); + + return $return; + } + + /** + * @param string $accountName + * @param string $accountType + * + * @return Account|null + */ + public function find(string $accountName, string $accountType): ?Account + { + Log::debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); + $type = AccountType::whereType($accountType)->first(); + + return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); + } + /** * @param string $accountName * @param string $accountType @@ -106,30 +147,12 @@ class AccountFactory } /** - * @param array $data - * - * @return Account - * @throws FireflyException - * @throws JsonException + * @param User $user */ - public function create(array $data): Account + public function setUser(User $user): void { - Log::debug('Now in AccountFactory::create()'); - $type = $this->getAccountType($data); - $data['iban'] = $this->filterIban($data['iban'] ?? null); - - // account may exist already: - $return = $this->find($data['name'], $type->type); - - if (null !== $return) { - return $return; - } - - $return = $this->createAccount($type, $data); - - event(new StoredAccount($return)); - - return $return; + $this->user = $user; + $this->accountRepository->setUser($user); } /** @@ -168,17 +191,32 @@ class AccountFactory } /** - * @param string $accountName - * @param string $accountType + * @param Account $account + * @param array $data * - * @return Account|null + * @return array + * @throws FireflyException + * @throws JsonException */ - public function find(string $accountName, string $accountType): ?Account + private function cleanMetaDataArray(Account $account, array $data): array { - Log::debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); - $type = AccountType::whereType($accountType)->first(); + $currencyId = array_key_exists('currency_id', $data) ? (int)$data['currency_id'] : 0; + $currencyCode = array_key_exists('currency_code', $data) ? (string)$data['currency_code'] : ''; + $accountRole = array_key_exists('account_role', $data) ? (string)$data['account_role'] : null; + $currency = $this->getCurrency($currencyId, $currencyCode); - return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); + // only asset account may have a role: + if ($account->accountType->type !== AccountType::ASSET) { + $accountRole = ''; + } + // only liability may have direction: + if (array_key_exists('liability_direction', $data) && !in_array($account->accountType->type, config('firefly.valid_liabilities'), true)) { + $data['liability_direction'] = null; + } + $data['account_role'] = $accountRole; + $data['currency_id'] = $currency->id; + + return $data; } /** @@ -254,29 +292,29 @@ class AccountFactory * @param Account $account * @param array $data * - * @return array * @throws FireflyException - * @throws JsonException */ - private function cleanMetaDataArray(Account $account, array $data): array + private function storeCreditLiability(Account $account, array $data) { - $currencyId = array_key_exists('currency_id', $data) ? (int)$data['currency_id'] : 0; - $currencyCode = array_key_exists('currency_code', $data) ? (string)$data['currency_code'] : ''; - $accountRole = array_key_exists('account_role', $data) ? (string)$data['account_role'] : null; - $currency = $this->getCurrency($currencyId, $currencyCode); - - // only asset account may have a role: - if ($account->accountType->type !== AccountType::ASSET) { - $accountRole = ''; + Log::debug('storeCreditLiability'); + $account->refresh(); + $accountType = $account->accountType->type; + $direction = $this->accountRepository->getMetaValue($account, 'liability_direction'); + $valid = config('firefly.valid_liabilities'); + if (in_array($accountType, $valid, true)) { + Log::debug('Is a liability with credit ("i am owed") direction.'); + if ($this->validOBData($data)) { + Log::debug('Has valid CL data.'); + $openingBalance = $data['opening_balance']; + $openingBalanceDate = $data['opening_balance_date']; + // store credit transaction. + $this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate); + } + if (!$this->validOBData($data)) { + Log::debug('Does NOT have valid CL data, deletr any CL transaction.'); + $this->deleteCreditTransaction($account); + } } - // only liability may have direction: - if (array_key_exists('liability_direction', $data) && !in_array($account->accountType->type, config('firefly.valid_liabilities'), true)) { - $data['liability_direction'] = null; - } - $data['account_role'] = $accountRole; - $data['currency_id'] = $currency->id; - - return $data; } /** @@ -344,35 +382,6 @@ class AccountFactory } } - /** - * @param Account $account - * @param array $data - * - * @throws FireflyException - */ - private function storeCreditLiability(Account $account, array $data) - { - Log::debug('storeCreditLiability'); - $account->refresh(); - $accountType = $account->accountType->type; - $direction = $this->accountRepository->getMetaValue($account, 'liability_direction'); - $valid = config('firefly.valid_liabilities'); - if (in_array($accountType, $valid, true)) { - Log::debug('Is a liability with credit ("i am owed") direction.'); - if ($this->validOBData($data)) { - Log::debug('Has valid CL data.'); - $openingBalance = $data['opening_balance']; - $openingBalanceDate = $data['opening_balance_date']; - // store credit transaction. - $this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate); - } - if (!$this->validOBData($data)) { - Log::debug('Does NOT have valid CL data, deletr any CL transaction.'); - $this->deleteCreditTransaction($account); - } - } - } - /** * @param Account $account * @param array $data @@ -396,13 +405,4 @@ class AccountFactory $updateService->setUser($account->user); $updateService->update($account, ['order' => $order]); } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - $this->accountRepository->setUser($user); - } } diff --git a/app/Factory/AccountMetaFactory.php b/app/Factory/AccountMetaFactory.php index 82341b9050..3977153c89 100644 --- a/app/Factory/AccountMetaFactory.php +++ b/app/Factory/AccountMetaFactory.php @@ -33,6 +33,16 @@ use Illuminate\Support\Facades\Log; */ class AccountMetaFactory { + /** + * @param array $data + * + * @return AccountMeta|null + */ + public function create(array $data): ?AccountMeta + { + return AccountMeta::create($data); + } + /** * Create update or delete meta data. * @@ -68,14 +78,4 @@ class AccountMetaFactory return $entry; } - - /** - * @param array $data - * - * @return AccountMeta|null - */ - public function create(array $data): ?AccountMeta - { - return AccountMeta::create($data); - } } diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index efc1f366a5..2028795ddb 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -30,8 +30,8 @@ use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; use FireflyIII\Services\Internal\Support\BillServiceTrait; use FireflyIII\User; use Illuminate\Database\QueryException; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class BillFactory diff --git a/app/Factory/CategoryFactory.php b/app/Factory/CategoryFactory.php index d7c2b63bb0..728d1c9d1a 100644 --- a/app/Factory/CategoryFactory.php +++ b/app/Factory/CategoryFactory.php @@ -36,6 +36,16 @@ class CategoryFactory { private User $user; + /** + * @param string $name + * + * @return Category|null + */ + public function findByName(string $name): ?Category + { + return $this->user->categories()->where('name', $name)->first(); + } + /** * @param int|null $categoryId * @param null|string $categoryName @@ -83,16 +93,6 @@ class CategoryFactory return null; } - /** - * @param string $name - * - * @return Category|null - */ - public function findByName(string $name): ?Category - { - return $this->user->categories()->where('name', $name)->first(); - } - /** * @param User $user */ diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php index 09b4be3b31..0851dd912c 100644 --- a/app/Factory/PiggyBankEventFactory.php +++ b/app/Factory/PiggyBankEventFactory.php @@ -25,7 +25,6 @@ namespace FireflyIII\Factory; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Support\Facades\Log; diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index 981c9c78b3..0514f3c8fc 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -29,9 +29,9 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; +use Illuminate\Support\Facades\Log; use Illuminate\Support\MessageBag; use JsonException; -use Illuminate\Support\Facades\Log; /** * Class RecurrenceFactory diff --git a/app/Factory/TagFactory.php b/app/Factory/TagFactory.php index a4e6a5d871..7b7d379a19 100644 --- a/app/Factory/TagFactory.php +++ b/app/Factory/TagFactory.php @@ -35,6 +35,40 @@ class TagFactory { private User $user; + /** + * @param array $data + * + * @return Tag|null + */ + public function create(array $data): ?Tag + { + $zoomLevel = 0 === (int)$data['zoom_level'] ? null : (int)$data['zoom_level']; + $latitude = 0.0 === (float)$data['latitude'] ? null : (float)$data['latitude']; // intentional float + $longitude = 0.0 === (float)$data['longitude'] ? null : (float)$data['longitude']; // intentional float + $array = [ + 'user_id' => $this->user->id, + 'tag' => trim($data['tag']), + 'tagMode' => 'nothing', + 'date' => $data['date'], + 'description' => $data['description'], + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + ]; + $tag = Tag::create($array); + if (null !== $tag && null !== $latitude && null !== $longitude) { + // create location object. + $location = new Location(); + $location->latitude = $latitude; + $location->longitude = $longitude; + $location->zoom_level = $zoomLevel; + $location->locatable()->associate($tag); + $location->save(); + } + + return $tag; + } + /** * @param string $tag * @@ -72,40 +106,6 @@ class TagFactory return $newTag; } - /** - * @param array $data - * - * @return Tag|null - */ - public function create(array $data): ?Tag - { - $zoomLevel = 0 === (int)$data['zoom_level'] ? null : (int)$data['zoom_level']; - $latitude = 0.0 === (float)$data['latitude'] ? null : (float)$data['latitude']; // intentional float - $longitude = 0.0 === (float)$data['longitude'] ? null : (float)$data['longitude']; // intentional float - $array = [ - 'user_id' => $this->user->id, - 'tag' => trim($data['tag']), - 'tagMode' => 'nothing', - 'date' => $data['date'], - 'description' => $data['description'], - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - ]; - $tag = Tag::create($array); - if (null !== $tag && null !== $latitude && null !== $longitude) { - // create location object. - $location = new Location(); - $location->latitude = $latitude; - $location->longitude = $longitude; - $location->zoom_level = $zoomLevel; - $location->locatable()->associate($tag); - $location->save(); - } - - return $tag; - } - /** * @param User $user */ diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index d01c67aa6e..6c3f3d4b47 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -80,6 +80,95 @@ class TransactionFactory return $this->create(app('steam')->negative($amount), $foreignAmount); } + /** + * Create transaction with positive amount (for destination accounts). + * + * @param string $amount + * @param string|null $foreignAmount + * + * @return Transaction + * @throws FireflyException + */ + public function createPositive(string $amount, ?string $foreignAmount): Transaction + { + if ('' === $foreignAmount) { + $foreignAmount = null; + } + if (null !== $foreignAmount) { + $foreignAmount = app('steam')->positive($foreignAmount); + } + + return $this->create(app('steam')->positive($amount), $foreignAmount); + } + + /** + * @param Account $account + * + + */ + public function setAccount(Account $account): void + { + $this->account = $account; + } + + /** + * @param array $accountInformation + */ + public function setAccountInformation(array $accountInformation): void + { + $this->accountInformation = $accountInformation; + } + + /** + * @param TransactionCurrency $currency + * + + */ + public function setCurrency(TransactionCurrency $currency): void + { + $this->currency = $currency; + } + + /** + * @param TransactionCurrency|null $foreignCurrency |null + * + + */ + public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void + { + $this->foreignCurrency = $foreignCurrency; + } + + /** + * @param TransactionJournal $journal + * + + */ + public function setJournal(TransactionJournal $journal): void + { + $this->journal = $journal; + } + + /** + * @param bool $reconciled + * + + */ + public function setReconciled(bool $reconciled): void + { + $this->reconciled = $reconciled; + } + + /** + * @param User $user + * + + */ + public function setUser(User $user): void + { + // empty function. + } + /** * @param string $amount * @param string|null $foreignAmount @@ -171,93 +260,4 @@ class TransactionFactory $service = app(AccountUpdateService::class); $service->update($this->account, ['iban' => $this->accountInformation['iban']]); } - - /** - * Create transaction with positive amount (for destination accounts). - * - * @param string $amount - * @param string|null $foreignAmount - * - * @return Transaction - * @throws FireflyException - */ - public function createPositive(string $amount, ?string $foreignAmount): Transaction - { - if ('' === $foreignAmount) { - $foreignAmount = null; - } - if (null !== $foreignAmount) { - $foreignAmount = app('steam')->positive($foreignAmount); - } - - return $this->create(app('steam')->positive($amount), $foreignAmount); - } - - /** - * @param Account $account - * - - */ - public function setAccount(Account $account): void - { - $this->account = $account; - } - - /** - * @param array $accountInformation - */ - public function setAccountInformation(array $accountInformation): void - { - $this->accountInformation = $accountInformation; - } - - /** - * @param TransactionCurrency $currency - * - - */ - public function setCurrency(TransactionCurrency $currency): void - { - $this->currency = $currency; - } - - /** - * @param TransactionCurrency|null $foreignCurrency |null - * - - */ - public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void - { - $this->foreignCurrency = $foreignCurrency; - } - - /** - * @param TransactionJournal $journal - * - - */ - public function setJournal(TransactionJournal $journal): void - { - $this->journal = $journal; - } - - /** - * @param bool $reconciled - * - - */ - public function setReconciled(bool $reconciled): void - { - $this->reconciled = $reconciled; - } - - /** - * @param User $user - * - - */ - public function setUser(User $user): void - { - // empty function. - } } diff --git a/app/Factory/TransactionGroupFactory.php b/app/Factory/TransactionGroupFactory.php index 7d04ba566c..3731f1a4e6 100644 --- a/app/Factory/TransactionGroupFactory.php +++ b/app/Factory/TransactionGroupFactory.php @@ -27,8 +27,8 @@ use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; use FireflyIII\User; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class TransactionGroupFactory diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 3df1293a52..389b15dc30 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -48,8 +48,8 @@ use FireflyIII\Support\NullArrayObject; use FireflyIII\User; use FireflyIII\Validation\AccountValidator; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class TransactionJournalFactory @@ -143,6 +143,79 @@ class TransactionJournalFactory return $collection; } + /** + * @param bool $errorOnHash + */ + public function setErrorOnHash(bool $errorOnHash): void + { + $this->errorOnHash = $errorOnHash; + if (true === $errorOnHash) { + Log::info('Will trigger duplication alert for this journal.'); + } + } + + /** + * Set the user. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + $this->currencyRepository->setUser($this->user); + $this->tagFactory->setUser($user); + $this->billRepository->setUser($this->user); + $this->budgetRepository->setUser($this->user); + $this->categoryRepository->setUser($this->user); + $this->piggyRepository->setUser($this->user); + $this->accountRepository->setUser($this->user); + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $data + * @param string $field + */ + protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void + { + $set = [ + 'journal' => $journal, + 'name' => $field, + 'data' => (string)($data[$field] ?? ''), + ]; + if ($data[$field] instanceof Carbon) { + $data[$field]->setTimezone(config('app.timezone')); + Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName())); + $set['data'] = $data[$field]->format('Y-m-d H:i:s'); + } + + Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); + + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); + $factory->updateOrCreate($set); + } + + /** + * Set foreign currency to NULL if it's the same as the normal currency: + * + * @param TransactionCurrency|null $currency + * @param TransactionCurrency|null $foreignCurrency + * + * @return TransactionCurrency|null + */ + private function compareCurrencies(?TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): ?TransactionCurrency + { + if (null === $currency) { + return null; + } + if (null !== $foreignCurrency && $foreignCurrency->id === $currency->id) { + return null; + } + + return $foreignCurrency; + } + /** * @param NullArrayObject $row * @@ -304,24 +377,6 @@ class TransactionJournalFactory return $journal; } - /** - * @param NullArrayObject $row - * - * @return string - * @throws JsonException - */ - private function hashArray(NullArrayObject $row): string - { - $dataRow = $row->getArrayCopy(); - - unset($dataRow['import_hash_v2'], $dataRow['original_source']); - $json = json_encode($dataRow, JSON_THROW_ON_ERROR); - $hash = hash('sha256', $json); - Log::debug(sprintf('The hash is: %s', $hash), $dataRow); - - return $hash; - } - /** * If this transaction already exists, throw an error. * @@ -354,6 +409,183 @@ class TransactionJournalFactory } } + /** + * Force the deletion of an entire set of transaction journals and their meta object in case of + * an error creating a group. + * + * @param Collection $collection + */ + private function forceDeleteOnError(Collection $collection): void + { + Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count())); + $service = app(JournalDestroyService::class); + /** @var TransactionJournal $journal */ + foreach ($collection as $journal) { + Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id)); + $service->destroy($journal); + } + } + + /** + * @param Transaction $transaction + */ + private function forceTrDelete(Transaction $transaction): void + { + $transaction->delete(); + } + + /** + * @param TransactionCurrency|null $currency + * @param Account $account + * + * @return TransactionCurrency + * @throws FireflyException + * @throws JsonException + */ + private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency + { + Log::debug('Now in getCurrency()'); + /** @var Preference|null $preference */ + $preference = $this->accountRepository->getAccountCurrency($account); + if (null === $preference && null === $currency) { + // return user's default: + return app('amount')->getDefaultCurrencyByUser($this->user); + } + $result = ($preference ?? $currency) ?? app('amount')->getSystemCurrency(); + Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name)); + + return $result; + } + + /** + * @param string $type + * @param TransactionCurrency|null $currency + * @param Account $source + * @param Account $destination + * + * @return TransactionCurrency + * @throws FireflyException + * @throws JsonException + */ + private function getCurrencyByAccount(string $type, ?TransactionCurrency $currency, Account $source, Account $destination): TransactionCurrency + { + Log::debug('Now in getCurrencyByAccount()'); + + return match ($type) { + default => $this->getCurrency($currency, $source), + TransactionType::DEPOSIT => $this->getCurrency($currency, $destination), + }; + } + + /** + * @param string $description + * + * @return string + */ + private function getDescription(string $description): string + { + $description = '' === $description ? '(empty description)' : $description; + + return substr($description, 0, 255); + } + + /** + * @param string $type + * @param TransactionCurrency|null $foreignCurrency + * @param Account $destination + * + * @return TransactionCurrency|null + * @throws FireflyException + * @throws JsonException + */ + private function getForeignByAccount(string $type, ?TransactionCurrency $foreignCurrency, Account $destination): ?TransactionCurrency + { + if (TransactionType::TRANSFER === $type) { + return $this->getCurrency($foreignCurrency, $destination); + } + + return $foreignCurrency; + } + + /** + * @param NullArrayObject $row + * + * @return string + * @throws JsonException + */ + private function hashArray(NullArrayObject $row): string + { + $dataRow = $row->getArrayCopy(); + + unset($dataRow['import_hash_v2'], $dataRow['original_source']); + $json = json_encode($dataRow, JSON_THROW_ON_ERROR); + $hash = hash('sha256', $json); + Log::debug(sprintf('The hash is: %s', $hash), $dataRow); + + return $hash; + } + + /** + * @param Account|null $sourceAccount + * @param Account|null $destinationAccount + * @return array + */ + private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array + { + Log::debug(sprintf('Now in %s', __METHOD__)); + if (null !== $sourceAccount && null !== $destinationAccount) { + Log::debug('Both accounts exist, simply return them.'); + return [$sourceAccount, $destinationAccount]; + } + if (null !== $sourceAccount && null === $destinationAccount) { + Log::debug('Destination account is NULL, source account is not.'); + $account = $this->accountRepository->getReconciliation($sourceAccount); + Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type)); + return [$sourceAccount, $account]; + } + + if (null === $sourceAccount && null !== $destinationAccount) { + Log::debug('Source account is NULL, destination account is not.'); + $account = $this->accountRepository->getReconciliation($destinationAccount); + Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type)); + return [$account, $destinationAccount]; + } + Log::debug('Unused fallback'); + return [$sourceAccount, $destinationAccount]; + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $transaction + */ + private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void + { + foreach ($this->fields as $field) { + $this->storeMeta($journal, $transaction, $field); + } + } + + /** + * Link a piggy bank to this journal. + * + * @param TransactionJournal $journal + * @param NullArrayObject $data + */ + private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void + { + Log::debug('Will now store piggy event.'); + + $piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']); + + if (null !== $piggyBank) { + $this->piggyEventFactory->create($journal, $piggyBank); + Log::debug('Create piggy event.'); + + return; + } + Log::debug('Create no piggy event'); + } + /** * @param NullArrayObject $data * @@ -395,236 +627,4 @@ class TransactionJournalFactory throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); } } - - /** - * Set the user. - * - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - $this->currencyRepository->setUser($this->user); - $this->tagFactory->setUser($user); - $this->billRepository->setUser($this->user); - $this->budgetRepository->setUser($this->user); - $this->categoryRepository->setUser($this->user); - $this->piggyRepository->setUser($this->user); - $this->accountRepository->setUser($this->user); - } - - /** - * @param Account|null $sourceAccount - * @param Account|null $destinationAccount - * @return array - */ - private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array - { - Log::debug(sprintf('Now in %s', __METHOD__)); - if (null !== $sourceAccount && null !== $destinationAccount) { - Log::debug('Both accounts exist, simply return them.'); - return [$sourceAccount, $destinationAccount]; - } - if (null !== $sourceAccount && null === $destinationAccount) { - Log::debug('Destination account is NULL, source account is not.'); - $account = $this->accountRepository->getReconciliation($sourceAccount); - Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type)); - return [$sourceAccount, $account]; - } - - if (null === $sourceAccount && null !== $destinationAccount) { - Log::debug('Source account is NULL, destination account is not.'); - $account = $this->accountRepository->getReconciliation($destinationAccount); - Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type)); - return [$account, $destinationAccount]; - } - Log::debug('Unused fallback'); - return [$sourceAccount, $destinationAccount]; - } - - /** - * @param string $type - * @param TransactionCurrency|null $currency - * @param Account $source - * @param Account $destination - * - * @return TransactionCurrency - * @throws FireflyException - * @throws JsonException - */ - private function getCurrencyByAccount(string $type, ?TransactionCurrency $currency, Account $source, Account $destination): TransactionCurrency - { - Log::debug('Now in getCurrencyByAccount()'); - - return match ($type) { - default => $this->getCurrency($currency, $source), - TransactionType::DEPOSIT => $this->getCurrency($currency, $destination), - }; - } - - /** - * @param TransactionCurrency|null $currency - * @param Account $account - * - * @return TransactionCurrency - * @throws FireflyException - * @throws JsonException - */ - private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency - { - Log::debug('Now in getCurrency()'); - /** @var Preference|null $preference */ - $preference = $this->accountRepository->getAccountCurrency($account); - if (null === $preference && null === $currency) { - // return user's default: - return app('amount')->getDefaultCurrencyByUser($this->user); - } - $result = ($preference ?? $currency) ?? app('amount')->getSystemCurrency(); - Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name)); - - return $result; - } - - /** - * Set foreign currency to NULL if it's the same as the normal currency: - * - * @param TransactionCurrency|null $currency - * @param TransactionCurrency|null $foreignCurrency - * - * @return TransactionCurrency|null - */ - private function compareCurrencies(?TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): ?TransactionCurrency - { - if (null === $currency) { - return null; - } - if (null !== $foreignCurrency && $foreignCurrency->id === $currency->id) { - return null; - } - - return $foreignCurrency; - } - - /** - * @param string $type - * @param TransactionCurrency|null $foreignCurrency - * @param Account $destination - * - * @return TransactionCurrency|null - * @throws FireflyException - * @throws JsonException - */ - private function getForeignByAccount(string $type, ?TransactionCurrency $foreignCurrency, Account $destination): ?TransactionCurrency - { - if (TransactionType::TRANSFER === $type) { - return $this->getCurrency($foreignCurrency, $destination); - } - - return $foreignCurrency; - } - - /** - * @param string $description - * - * @return string - */ - private function getDescription(string $description): string - { - $description = '' === $description ? '(empty description)' : $description; - - return substr($description, 0, 255); - } - - /** - * Force the deletion of an entire set of transaction journals and their meta object in case of - * an error creating a group. - * - * @param Collection $collection - */ - private function forceDeleteOnError(Collection $collection): void - { - Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count())); - $service = app(JournalDestroyService::class); - /** @var TransactionJournal $journal */ - foreach ($collection as $journal) { - Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id)); - $service->destroy($journal); - } - } - - /** - * @param Transaction $transaction - */ - private function forceTrDelete(Transaction $transaction): void - { - $transaction->delete(); - } - - /** - * Link a piggy bank to this journal. - * - * @param TransactionJournal $journal - * @param NullArrayObject $data - */ - private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void - { - Log::debug('Will now store piggy event.'); - - $piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']); - - if (null !== $piggyBank) { - $this->piggyEventFactory->create($journal, $piggyBank); - Log::debug('Create piggy event.'); - - return; - } - Log::debug('Create no piggy event'); - } - - /** - * @param TransactionJournal $journal - * @param NullArrayObject $transaction - */ - private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void - { - foreach ($this->fields as $field) { - $this->storeMeta($journal, $transaction, $field); - } - } - - /** - * @param TransactionJournal $journal - * @param NullArrayObject $data - * @param string $field - */ - protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void - { - $set = [ - 'journal' => $journal, - 'name' => $field, - 'data' => (string)($data[$field] ?? ''), - ]; - if ($data[$field] instanceof Carbon) { - $data[$field]->setTimezone(config('app.timezone')); - Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName())); - $set['data'] = $data[$field]->format('Y-m-d H:i:s'); - } - - Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); - - /** @var TransactionJournalMetaFactory $factory */ - $factory = app(TransactionJournalMetaFactory::class); - $factory->updateOrCreate($set); - } - - /** - * @param bool $errorOnHash - */ - public function setErrorOnHash(bool $errorOnHash): void - { - $this->errorOnHash = $errorOnHash; - if (true === $errorOnHash) { - Log::info('Will trigger duplication alert for this journal.'); - } - } } diff --git a/app/Generator/Report/Account/MonthReportGenerator.php b/app/Generator/Report/Account/MonthReportGenerator.php index 0973044a41..97608c267d 100644 --- a/app/Generator/Report/Account/MonthReportGenerator.php +++ b/app/Generator/Report/Account/MonthReportGenerator.php @@ -68,16 +68,6 @@ class MonthReportGenerator implements ReportGeneratorInterface return $result; } - /** - * Return the preferred period. - * - * @return string - */ - protected function preferredPeriod(): string - { - return 'day'; - } - /** * Set accounts. * @@ -169,4 +159,14 @@ class MonthReportGenerator implements ReportGeneratorInterface { return $this; } + + /** + * Return the preferred period. + * + * @return string + */ + protected function preferredPeriod(): string + { + return 'day'; + } } diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index fcfdc37cdc..5f07c4f2f7 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -31,8 +31,8 @@ use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Throwable; /** diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 70479207ba..c40ffa54f4 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -82,6 +82,34 @@ class MonthReportGenerator implements ReportGeneratorInterface return $result; } + /** + * Set the involved accounts. + * + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * Set the involved budgets. + * + * @param Collection $budgets + * + * @return ReportGeneratorInterface + */ + public function setBudgets(Collection $budgets): ReportGeneratorInterface + { + $this->budgets = $budgets; + + return $this; + } + /** * Unused category setter. * @@ -172,32 +200,4 @@ class MonthReportGenerator implements ReportGeneratorInterface return $journals; } - - /** - * Set the involved budgets. - * - * @param Collection $budgets - * - * @return ReportGeneratorInterface - */ - public function setBudgets(Collection $budgets): ReportGeneratorInterface - { - $this->budgets = $budgets; - - return $this; - } - - /** - * Set the involved accounts. - * - * @param Collection $accounts - * - * @return ReportGeneratorInterface - */ - public function setAccounts(Collection $accounts): ReportGeneratorInterface - { - $this->accounts = $accounts; - - return $this; - } } diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index f480dc913c..f3a8c2bbed 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -82,6 +82,20 @@ class MonthReportGenerator implements ReportGeneratorInterface } } + /** + * Set the involved accounts. + * + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + /** * Empty budget setter. * @@ -94,6 +108,20 @@ class MonthReportGenerator implements ReportGeneratorInterface return $this; } + /** + * Set the categories involved in this report. + * + * @param Collection $categories + * + * @return ReportGeneratorInterface + */ + public function setCategories(Collection $categories): ReportGeneratorInterface + { + $this->categories = $categories; + + return $this; + } + /** * Set the end date for this report. * @@ -171,34 +199,6 @@ class MonthReportGenerator implements ReportGeneratorInterface return $transactions; } - /** - * Set the categories involved in this report. - * - * @param Collection $categories - * - * @return ReportGeneratorInterface - */ - public function setCategories(Collection $categories): ReportGeneratorInterface - { - $this->categories = $categories; - - return $this; - } - - /** - * Set the involved accounts. - * - * @param Collection $accounts - * - * @return ReportGeneratorInterface - */ - public function setAccounts(Collection $accounts): ReportGeneratorInterface - { - $this->accounts = $accounts; - - return $this; - } - /** * Get the income for this report. * diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index 35560f040f..66ed57089b 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -37,8 +37,8 @@ use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Ramsey\Uuid\Uuid; use Symfony\Component\HttpFoundation\ParameterBag; @@ -81,38 +81,62 @@ class StandardMessageGenerator implements MessageGeneratorInterface } /** + * @inheritDoc + */ + public function getVersion(): int + { + return $this->version; + } + + /** + * @param Collection $objects + */ + public function setObjects(Collection $objects): void + { + $this->objects = $objects; + } + + /** + * @param int $trigger + */ + public function setTrigger(int $trigger): void + { + $this->trigger = $trigger; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + /** + * @inheritDoc + */ + public function setWebhooks(Collection $webhooks): void + { + $this->webhooks = $webhooks; + } + + /** + * @param TransactionGroup $transactionGroup + * * @return Collection */ - private function getWebhooks(): Collection + private function collectAccounts(TransactionGroup $transactionGroup): Collection { - return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']); - } - - /** - * - */ - private function run(): void - { - Log::debug('Now in StandardMessageGenerator::run'); - /** @var Webhook $webhook */ - foreach ($this->webhooks as $webhook) { - $this->runWebhook($webhook); + $accounts = new Collection(); + /** @var TransactionJournal $journal */ + foreach ($transactionGroup->transactionJournals as $journal) { + /** @var Transaction $transaction */ + foreach ($journal->transactions as $transaction) { + $accounts->push($transaction->account); + } } - Log::debug('Done with StandardMessageGenerator::run'); - } - /** - * @param Webhook $webhook - * @throws FireflyException - * @throws JsonException - */ - private function runWebhook(Webhook $webhook): void - { - Log::debug(sprintf('Now in runWebhook(#%d)', $webhook->id)); - /** @var Model $object */ - foreach ($this->objects as $object) { - $this->generateMessage($webhook, $object); - } + return $accounts->unique(); } /** @@ -188,30 +212,38 @@ class StandardMessageGenerator implements MessageGeneratorInterface } /** - * @inheritDoc + * @return Collection */ - public function getVersion(): int + private function getWebhooks(): Collection { - return $this->version; + return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']); } /** - * @param TransactionGroup $transactionGroup * - * @return Collection */ - private function collectAccounts(TransactionGroup $transactionGroup): Collection + private function run(): void { - $accounts = new Collection(); - /** @var TransactionJournal $journal */ - foreach ($transactionGroup->transactionJournals as $journal) { - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $accounts->push($transaction->account); - } + Log::debug('Now in StandardMessageGenerator::run'); + /** @var Webhook $webhook */ + foreach ($this->webhooks as $webhook) { + $this->runWebhook($webhook); } + Log::debug('Done with StandardMessageGenerator::run'); + } - return $accounts->unique(); + /** + * @param Webhook $webhook + * @throws FireflyException + * @throws JsonException + */ + private function runWebhook(Webhook $webhook): void + { + Log::debug(sprintf('Now in runWebhook(#%d)', $webhook->id)); + /** @var Model $object */ + foreach ($this->objects as $object) { + $this->generateMessage($webhook, $object); + } } /** @@ -231,36 +263,4 @@ class StandardMessageGenerator implements MessageGeneratorInterface $webhookMessage->save(); Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id)); } - - /** - * @param Collection $objects - */ - public function setObjects(Collection $objects): void - { - $this->objects = $objects; - } - - /** - * @param int $trigger - */ - public function setTrigger(int $trigger): void - { - $this->trigger = $trigger; - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } - - /** - * @inheritDoc - */ - public function setWebhooks(Collection $webhooks): void - { - $this->webhooks = $webhooks; - } } diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php index fe45a84574..9d73bde36a 100644 --- a/app/Handlers/Events/APIEventHandler.php +++ b/app/Handlers/Events/APIEventHandler.php @@ -26,9 +26,9 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Notifications\User\NewAccessToken; use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use Laravel\Passport\Events\AccessTokenCreated; -use Illuminate\Support\Facades\Log; /** * Class APIEventHandler diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php index a8c4043b38..f990032395 100644 --- a/app/Handlers/Events/AutomationHandler.php +++ b/app/Handlers/Events/AutomationHandler.php @@ -29,8 +29,8 @@ use FireflyIII\Models\TransactionGroup; use FireflyIII\Notifications\User\TransactionCreation; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Transformers\TransactionGroupTransformer; -use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Notification; /** * Class AutomationHandler diff --git a/app/Handlers/Events/Model/BudgetLimitHandler.php b/app/Handlers/Events/Model/BudgetLimitHandler.php index df70b523cc..674aacb134 100644 --- a/app/Handlers/Events/Model/BudgetLimitHandler.php +++ b/app/Handlers/Events/Model/BudgetLimitHandler.php @@ -52,16 +52,6 @@ class BudgetLimitHandler $this->updateAvailableBudget($event->budgetLimit); } - /** - * @param Updated $event - * @return void - */ - public function updated(Updated $event): void - { - Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); - $this->updateAvailableBudget($event->budgetLimit); - } - /** * @param Deleted $event * @return void @@ -74,6 +64,16 @@ class BudgetLimitHandler $this->updateAvailableBudget($event->budgetLimit); } + /** + * @param Updated $event + * @return void + */ + public function updated(Updated $event): void + { + Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); + $this->updateAvailableBudget($event->budgetLimit); + } + /** * @param AvailableBudget $availableBudget * @return void @@ -182,12 +182,12 @@ class BudgetLimitHandler $end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange); $end = app('navigation')->endOfPeriod($end, $viewRange); $budget = Budget::find($budgetLimit->budget_id); - if(null === $budget) { + if (null === $budget) { Log::warning('Budget is null, cannot continue.'); $budgetLimit->forceDelete(); return; } - $user = $budget->user; + $user = $budget->user; // sanity check. It happens when the budget has been deleted so the original user is unknown. if (null === $user) { diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 005ffd4918..1b5d6b2474 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -47,8 +47,8 @@ use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Support\Facades\FireflyConfig; use FireflyIII\User; use Illuminate\Auth\Events\Login; -use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Notification; use Mail; /** diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 7280be2037..a55fb5d055 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -32,9 +32,9 @@ use Illuminate\Contracts\Encryption\EncryptException; use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\MessageBag; -use Illuminate\Support\Facades\Log; use Symfony\Component\HttpFoundation\File\UploadedFile; /** @@ -211,6 +211,39 @@ class AttachmentHelper implements AttachmentHelperInterface return true; } + /** + * Check if a model already has this file attached. + * + * @param UploadedFile $file + * @param Model $model + * + * @return bool + */ + protected function hasFile(UploadedFile $file, Model $model): bool + { + $md5 = md5_file($file->getRealPath()); + $name = $file->getClientOriginalName(); + $class = get_class($model); + $count = 0; + // ignore lines about polymorphic calls. + if ($model instanceof PiggyBank) { + $count = $model->account->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); + } + if ($model instanceof PiggyBank) { + $count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count( + ); // @phpstan-ignore-line + } + $result = false; + if ($count > 0) { + $msg = (string)trans('validation.file_already_attached', ['name' => $name]); + $this->errors->add('attachments', $msg); + Log::error($msg); + $result = true; + } + + return $result; + } + /** * Process the upload of a file. * @@ -268,39 +301,6 @@ class AttachmentHelper implements AttachmentHelperInterface return $attachment; } - /** - * Verify if the file was uploaded correctly. - * - * @param UploadedFile $file - * @param Model $model - * - * @return bool - */ - protected function validateUpload(UploadedFile $file, Model $model): bool - { - Log::debug('Now in validateUpload()'); - $result = true; - if (!$this->validMime($file)) { - $result = false; - } - if (0 === $file->getSize()) { - Log::error('Cannot upload empty file.'); - $result = false; - } - - - // can't seem to reach this point. - if (true === $result && !$this->validSize($file)) { - $result = false; - } - - if (true === $result && $this->hasFile($file, $model)) { - $result = false; - } - - return $result; - } - /** * Verify if the mime of a file is valid. * @@ -353,33 +353,33 @@ class AttachmentHelper implements AttachmentHelperInterface } /** - * Check if a model already has this file attached. + * Verify if the file was uploaded correctly. * * @param UploadedFile $file * @param Model $model * * @return bool */ - protected function hasFile(UploadedFile $file, Model $model): bool + protected function validateUpload(UploadedFile $file, Model $model): bool { - $md5 = md5_file($file->getRealPath()); - $name = $file->getClientOriginalName(); - $class = get_class($model); - $count = 0; - // ignore lines about polymorphic calls. - if ($model instanceof PiggyBank) { - $count = $model->account->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); + Log::debug('Now in validateUpload()'); + $result = true; + if (!$this->validMime($file)) { + $result = false; } - if ($model instanceof PiggyBank) { - $count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count( - ); // @phpstan-ignore-line + if (0 === $file->getSize()) { + Log::error('Cannot upload empty file.'); + $result = false; } - $result = false; - if ($count > 0) { - $msg = (string)trans('validation.file_already_attached', ['name' => $name]); - $this->errors->add('attachments', $msg); - Log::error($msg); - $result = true; + + + // can't seem to reach this point. + if (true === $result && !$this->validSize($file)) { + $result = false; + } + + if (true === $result && $this->hasFile($file, $model)) { + $result = false; } return $result; diff --git a/app/Helpers/Collector/Extensions/MetaCollection.php b/app/Helpers/Collector/Extensions/MetaCollection.php index 896eec2645..9ccf3d34bc 100644 --- a/app/Helpers/Collector/Extensions/MetaCollection.php +++ b/app/Helpers/Collector/Extensions/MetaCollection.php @@ -53,25 +53,6 @@ trait MetaCollection return $this; } - /** - * Will include bill name + ID, if any. - * - * @return GroupCollectorInterface - */ - public function withBillInformation(): GroupCollectorInterface - { - if (false === $this->hasBillInformation) { - // join bill table - $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); - // add fields - $this->fields[] = 'bills.id as bill_id'; - $this->fields[] = 'bills.name as bill_name'; - $this->hasBillInformation = true; - } - - return $this; - } - /** * Exclude a specific budget. * @@ -91,27 +72,6 @@ trait MetaCollection return $this; } - /** - * Will include budget ID + name, if any. - * - * @return GroupCollectorInterface - */ - public function withBudgetInformation(): GroupCollectorInterface - { - if (false === $this->hasBudgetInformation) { - // join link table - $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); - // add fields - $this->fields[] = 'budgets.id as budget_id'; - $this->fields[] = 'budgets.name as budget_name'; - $this->hasBudgetInformation = true; - } - - return $this; - } - /** * @inheritDoc */ @@ -144,27 +104,6 @@ trait MetaCollection return $this; } - /** - * Will include category ID + name, if any. - * - * @return GroupCollectorInterface - */ - public function withCategoryInformation(): GroupCollectorInterface - { - if (false === $this->hasCatInformation) { - // join link table - $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); - // add fields - $this->fields[] = 'categories.id as category_id'; - $this->fields[] = 'categories.name as category_name'; - $this->hasCatInformation = true; - } - - return $this; - } - /** * Exclude a specific category. * @@ -196,19 +135,6 @@ trait MetaCollection return $this; } - /** - * Join table to get tag information. - */ - protected function joinMetaDataTables(): void - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - $this->fields[] = 'journal_meta.name as meta_name'; - $this->fields[] = 'journal_meta.data as meta_data'; - } - } - /** * @inheritDoc */ @@ -425,37 +351,6 @@ trait MetaCollection return $this; } - /** - * @return GroupCollectorInterface - */ - public function withTagInformation(): GroupCollectorInterface - { - $this->fields[] = 'tags.id as tag_id'; - $this->fields[] = 'tags.tag as tag_name'; - $this->fields[] = 'tags.date as tag_date'; - $this->fields[] = 'tags.description as tag_description'; - $this->fields[] = 'tags.latitude as tag_latitude'; - $this->fields[] = 'tags.longitude as tag_longitude'; - $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; - - $this->joinTagTables(); - - return $this; - } - - /** - * Join table to get tag information. - */ - protected function joinTagTables(): void - { - if (false === $this->hasJoinedTagTables) { - // join some extra tables: - $this->hasJoinedTagTables = true; - $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); - } - } - /** * @inheritDoc */ @@ -541,29 +436,6 @@ trait MetaCollection return $this; } - /** - * @inheritDoc - */ - public function withNotes(): GroupCollectorInterface - { - if (false === $this->hasNotesInformation) { - // join bill table - $this->query->leftJoin( - 'notes', - static function (JoinClause $join) { - $join->on('notes.noteable_id', '=', 'transaction_journals.id'); - $join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal'); - $join->whereNull('notes.deleted_at'); - } - ); - // add fields - $this->fields[] = 'notes.text as notes'; - $this->hasNotesInformation = true; - } - - return $this; - } - /** * @param string $value * @@ -906,6 +778,25 @@ trait MetaCollection return $this; } + /** + * Will include bill name + ID, if any. + * + * @return GroupCollectorInterface + */ + public function withBillInformation(): GroupCollectorInterface + { + if (false === $this->hasBillInformation) { + // join bill table + $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); + // add fields + $this->fields[] = 'bills.id as bill_id'; + $this->fields[] = 'bills.name as bill_name'; + $this->hasBillInformation = true; + } + + return $this; + } + /** * Limit results to a transactions without a budget.. * @@ -919,6 +810,27 @@ trait MetaCollection return $this; } + /** + * Will include budget ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withBudgetInformation(): GroupCollectorInterface + { + if (false === $this->hasBudgetInformation) { + // join link table + $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); + // add fields + $this->fields[] = 'budgets.id as budget_id'; + $this->fields[] = 'budgets.name as budget_name'; + $this->hasBudgetInformation = true; + } + + return $this; + } + /** * Limit results to a transactions without a category. * @@ -932,6 +844,27 @@ trait MetaCollection return $this; } + /** + * Will include category ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withCategoryInformation(): GroupCollectorInterface + { + if (false === $this->hasCatInformation) { + // join link table + $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); + // add fields + $this->fields[] = 'categories.id as category_id'; + $this->fields[] = 'categories.name as category_name'; + $this->hasCatInformation = true; + } + + return $this; + } + /** * @inheritDoc */ @@ -956,6 +889,47 @@ trait MetaCollection return $this; } + /** + * @inheritDoc + */ + public function withNotes(): GroupCollectorInterface + { + if (false === $this->hasNotesInformation) { + // join bill table + $this->query->leftJoin( + 'notes', + static function (JoinClause $join) { + $join->on('notes.noteable_id', '=', 'transaction_journals.id'); + $join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal'); + $join->whereNull('notes.deleted_at'); + } + ); + // add fields + $this->fields[] = 'notes.text as notes'; + $this->hasNotesInformation = true; + } + + return $this; + } + + /** + * @return GroupCollectorInterface + */ + public function withTagInformation(): GroupCollectorInterface + { + $this->fields[] = 'tags.id as tag_id'; + $this->fields[] = 'tags.tag as tag_name'; + $this->fields[] = 'tags.date as tag_date'; + $this->fields[] = 'tags.description as tag_description'; + $this->fields[] = 'tags.latitude as tag_latitude'; + $this->fields[] = 'tags.longitude as tag_longitude'; + $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; + + $this->joinTagTables(); + + return $this; + } + /** * Limit results to a transactions without a bill. * @@ -1062,4 +1036,30 @@ trait MetaCollection return $this; } + + /** + * Join table to get tag information. + */ + protected function joinMetaDataTables(): void + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + $this->fields[] = 'journal_meta.name as meta_name'; + $this->fields[] = 'journal_meta.data as meta_data'; + } + } + + /** + * Join table to get tag information. + */ + protected function joinTagTables(): void + { + if (false === $this->hasJoinedTagTables) { + // join some extra tables: + $this->hasJoinedTagTables = true; + $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); + } + } } diff --git a/app/Helpers/Collector/Extensions/TimeCollection.php b/app/Helpers/Collector/Extensions/TimeCollection.php index 31bbd13f03..7d2d598ee0 100644 --- a/app/Helpers/Collector/Extensions/TimeCollection.php +++ b/app/Helpers/Collector/Extensions/TimeCollection.php @@ -102,18 +102,6 @@ trait TimeCollection return $this; } - /** - * @inheritDoc - */ - public function withMetaDate(string $field): GroupCollectorInterface - { - $this->joinMetaDataTables(); - $this->query->where('journal_meta.name', '=', $field); - $this->query->whereNotNull('journal_meta.data'); - - return $this; - } - /** * @param Carbon $start * @param Carbon $end @@ -798,6 +786,18 @@ trait TimeCollection return $this; } + /** + * @inheritDoc + */ + public function withMetaDate(string $field): GroupCollectorInterface + { + $this->joinMetaDataTables(); + $this->query->where('journal_meta.name', '=', $field); + $this->query->whereNotNull('journal_meta.data'); + + return $this; + } + /** * @param string $year * @return GroupCollectorInterface diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 8e4b83808d..7de73cb64d 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -504,6 +504,312 @@ class GroupCollector implements GroupCollectorInterface return $collection; } + /** + * Same as getGroups but everything is in a paginator. + * + * @return LengthAwarePaginator + */ + public function getPaginatedGroups(): LengthAwarePaginator + { + $set = $this->getGroups(); + if (0 === $this->limit) { + $this->setLimit(50); + } + + return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + } + + /** + * @inheritDoc + */ + public function isNotReconciled(): GroupCollectorInterface + { + $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); + return $this; + } + + /** + * @inheritDoc + */ + public function isReconciled(): GroupCollectorInterface + { + $this->query->where('source.reconciled', 1)->where('destination.reconciled', 1); + return $this; + } + + /** + * Limit results to a specific currency, either foreign or normal one. + * + * @param TransactionCurrency $currency + * + * @return GroupCollectorInterface + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where( + static function (EloquentBuilder $q) use ($currency) { + $q->where('source.transaction_currency_id', $currency->id); + $q->orWhere('source.foreign_currency_id', $currency->id); + } + ); + + return $this; + } + + /** + * @inheritDoc + */ + public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where('source.foreign_currency_id', $currency->id); + + return $this; + } + + /** + * Limit the result to a set of specific transaction groups. + * + * @param array $groupIds + * + * @return GroupCollectorInterface + */ + public function setIds(array $groupIds): GroupCollectorInterface + { + $this->query->whereIn('transaction_groups.id', $groupIds); + + return $this; + } + + /** + * Limit the result to a set of specific journals. + * + * @param array $journalIds + * + * @return GroupCollectorInterface + */ + public function setJournalIds(array $journalIds): GroupCollectorInterface + { + if (0 !== count($journalIds)) { + // make all integers. + $integerIDs = array_map('intval', $journalIds); + + + $this->query->whereIn('transaction_journals.id', $integerIDs); + } + + return $this; + } + + /** + * Limit the number of returned entries. + * + * @param int $limit + * + * @return GroupCollectorInterface + */ + public function setLimit(int $limit): GroupCollectorInterface + { + $this->limit = $limit; + app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); + + return $this; + } + + /** + * Set the page to get. + * + * @param int $page + * + * @return GroupCollectorInterface + */ + public function setPage(int $page): GroupCollectorInterface + { + $page = 0 === $page ? 1 : $page; + $this->page = $page; + app('log')->debug(sprintf('GroupCollector: page is now %d', $page)); + + return $this; + } + + /** + * Search for words in descriptions. + * + * @param array $array + * + * @return GroupCollectorInterface + */ + public function setSearchWords(array $array): GroupCollectorInterface + { + if (0 === count($array)) { + return $this; + } + $this->query->where( + static function (EloquentBuilder $q) use ($array) { + $q->where( + static function (EloquentBuilder $q1) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q1->where('transaction_journals.description', 'LIKE', $keyword); + } + } + ); + $q->orWhere( + static function (EloquentBuilder $q2) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q2->where('transaction_groups.title', 'LIKE', $keyword); + } + } + ); + } + ); + + return $this; + } + + /** + * Limit the search to one specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface + { + $this->query->where('transaction_groups.id', $transactionGroup->id); + + return $this; + } + + /** + * Limit the included transaction types. + * + * @param array $types + * + * @return GroupCollectorInterface + */ + public function setTypes(array $types): GroupCollectorInterface + { + $this->query->whereIn('transaction_types.type', $types); + + return $this; + } + + /** + * Set the user object and start the query. + * + * @param User $user + * + * @return GroupCollectorInterface + */ + public function setUser(User $user): GroupCollectorInterface + { + if (null === $this->user) { + $this->user = $user; + $this->startQuery(); + } + + return $this; + } + + /** + * Automatically include all stuff required to make API calls work. + * + * @return GroupCollectorInterface + */ + public function withAPIInformation(): GroupCollectorInterface + { + // include source + destination account name and type. + $this->withAccountInformation() + // include category ID + name (if any) + ->withCategoryInformation() + // include budget ID + name (if any) + ->withBudgetInformation() + // include bill ID + name (if any) + ->withBillInformation(); + + return $this; + } + + /** + * Convert a selected set of fields to arrays. + * + * @param array $array + * + * @return array + */ + private function convertToInteger(array $array): array + { + foreach ($this->integerFields as $field) { + $array[$field] = array_key_exists($field, $array) ? (int)$array[$field] : null; + } + + return $array; + } + + /** + * @param array $array + * @return array + */ + private function convertToStrings(array $array): array + { + foreach ($this->stringFields as $field) { + $array[$field] = array_key_exists($field, $array) && null !== $array[$field] ? (string)$array[$field] : null; + } + + return $array; + } + + /** + * @param array $existingJournal + * @param TransactionJournal $newJournal + * + * @return array + */ + private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array + { + $newArray = $newJournal->toArray(); + if (array_key_exists('attachment_id', $newArray)) { + $attachmentId = (int)$newJournal['attachment_id']; + + $existingJournal['attachments'][$attachmentId] = [ + 'id' => $attachmentId, + ]; + } + + return $existingJournal; + } + + /** + * @param array $existingJournal + * @param TransactionJournal $newJournal + * + * @return array + */ + private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array + { + $newArray = $newJournal->toArray(); + if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well. + $tagId = (int)$newJournal['tag_id']; + + $tagDate = null; + try { + $tagDate = Carbon::parse($newArray['tag_date']); + } catch (InvalidFormatException $e) { + Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); + } + + $existingJournal['tags'][$tagId] = [ + 'id' => (int)$newArray['tag_id'], + 'name' => $newArray['tag_name'], + 'date' => $tagDate, + 'description' => $newArray['tag_description'], + ]; + } + + return $existingJournal; + } + /** * @param Collection $collection * @@ -645,85 +951,6 @@ class GroupCollector implements GroupCollectorInterface return $result; } - /** - * Convert a selected set of fields to arrays. - * - * @param array $array - * - * @return array - */ - private function convertToInteger(array $array): array - { - foreach ($this->integerFields as $field) { - $array[$field] = array_key_exists($field, $array) ? (int)$array[$field] : null; - } - - return $array; - } - - /** - * @param array $array - * @return array - */ - private function convertToStrings(array $array): array - { - foreach ($this->stringFields as $field) { - $array[$field] = array_key_exists($field, $array) && null !== $array[$field] ? (string)$array[$field] : null; - } - - return $array; - } - - /** - * @param array $existingJournal - * @param TransactionJournal $newJournal - * - * @return array - */ - private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array - { - $newArray = $newJournal->toArray(); - if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well. - $tagId = (int)$newJournal['tag_id']; - - $tagDate = null; - try { - $tagDate = Carbon::parse($newArray['tag_date']); - } catch (InvalidFormatException $e) { - Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); - } - - $existingJournal['tags'][$tagId] = [ - 'id' => (int)$newArray['tag_id'], - 'name' => $newArray['tag_name'], - 'date' => $tagDate, - 'description' => $newArray['tag_description'], - ]; - } - - return $existingJournal; - } - - /** - * @param array $existingJournal - * @param TransactionJournal $newJournal - * - * @return array - */ - private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array - { - $newArray = $newJournal->toArray(); - if (array_key_exists('attachment_id', $newArray)) { - $attachmentId = (int)$newJournal['attachment_id']; - - $existingJournal['attachments'][$attachmentId] = [ - 'id' => $attachmentId, - ]; - } - - return $existingJournal; - } - /** * @param array $groups * @@ -801,214 +1028,6 @@ class GroupCollector implements GroupCollectorInterface return $currentCollection; } - /** - * Same as getGroups but everything is in a paginator. - * - * @return LengthAwarePaginator - */ - public function getPaginatedGroups(): LengthAwarePaginator - { - $set = $this->getGroups(); - if (0 === $this->limit) { - $this->setLimit(50); - } - - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); - } - - /** - * Limit the number of returned entries. - * - * @param int $limit - * - * @return GroupCollectorInterface - */ - public function setLimit(int $limit): GroupCollectorInterface - { - $this->limit = $limit; - app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); - - return $this; - } - - /** - * @inheritDoc - */ - public function isNotReconciled(): GroupCollectorInterface - { - $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); - return $this; - } - - /** - * @inheritDoc - */ - public function isReconciled(): GroupCollectorInterface - { - $this->query->where('source.reconciled', 1)->where('destination.reconciled', 1); - return $this; - } - - /** - * Limit results to a specific currency, either foreign or normal one. - * - * @param TransactionCurrency $currency - * - * @return GroupCollectorInterface - */ - public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where( - static function (EloquentBuilder $q) use ($currency) { - $q->where('source.transaction_currency_id', $currency->id); - $q->orWhere('source.foreign_currency_id', $currency->id); - } - ); - - return $this; - } - - /** - * @inheritDoc - */ - public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where('source.foreign_currency_id', $currency->id); - - return $this; - } - - /** - * Limit the result to a set of specific transaction groups. - * - * @param array $groupIds - * - * @return GroupCollectorInterface - */ - public function setIds(array $groupIds): GroupCollectorInterface - { - $this->query->whereIn('transaction_groups.id', $groupIds); - - return $this; - } - - /** - * Limit the result to a set of specific journals. - * - * @param array $journalIds - * - * @return GroupCollectorInterface - */ - public function setJournalIds(array $journalIds): GroupCollectorInterface - { - if (0 !== count($journalIds)) { - // make all integers. - $integerIDs = array_map('intval', $journalIds); - - - $this->query->whereIn('transaction_journals.id', $integerIDs); - } - - return $this; - } - - /** - * Set the page to get. - * - * @param int $page - * - * @return GroupCollectorInterface - */ - public function setPage(int $page): GroupCollectorInterface - { - $page = 0 === $page ? 1 : $page; - $this->page = $page; - app('log')->debug(sprintf('GroupCollector: page is now %d', $page)); - - return $this; - } - - /** - * Search for words in descriptions. - * - * @param array $array - * - * @return GroupCollectorInterface - */ - public function setSearchWords(array $array): GroupCollectorInterface - { - if (0 === count($array)) { - return $this; - } - $this->query->where( - static function (EloquentBuilder $q) use ($array) { - $q->where( - static function (EloquentBuilder $q1) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q1->where('transaction_journals.description', 'LIKE', $keyword); - } - } - ); - $q->orWhere( - static function (EloquentBuilder $q2) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q2->where('transaction_groups.title', 'LIKE', $keyword); - } - } - ); - } - ); - - return $this; - } - - /** - * Limit the search to one specific transaction group. - * - * @param TransactionGroup $transactionGroup - * - * @return GroupCollectorInterface - */ - public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface - { - $this->query->where('transaction_groups.id', $transactionGroup->id); - - return $this; - } - - /** - * Limit the included transaction types. - * - * @param array $types - * - * @return GroupCollectorInterface - */ - public function setTypes(array $types): GroupCollectorInterface - { - $this->query->whereIn('transaction_types.type', $types); - - return $this; - } - - /** - * Set the user object and start the query. - * - * @param User $user - * - * @return GroupCollectorInterface - */ - public function setUser(User $user): GroupCollectorInterface - { - if (null === $this->user) { - $this->user = $user; - $this->startQuery(); - } - - return $this; - } - /** * Build the query. */ @@ -1051,23 +1070,4 @@ class GroupCollector implements GroupCollectorInterface ->orderBy('transaction_journals.description', 'DESC') ->orderBy('source.amount', 'DESC'); } - - /** - * Automatically include all stuff required to make API calls work. - * - * @return GroupCollectorInterface - */ - public function withAPIInformation(): GroupCollectorInterface - { - // include source + destination account name and type. - $this->withAccountInformation() - // include category ID + name (if any) - ->withCategoryInformation() - // include budget ID + name (if any) - ->withBudgetInformation() - // include bill ID + name (if any) - ->withBillInformation(); - - return $this; - } } diff --git a/app/Helpers/Fiscal/FiscalHelper.php b/app/Helpers/Fiscal/FiscalHelper.php index 3275c83960..f2dcf4d0f1 100644 --- a/app/Helpers/Fiscal/FiscalHelper.php +++ b/app/Helpers/Fiscal/FiscalHelper.php @@ -25,7 +25,6 @@ namespace FireflyIII\Helpers\Fiscal; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Helpers/Update/UpdateTrait.php b/app/Helpers/Update/UpdateTrait.php index 06f34fb15e..b5443da8de 100644 --- a/app/Helpers/Update/UpdateTrait.php +++ b/app/Helpers/Update/UpdateTrait.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Update; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequestInterface; use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; diff --git a/app/Helpers/Webhook/Sha3SignatureGenerator.php b/app/Helpers/Webhook/Sha3SignatureGenerator.php index 931914b3ac..c51edf0a4f 100644 --- a/app/Helpers/Webhook/Sha3SignatureGenerator.php +++ b/app/Helpers/Webhook/Sha3SignatureGenerator.php @@ -25,8 +25,8 @@ namespace FireflyIII\Helpers\Webhook; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\WebhookMessage; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class Sha3SignatureGenerator diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php index 44c8bc8370..02a8792e29 100644 --- a/app/Http/Controllers/Account/CreateController.php +++ b/app/Http/Controllers/Account/CreateController.php @@ -34,8 +34,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index c8014aac7c..9d7cbe5320 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -32,9 +32,9 @@ use FireflyIII\Support\Http\Controllers\BasicDataSupport; use Illuminate\Contracts\View\Factory; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use JsonException; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index a1131de344..c2980ac665 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -38,9 +38,9 @@ use FireflyIII\User; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use JsonException; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php index 8b13529d5a..35b8287f09 100644 --- a/app/Http/Controllers/Admin/ConfigurationController.php +++ b/app/Http/Controllers/Admin/ConfigurationController.php @@ -29,8 +29,8 @@ use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Http\Requests\ConfigurationRequest; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index 5bfd8d9102..751dc3b4ab 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -33,8 +33,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php index e89254c06d..a1f52b1b47 100644 --- a/app/Http/Controllers/Admin/LinkController.php +++ b/app/Http/Controllers/Admin/LinkController.php @@ -32,8 +32,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * Class LinkController. diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 728c906527..25f76c9e00 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -36,8 +36,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index de43be4208..d58b6ad823 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -31,8 +31,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index cdb49b73a9..f49ba24642 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -38,8 +38,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Redirector; -use Illuminate\Validation\ValidationException; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\ValidationException; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -128,37 +128,6 @@ class LoginController extends Controller $this->sendFailedLoginResponse($request); } - /** - * Get the login username to be used by the controller. - * - * @return string - */ - public function username() - { - return $this->username; - } - - /** - * Get the failed login response instance. - * - * @param Request $request - * - * @return void - * - * @throws ValidationException - */ - protected function sendFailedLoginResponse(Request $request) - { - $exception = ValidationException::withMessages( - [ - $this->username() => [trans('auth.failed')], - ] - ); - $exception->redirectTo = route('login'); - - throw $exception; - } - /** * Log the user out of the application. * @@ -244,4 +213,35 @@ class LoginController extends Controller return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField')); } + + /** + * Get the login username to be used by the controller. + * + * @return string + */ + public function username() + { + return $this->username; + } + + /** + * Get the failed login response instance. + * + * @param Request $request + * + * @return void + * + * @throws ValidationException + */ + protected function sendFailedLoginResponse(Request $request) + { + $exception = ValidationException::withMessages( + [ + $this->username() => [trans('auth.failed')], + ] + ); + $exception->redirectTo = route('login'); + + throw $exception; + } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index f658a03b26..87085c04ab 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -35,9 +35,9 @@ use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; use Illuminate\View\View; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -117,30 +117,6 @@ class RegisterController extends Controller return redirect($this->redirectPath()); } - /** - * @return bool - * @throws FireflyException - */ - protected function allowedToRegister(): bool - { - // is allowed to register? - $allowRegistration = true; - try { - $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; - } catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) { - $singleUserMode = true; - } - $userCount = User::count(); - $guard = config('auth.defaults.guard'); - if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) { - $allowRegistration = false; - } - if ('web' !== $guard) { - $allowRegistration = false; - } - return $allowRegistration; - } - /** * Show the application registration form if the invitation code is valid. * @@ -201,4 +177,28 @@ class RegisterController extends Controller return view('auth.register', compact('isDemoSite', 'email', 'pageTitle')); } + + /** + * @return bool + * @throws FireflyException + */ + protected function allowedToRegister(): bool + { + // is allowed to register? + $allowRegistration = true; + try { + $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; + } catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) { + $singleUserMode = true; + } + $userCount = User::count(); + $guard = config('auth.defaults.guard'); + if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) { + $allowRegistration = false; + } + if ('web' !== $guard) { + $allowRegistration = false; + } + return $allowRegistration; + } } diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index c50ec7051a..b55cca871d 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -99,26 +99,20 @@ class TwoFactorController extends Controller } /** - * Each MFA history has a timestamp and a code, saving the MFA entries for 5 minutes. So if the - * submitted MFA code has been submitted in the last 5 minutes, it won't work despite being valid. - * * @param string $mfaCode - * @param array $mfaHistory - * - * @return bool */ - private function inMFAHistory(string $mfaCode, array $mfaHistory): bool + private function addToMFAHistory(string $mfaCode): void { - $now = time(); - foreach ($mfaHistory as $entry) { - $time = $entry['time']; - $code = $entry['code']; - if ($code === $mfaCode && $now - $time <= 300) { - return true; - } - } + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $entry = [ + 'time' => time(), + 'code' => $mfaCode, + ]; + $mfaHistory[] = $entry; - return false; + Preferences::set('mfa_history', $mfaHistory); + $this->filterMFAHistory(); } /** @@ -144,20 +138,26 @@ class TwoFactorController extends Controller } /** + * Each MFA history has a timestamp and a code, saving the MFA entries for 5 minutes. So if the + * submitted MFA code has been submitted in the last 5 minutes, it won't work despite being valid. + * * @param string $mfaCode + * @param array $mfaHistory + * + * @return bool */ - private function addToMFAHistory(string $mfaCode): void + private function inMFAHistory(string $mfaCode, array $mfaHistory): bool { - /** @var array $mfaHistory */ - $mfaHistory = Preferences::get('mfa_history', [])->data; - $entry = [ - 'time' => time(), - 'code' => $mfaCode, - ]; - $mfaHistory[] = $entry; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($code === $mfaCode && $now - $time <= 300) { + return true; + } + } - Preferences::set('mfa_history', $mfaHistory); - $this->filterMFAHistory(); + return false; } /** diff --git a/app/Http/Controllers/Bill/IndexController.php b/app/Http/Controllers/Bill/IndexController.php index 6bbe980bf9..41702e41b6 100644 --- a/app/Http/Controllers/Bill/IndexController.php +++ b/app/Http/Controllers/Bill/IndexController.php @@ -135,49 +135,26 @@ class IndexController extends Controller } /** - * @param array $bills + * Set the order of a bill. * - * @return array - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface + * @param Request $request + * @param Bill $bill + * + * @return JsonResponse */ - private function getSums(array $bills): array + public function setOrder(Request $request, Bill $bill): JsonResponse { - $sums = []; - $range = app('navigation')->getViewRange(false); - - /** @var array $group */ - foreach ($bills as $groupOrder => $group) { - /** @var array $bill */ - foreach ($group['bills'] as $bill) { - if (false === $bill['active']) { - continue; - } - - $currencyId = $bill['currency_id']; - $sums[$groupOrder][$currencyId] = $sums[$groupOrder][$currencyId] ?? [ - 'currency_id' => $currencyId, - 'currency_code' => $bill['currency_code'], - 'currency_name' => $bill['currency_name'], - 'currency_symbol' => $bill['currency_symbol'], - 'currency_decimal_places' => $bill['currency_decimal_places'], - 'avg' => '0', - 'period' => $range, - 'per_period' => '0', - ]; - // only fill in avg when bill is active. - if (count($bill['pay_dates']) > 0) { - $avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2'); - $avg = bcmul($avg, (string)count($bill['pay_dates'])); - $sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg); - } - // fill in per period regardless: - $sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range)); - } + $objectGroupTitle = (string)$request->get('objectGroupTitle'); + $newOrder = (int)$request->get('order'); + $this->repository->setOrder($bill, $newOrder); + if ('' !== $objectGroupTitle) { + $this->repository->setObjectGroup($bill, $objectGroupTitle); + } + if ('' === $objectGroupTitle) { + $this->repository->removeObjectGroup($bill); } - return $sums; + return response()->json(['data' => 'OK']); } /** @@ -227,6 +204,52 @@ class IndexController extends Controller return $perPeriod; } + /** + * @param array $bills + * + * @return array + * @throws FireflyException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function getSums(array $bills): array + { + $sums = []; + $range = app('navigation')->getViewRange(false); + + /** @var array $group */ + foreach ($bills as $groupOrder => $group) { + /** @var array $bill */ + foreach ($group['bills'] as $bill) { + if (false === $bill['active']) { + continue; + } + + $currencyId = $bill['currency_id']; + $sums[$groupOrder][$currencyId] = $sums[$groupOrder][$currencyId] ?? [ + 'currency_id' => $currencyId, + 'currency_code' => $bill['currency_code'], + 'currency_name' => $bill['currency_name'], + 'currency_symbol' => $bill['currency_symbol'], + 'currency_decimal_places' => $bill['currency_decimal_places'], + 'avg' => '0', + 'period' => $range, + 'per_period' => '0', + ]; + // only fill in avg when bill is active. + if (count($bill['pay_dates']) > 0) { + $avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2'); + $avg = bcmul($avg, (string)count($bill['pay_dates'])); + $sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg); + } + // fill in per period regardless: + $sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range)); + } + } + + return $sums; + } + /** * @param array $sums * @@ -264,27 +287,4 @@ class IndexController extends Controller return $totals; } - - /** - * Set the order of a bill. - * - * @param Request $request - * @param Bill $bill - * - * @return JsonResponse - */ - public function setOrder(Request $request, Bill $bill): JsonResponse - { - $objectGroupTitle = (string)$request->get('objectGroupTitle'); - $newOrder = (int)$request->get('order'); - $this->repository->setOrder($bill, $newOrder); - if ('' !== $objectGroupTitle) { - $this->repository->setObjectGroup($bill, $objectGroupTitle); - } - if ('' === $objectGroupTitle) { - $this->repository->removeObjectGroup($bill); - } - - return response()->json(['data' => 'OK']); - } } diff --git a/app/Http/Controllers/Budget/BudgetLimitController.php b/app/Http/Controllers/Budget/BudgetLimitController.php index b1d00e34e6..a966d80526 100644 --- a/app/Http/Controllers/Budget/BudgetLimitController.php +++ b/app/Http/Controllers/Budget/BudgetLimitController.php @@ -26,7 +26,6 @@ namespace FireflyIII\Http\Controllers\Budget; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\TransactionCurrency; @@ -41,8 +40,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index 5838305182..05e6dcaff6 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -173,6 +173,29 @@ class IndexController extends Controller ); } + /** + * @param Request $request + * @param BudgetRepositoryInterface $repository + * + * @return JsonResponse + */ + public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse + { + $budgetIds = $request->get('budgetIds'); + + foreach ($budgetIds as $index => $budgetId) { + $budgetId = (int)$budgetId; + $budget = $repository->find($budgetId); + if (null !== $budget) { + Log::debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1)); + $repository->setBudgetOrder($budget, $index + 1); + } + } + app('preferences')->mark(); + + return response()->json(['OK']); + } + /** * @param Carbon $start * @param Carbon $end @@ -328,27 +351,4 @@ class IndexController extends Controller return $sums; } - - /** - * @param Request $request - * @param BudgetRepositoryInterface $repository - * - * @return JsonResponse - */ - public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse - { - $budgetIds = $request->get('budgetIds'); - - foreach ($budgetIds as $index => $budgetId) { - $budgetId = (int)$budgetId; - $budget = $repository->find($budgetId); - if (null !== $budget) { - Log::debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1)); - $repository->setBudgetOrder($budget, $index + 1); - } - } - app('preferences')->mark(); - - return response()->json(['OK']); - } } diff --git a/app/Http/Controllers/Category/NoCategoryController.php b/app/Http/Controllers/Category/NoCategoryController.php index d77c5df7fd..5cdf5ad222 100644 --- a/app/Http/Controllers/Category/NoCategoryController.php +++ b/app/Http/Controllers/Category/NoCategoryController.php @@ -33,9 +33,9 @@ use FireflyIII\Support\Http\Controllers\PeriodOverview; use Illuminate\Contracts\View\Factory; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use JsonException; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 86af022265..1b1cf09140 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -40,8 +40,8 @@ use FireflyIII\Support\Http\Controllers\ChartGeneration; use FireflyIII\Support\Http\Controllers\DateCalculation; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -173,22 +173,6 @@ class AccountController extends Controller return response()->json($data); } - /** - * Expenses per budget for all time, as shown on account overview. - * - * @param AccountRepositoryInterface $repository - * @param Account $account - * - * @return JsonResponse - */ - public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse - { - $start = $repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); - $end = today(config('app.timezone')); - - return $this->expenseBudget($account, $start, $end); - } - /** * Expenses per budget, as shown on account overview. * @@ -248,19 +232,19 @@ class AccountController extends Controller } /** - * Expenses grouped by category for account. + * Expenses per budget for all time, as shown on account overview. * * @param AccountRepositoryInterface $repository * @param Account $account * * @return JsonResponse */ - public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse { $start = $repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); $end = today(config('app.timezone')); - return $this->expenseCategory($account, $start, $end); + return $this->expenseBudget($account, $start, $end); } /** @@ -319,6 +303,22 @@ class AccountController extends Controller return response()->json($data); } + /** + * Expenses grouped by category for account. + * + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return JsonResponse + */ + public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + { + $start = $repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); + $end = today(config('app.timezone')); + + return $this->expenseCategory($account, $start, $end); + } + /** * Shows the balances for all the user's frontpage accounts. * @@ -347,22 +347,6 @@ class AccountController extends Controller return response()->json($this->accountBalanceChart($accounts, $start, $end)); } - /** - * Shows the income grouped by category for an account, in all time. - * - * @param AccountRepositoryInterface $repository - * @param Account $account - * - * @return JsonResponse - */ - public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse - { - $start = $repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); - $end = today(config('app.timezone')); - - return $this->incomeCategory($account, $start, $end); - } - /** * Shows all income per account for each category. * @@ -419,6 +403,22 @@ class AccountController extends Controller return response()->json($data); } + /** + * Shows the income grouped by category for an account, in all time. + * + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return JsonResponse + */ + public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + { + $start = $repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); + $end = today(config('app.timezone')); + + return $this->incomeCategory($account, $start, $end); + } + /** * Shows overview of account during a single period. * @@ -460,54 +460,6 @@ class AccountController extends Controller return response()->json($data); } - /** - * @param Carbon $start - * @param Carbon $end - * @param Account $account - * @param TransactionCurrency $currency - * - * @return array - * @throws FireflyException - * @throws JsonException - */ - private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array - { - $locale = app('steam')->getLocale(); - $step = $this->calculateStep($start, $end); - $result = [ - 'label' => sprintf('%s (%s)', $account->name, $currency->symbol), - 'currency_symbol' => $currency->symbol, - 'currency_code' => $currency->code, - ]; - $entries = []; - $current = clone $start; - if ('1D' === $step) { - // per day the entire period, balance for every day. - $format = (string)trans('config.month_and_day_js', [], $locale); - $range = app('steam')->balanceInRange($account, $start, $end, $currency); - $previous = array_values($range)[0]; - while ($end >= $current) { - $theDate = $current->format('Y-m-d'); - $balance = $range[$theDate] ?? $previous; - $label = $current->isoFormat($format); - $entries[$label] = (float)$balance; - $previous = $balance; - $current->addDay(); - } - } - if ('1W' === $step || '1M' === $step || '1Y' === $step) { - while ($end >= $current) { - $balance = (float)app('steam')->balance($account, $current, $currency); - $label = app('navigation')->periodShow($current, $step); - $entries[$label] = $balance; - $current = app('navigation')->addPeriod($current, $step, 0); - } - } - $result['entries'] = $entries; - - return $result; - } - /** * Shows the balances for a given set of dates and accounts. * @@ -619,4 +571,52 @@ class AccountController extends Controller return response()->json($data); } + + /** + * @param Carbon $start + * @param Carbon $end + * @param Account $account + * @param TransactionCurrency $currency + * + * @return array + * @throws FireflyException + * @throws JsonException + */ + private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array + { + $locale = app('steam')->getLocale(); + $step = $this->calculateStep($start, $end); + $result = [ + 'label' => sprintf('%s (%s)', $account->name, $currency->symbol), + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, + ]; + $entries = []; + $current = clone $start; + if ('1D' === $step) { + // per day the entire period, balance for every day. + $format = (string)trans('config.month_and_day_js', [], $locale); + $range = app('steam')->balanceInRange($account, $start, $end, $currency); + $previous = array_values($range)[0]; + while ($end >= $current) { + $theDate = $current->format('Y-m-d'); + $balance = $range[$theDate] ?? $previous; + $label = $current->isoFormat($format); + $entries[$label] = (float)$balance; + $previous = $balance; + $current->addDay(); + } + } + if ('1W' === $step || '1M' === $step || '1Y' === $step) { + while ($end >= $current) { + $balance = (float)app('steam')->balance($account, $current, $currency); + $label = app('navigation')->periodShow($current, $step); + $entries[$label] = $balance; + $current = app('navigation')->addPeriod($current, $step, 0); + } + } + $result['entries'] = $entries; + + return $result; + } } diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 9476d3c4be..8cd516cc0c 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -226,29 +226,6 @@ class BudgetReportController extends Controller return response()->json($data); } - /** - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function makeEntries(Carbon $start, Carbon $end): array - { - $return = []; - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $preferredRange = app('navigation')->preferredRangeFormat($start, $end); - $currentStart = clone $start; - while ($currentStart <= $end) { - $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); - $key = $currentStart->isoFormat($format); - $return[$key] = '0'; - $currentStart = clone $currentEnd; - $currentStart->addDay()->startOfDay(); - } - - return $return; - } - /** * Chart that groups expenses by the account. * @@ -285,4 +262,27 @@ class BudgetReportController extends Controller return response()->json($data); } + + /** + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function makeEntries(Carbon $start, Carbon $end): array + { + $return = []; + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $preferredRange = app('navigation')->preferredRangeFormat($start, $end); + $currentStart = clone $start; + while ($currentStart <= $end) { + $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); + $key = $currentStart->isoFormat($format); + $return[$key] = '0'; + $currentStart = clone $currentEnd; + $currentStart->addDay()->startOfDay(); + } + + return $return; + } } diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index bed0c3eaf0..54de47bb70 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -102,14 +102,6 @@ class CategoryController extends Controller return response()->json($data); } - /** - * @return Carbon - */ - private function getDate(): Carbon - { - return today(config('app.timezone')); - } - /** * Shows the category chart on the front page. * TODO test method for category refactor. @@ -166,6 +158,81 @@ class CategoryController extends Controller return response()->json($data); } + /** + * Chart for period for transactions without a category. + * TODO test me. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return JsonResponse + */ + public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse + { + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('chart.category.period.no-cat'); + $cache->addProperty($accounts->pluck('id')->toArray()); + if ($cache->has()) { + return response()->json($cache->get()); + } + $data = $this->reportPeriodChart($accounts, $start, $end, null); + + $cache->store($data); + + return response()->json($data); + } + + /** + * Chart for a specific period. + * TODO test me, for category refactor. + * + * @param Category $category + * @param Carbon $date + * + * @return JsonResponse + * @throws FireflyException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function specificPeriod(Category $category, Carbon $date): JsonResponse + { + $range = app('navigation')->getViewRange(false); + $start = app('navigation')->startOfPeriod($date, $range); + $end = session()->get('end'); + if ($end < $start) { + [$end, $start] = [$start, $end]; + } + + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($category->id); + $cache->addProperty('chart.category.period-chart'); + if ($cache->has()) { + return response()->json($cache->get()); + } + + /** @var WholePeriodChartGenerator $chartGenerator */ + $chartGenerator = app(WholePeriodChartGenerator::class); + $chartData = $chartGenerator->generate($category, $start, $end); + $data = $this->generator->multiSet($chartData); + + $cache->store($data); + + return response()->json($data); + } + + /** + * @return Carbon + */ + private function getDate(): Carbon + { + return today(config('app.timezone')); + } + /** * Generate report chart for either with or without category. * @@ -251,71 +318,4 @@ class CategoryController extends Controller return $this->generator->multiSet($chartData); } - - /** - * Chart for period for transactions without a category. - * TODO test me. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return JsonResponse - */ - public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse - { - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('chart.category.period.no-cat'); - $cache->addProperty($accounts->pluck('id')->toArray()); - if ($cache->has()) { - return response()->json($cache->get()); - } - $data = $this->reportPeriodChart($accounts, $start, $end, null); - - $cache->store($data); - - return response()->json($data); - } - - /** - * Chart for a specific period. - * TODO test me, for category refactor. - * - * @param Category $category - * @param Carbon $date - * - * @return JsonResponse - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function specificPeriod(Category $category, Carbon $date): JsonResponse - { - $range = app('navigation')->getViewRange(false); - $start = app('navigation')->startOfPeriod($date, $range); - $end = session()->get('end'); - if ($end < $start) { - [$end, $start] = [$start, $end]; - } - - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($category->id); - $cache->addProperty('chart.category.period-chart'); - if ($cache->has()) { - return response()->json($cache->get()); - } - - /** @var WholePeriodChartGenerator $chartGenerator */ - $chartGenerator = app(WholePeriodChartGenerator::class); - $chartData = $chartGenerator->generate($category, $start, $end); - $data = $this->generator->multiSet($chartData); - - $cache->store($data); - - return response()->json($data); - } } diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index 8884796ef2..1e6da6b0db 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -319,31 +319,6 @@ class CategoryReportController extends Controller return response()->json($data); } - /** - * TODO duplicate function - * - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function makeEntries(Carbon $start, Carbon $end): array - { - $return = []; - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $preferredRange = app('navigation')->preferredRangeFormat($start, $end); - $currentStart = clone $start; - while ($currentStart <= $end) { - $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); - $key = $currentStart->isoFormat($format); - $return[$key] = '0'; - $currentStart = clone $currentEnd; - $currentStart->addDay()->startOfDay(); - } - - return $return; - } - /** * @param Collection $accounts * @param Collection $categories @@ -415,4 +390,29 @@ class CategoryReportController extends Controller return response()->json($data); } + + /** + * TODO duplicate function + * + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function makeEntries(Carbon $start, Carbon $end): array + { + $return = []; + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $preferredRange = app('navigation')->preferredRangeFormat($start, $end); + $currentStart = clone $start; + while ($currentStart <= $end) { + $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); + $key = $currentStart->isoFormat($format); + $return[$key] = '0'; + $currentStart = clone $currentEnd; + $currentStart->addDay()->startOfDay(); + } + + return $return; + } } diff --git a/app/Http/Controllers/Chart/DoubleReportController.php b/app/Http/Controllers/Chart/DoubleReportController.php index 733e4337b3..99130b929c 100644 --- a/app/Http/Controllers/Chart/DoubleReportController.php +++ b/app/Http/Controllers/Chart/DoubleReportController.php @@ -247,56 +247,6 @@ class DoubleReportController extends Controller return response()->json($data); } - /** - * TODO duplicate function - * - * @param Collection $accounts - * @param int $id - * @param string $name - * @param null|string $iban - * - * @return string - */ - private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string - { - /** @var Account $account */ - foreach ($accounts as $account) { - if ($account->name === $name && $account->id !== $id) { - return $account->name; - } - if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { - return $account->iban; - } - } - - return $name; - } - - /** - * TODO duplicate function - * - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function makeEntries(Carbon $start, Carbon $end): array - { - $return = []; - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $preferredRange = app('navigation')->preferredRangeFormat($start, $end); - $currentStart = clone $start; - while ($currentStart <= $end) { - $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); - $key = $currentStart->isoFormat($format); - $return[$key] = '0'; - $currentStart = clone $currentEnd; - $currentStart->addDay()->startOfDay(); - } - - return $return; - } - /** * @param Collection $accounts * @param Collection $others @@ -416,4 +366,54 @@ class DoubleReportController extends Controller return response()->json($data); } + + /** + * TODO duplicate function + * + * @param Collection $accounts + * @param int $id + * @param string $name + * @param null|string $iban + * + * @return string + */ + private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string + { + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->name === $name && $account->id !== $id) { + return $account->name; + } + if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { + return $account->iban; + } + } + + return $name; + } + + /** + * TODO duplicate function + * + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function makeEntries(Carbon $start, Carbon $end): array + { + $return = []; + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $preferredRange = app('navigation')->preferredRangeFormat($start, $end); + $currentStart = clone $start; + while ($currentStart <= $end) { + $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); + $key = $currentStart->isoFormat($format); + $return[$key] = '0'; + $currentStart = clone $currentEnd; + $currentStart->addDay()->startOfDay(); + } + + return $return; + } } diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index a762f22e27..685bb3fd5e 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -37,8 +37,8 @@ use FireflyIII\Support\Http\Controllers\BasicDataSupport; use FireflyIII\Support\Http\Controllers\ChartGeneration; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class ReportController. diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php index ad37e32740..d462590876 100644 --- a/app/Http/Controllers/Chart/TagReportController.php +++ b/app/Http/Controllers/Chart/TagReportController.php @@ -324,31 +324,6 @@ class TagReportController extends Controller return response()->json($data); } - /** - * TODO duplicate function - * - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function makeEntries(Carbon $start, Carbon $end): array - { - $return = []; - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $preferredRange = app('navigation')->preferredRangeFormat($start, $end); - $currentStart = clone $start; - while ($currentStart <= $end) { - $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); - $key = $currentStart->isoFormat($format); - $return[$key] = '0'; - $currentStart = clone $currentEnd; - $currentStart->addDay()->startOfDay(); - } - - return $return; - } - /** * @param Collection $accounts * @param Collection $tags @@ -488,4 +463,29 @@ class TagReportController extends Controller return response()->json($data); } + + /** + * TODO duplicate function + * + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function makeEntries(Carbon $start, Carbon $end): array + { + $return = []; + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $preferredRange = app('navigation')->preferredRangeFormat($start, $end); + $currentStart = clone $start; + while ($currentStart <= $end) { + $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); + $key = $currentStart->isoFormat($format); + $return[$key] = '0'; + $currentStart = clone $currentEnd; + $currentStart->addDay()->startOfDay(); + } + + return $return; + } } diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index 2715d916d5..00364402fb 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -35,8 +35,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 4ffa5f6240..268efe234e 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -69,14 +69,14 @@ class HomeController extends Controller { try { $stringStart = e((string)$request->get('start')); - $start = Carbon::createFromFormat('Y-m-d', $stringStart); + $start = Carbon::createFromFormat('Y-m-d', $stringStart); } catch (InvalidFormatException $e) { Log::error(sprintf('Start: could not parse date string "%s" so ignore it.', $stringStart)); $start = Carbon::now()->startOfMonth(); } try { $stringEnd = e((string)$request->get('end')); - $end = Carbon::createFromFormat('Y-m-d', $stringEnd); + $end = Carbon::createFromFormat('Y-m-d', $stringEnd); } catch (InvalidFormatException $e) { Log::error(sprintf('End could not parse date string "%s" so ignore it.', $stringEnd)); $end = Carbon::now()->endOfMonth(); diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php index fbe8ac5419..b0778d25ea 100644 --- a/app/Http/Controllers/Json/ReconcileController.php +++ b/app/Http/Controllers/Json/ReconcileController.php @@ -34,8 +34,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Throwable; /** @@ -169,46 +169,6 @@ class ReconcileController extends Controller return response()->json($return); } - /** - * @param Account $account - * @param TransactionCurrency $currency - * @param array $journal - * @param string $amount - * - * @return string - */ - private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string - { - $toAdd = '0'; - Log::debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description'])); - - // not much magic below we need to cover using tests. - - if ($account->id === $journal['source_account_id']) { - if ($currency->id === $journal['currency_id']) { - $toAdd = $journal['amount']; - } - if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { - $toAdd = $journal['foreign_amount']; - } - } - if ($account->id === $journal['destination_account_id']) { - if ($currency->id === $journal['currency_id']) { - $toAdd = bcmul($journal['amount'], '-1'); - } - if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { - $toAdd = bcmul($journal['foreign_amount'], '-1'); - } - } - - - Log::debug(sprintf('Going to add %s to %s', $toAdd, $amount)); - $amount = bcadd($amount, $toAdd); - Log::debug(sprintf('Result is %s', $amount)); - - return $amount; - } - /** * Returns a list of transactions in a modal. * @@ -265,6 +225,46 @@ class ReconcileController extends Controller return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]); } + /** + * @param Account $account + * @param TransactionCurrency $currency + * @param array $journal + * @param string $amount + * + * @return string + */ + private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string + { + $toAdd = '0'; + Log::debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description'])); + + // not much magic below we need to cover using tests. + + if ($account->id === $journal['source_account_id']) { + if ($currency->id === $journal['currency_id']) { + $toAdd = $journal['amount']; + } + if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { + $toAdd = $journal['foreign_amount']; + } + } + if ($account->id === $journal['destination_account_id']) { + if ($currency->id === $journal['currency_id']) { + $toAdd = bcmul($journal['amount'], '-1'); + } + if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { + $toAdd = bcmul($journal['foreign_amount'], '-1'); + } + } + + + Log::debug(sprintf('Going to add %s to %s', $toAdd, $amount)); + $amount = bcadd($amount, $toAdd); + Log::debug(sprintf('Result is %s', $amount)); + + return $amount; + } + /** * "fix" amounts to make it easier on the reconciliation overview: * diff --git a/app/Http/Controllers/PiggyBank/AmountController.php b/app/Http/Controllers/PiggyBank/AmountController.php index 586ba632c3..d76491a68e 100644 --- a/app/Http/Controllers/PiggyBank/AmountController.php +++ b/app/Http/Controllers/PiggyBank/AmountController.php @@ -32,8 +32,8 @@ use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * Class AmountController diff --git a/app/Http/Controllers/PiggyBank/IndexController.php b/app/Http/Controllers/PiggyBank/IndexController.php index 47c240c952..9383eebd87 100644 --- a/app/Http/Controllers/PiggyBank/IndexController.php +++ b/app/Http/Controllers/PiggyBank/IndexController.php @@ -144,6 +144,29 @@ class IndexController extends Controller return view('piggy-banks.index', compact('piggyBanks', 'accounts')); } + /** + * Set the order of a piggy bank. + * + * @param Request $request + * @param PiggyBank $piggyBank + * + * @return JsonResponse + */ + public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse + { + $objectGroupTitle = (string)$request->get('objectGroupTitle'); + $newOrder = (int)$request->get('order'); + $this->piggyRepos->setOrder($piggyBank, $newOrder); + if ('' !== $objectGroupTitle) { + $this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle); + } + if ('' === $objectGroupTitle) { + $this->piggyRepos->removeObjectGroup($piggyBank); + } + + return response()->json(['data' => 'OK']); + } + /** * @param array $piggyBanks * @@ -183,27 +206,4 @@ class IndexController extends Controller return $piggyBanks; } - - /** - * Set the order of a piggy bank. - * - * @param Request $request - * @param PiggyBank $piggyBank - * - * @return JsonResponse - */ - public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse - { - $objectGroupTitle = (string)$request->get('objectGroupTitle'); - $newOrder = (int)$request->get('order'); - $this->piggyRepos->setOrder($piggyBank, $newOrder); - if ('' !== $objectGroupTitle) { - $this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle); - } - if ('' === $objectGroupTitle) { - $this->piggyRepos->removeObjectGroup($piggyBank); - } - - return response()->json(['data' => 'OK']); - } } diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 97062d18a6..067e040eaa 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -262,7 +262,7 @@ class PreferencesController extends Controller // dark mode $darkMode = $request->get('darkMode') ?? 'browser'; - if(in_array($darkMode, config('firefly.available_dark_modes'), true)) { + if (in_array($darkMode, config('firefly.available_dark_modes'), true)) { app('preferences')->set('darkMode', $darkMode); } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 2ecf0b661b..f7189e3df0 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -49,9 +49,9 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use Laravel\Passport\ClientRepository; -use Illuminate\Support\Facades\Log; use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException; use PragmaRX\Google2FA\Exceptions\InvalidCharactersException; use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException; @@ -98,6 +98,51 @@ class ProfileController extends Controller $this->middleware(IsDemoUser::class)->except(['index']); } + /** + * Change your email address. + * + * @param Request $request + * + * @return Factory|RedirectResponse|View + */ + public function changeEmail(Request $request): Factory|RedirectResponse|View + { + if (!$this->internalAuth || !$this->internalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + + $title = auth()->user()->email; + $email = auth()->user()->email; + $subTitle = (string)trans('firefly.change_your_email'); + $subTitleIcon = 'fa-envelope'; + + return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email')); + } + + /** + * Change your password. + * + * @param Request $request + * + * @return Factory|RedirectResponse|Redirector|View + */ + public function changePassword(Request $request) + { + if (!$this->internalAuth || !$this->internalIdentity) { + $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); + + return redirect(route('profile.index')); + } + + $title = auth()->user()->email; + $subTitle = (string)trans('firefly.change_your_password'); + $subTitleIcon = 'fa-key'; + + return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon')); + } + /** * View that generates a 2FA code for the user. * @@ -401,29 +446,6 @@ class ProfileController extends Controller return redirect(route('index')); } - /** - * Change your email address. - * - * @param Request $request - * - * @return Factory|RedirectResponse|View - */ - public function changeEmail(Request $request): Factory|RedirectResponse|View - { - if (!$this->internalAuth || !$this->internalIdentity) { - $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); - - return redirect(route('profile.index')); - } - - $title = auth()->user()->email; - $email = auth()->user()->email; - $subTitle = (string)trans('firefly.change_your_email'); - $subTitleIcon = 'fa-envelope'; - - return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email')); - } - /** * Submit change password form. * @@ -459,28 +481,6 @@ class ProfileController extends Controller return redirect(route('profile.index')); } - /** - * Change your password. - * - * @param Request $request - * - * @return Factory|RedirectResponse|Redirector|View - */ - public function changePassword(Request $request) - { - if (!$this->internalAuth || !$this->internalIdentity) { - $request->session()->flash('error', trans('firefly.external_user_mgt_disabled')); - - return redirect(route('profile.index')); - } - - $title = auth()->user()->email; - $subTitle = (string)trans('firefly.change_your_password'); - $subTitleIcon = 'fa-key'; - - return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon')); - } - /** * Submit 2FA for the first time. * @@ -531,51 +531,6 @@ class ProfileController extends Controller return redirect(route('profile.index')); } - /** - * TODO duplicate code. - * - * @param string $mfaCode - * - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - private function addToMFAHistory(string $mfaCode): void - { - /** @var array $mfaHistory */ - $mfaHistory = app('preferences')->get('mfa_history', [])->data; - $entry = [ - 'time' => time(), - 'code' => $mfaCode, - ]; - $mfaHistory[] = $entry; - - app('preferences')->set('mfa_history', $mfaHistory); - $this->filterMFAHistory(); - } - - /** - * Remove old entries from the preferences array. - */ - private function filterMFAHistory(): void - { - /** @var array $mfaHistory */ - $mfaHistory = app('preferences')->get('mfa_history', [])->data; - $newHistory = []; - $now = time(); - foreach ($mfaHistory as $entry) { - $time = $entry['time']; - $code = $entry['code']; - if ($now - $time <= 300) { - $newHistory[] = [ - 'time' => $time, - 'code' => $code, - ]; - } - } - app('preferences')->set('mfa_history', $newHistory); - } - /** * Submit delete account. * @@ -715,4 +670,49 @@ class ProfileController extends Controller return redirect(route('login')); } + + /** + * TODO duplicate code. + * + * @param string $mfaCode + * + * @throws FireflyException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function addToMFAHistory(string $mfaCode): void + { + /** @var array $mfaHistory */ + $mfaHistory = app('preferences')->get('mfa_history', [])->data; + $entry = [ + 'time' => time(), + 'code' => $mfaCode, + ]; + $mfaHistory[] = $entry; + + app('preferences')->set('mfa_history', $mfaHistory); + $this->filterMFAHistory(); + } + + /** + * Remove old entries from the preferences array. + */ + private function filterMFAHistory(): void + { + /** @var array $mfaHistory */ + $mfaHistory = app('preferences')->get('mfa_history', [])->data; + $newHistory = []; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($now - $time <= 300) { + $newHistory[] = [ + 'time' => $time, + 'code' => $code, + ]; + } + } + app('preferences')->set('mfa_history', $newHistory); + } } diff --git a/app/Http/Controllers/Report/BudgetController.php b/app/Http/Controllers/Report/BudgetController.php index 809f93e51e..60560315fe 100644 --- a/app/Http/Controllers/Report/BudgetController.php +++ b/app/Http/Controllers/Report/BudgetController.php @@ -34,9 +34,9 @@ use FireflyIII\Support\Http\Controllers\BasicDataSupport; use FireflyIII\Support\Report\Budget\BudgetReportGenerator; use Illuminate\Contracts\View\Factory; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use JsonException; -use Illuminate\Support\Facades\Log; use Throwable; /** diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index 58ab80f5af..729ba83d3e 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -35,8 +35,8 @@ use FireflyIII\Support\Http\Controllers\BasicDataSupport; use FireflyIII\Support\Report\Category\CategoryReportGenerator; use Illuminate\Contracts\View\Factory; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Throwable; /** diff --git a/app/Http/Controllers/Report/DoubleController.php b/app/Http/Controllers/Report/DoubleController.php index 75f2ab43ce..1dfda88e75 100644 --- a/app/Http/Controllers/Report/DoubleController.php +++ b/app/Http/Controllers/Report/DoubleController.php @@ -32,8 +32,8 @@ use FireflyIII\Repositories\Account\OperationsRepositoryInterface; use FireflyIII\Support\Http\Controllers\AugumentData; use Illuminate\Contracts\View\Factory; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Throwable; /** @@ -295,31 +295,6 @@ class DoubleController extends Controller return view('reports.double.partials.accounts', compact('sums', 'report')); } - /** - * TODO this method is duplicated. - * - * @param Collection $accounts - * @param int $id - * @param string $name - * @param string|null $iban - * - * @return string - */ - private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string - { - /** @var Account $account */ - foreach ($accounts as $account) { - if ($account->name === $name && $account->id !== $id) { - return $account->name; - } - if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { - return $account->iban; - } - } - - return $name; - } - /** * @param Collection $accounts * @param Collection $double @@ -521,4 +496,29 @@ class DoubleController extends Controller return $result; } + + /** + * TODO this method is duplicated. + * + * @param Collection $accounts + * @param int $id + * @param string $name + * @param string|null $iban + * + * @return string + */ + private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string + { + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->name === $name && $account->id !== $id) { + return $account->name; + } + if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { + return $account->iban; + } + } + + return $name; + } } diff --git a/app/Http/Controllers/Report/TagController.php b/app/Http/Controllers/Report/TagController.php index fa375a05cb..5929d992d5 100644 --- a/app/Http/Controllers/Report/TagController.php +++ b/app/Http/Controllers/Report/TagController.php @@ -31,8 +31,8 @@ use FireflyIII\Models\Tag; use FireflyIII\Repositories\Tag\OperationsRepositoryInterface; use Illuminate\Contracts\View\Factory; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Throwable; /** diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 63b8e4d6c1..c266fd6b66 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -38,8 +38,8 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/app/Http/Controllers/Rule/EditController.php b/app/Http/Controllers/Rule/EditController.php index 2e9eecd758..22261682e8 100644 --- a/app/Http/Controllers/Rule/EditController.php +++ b/app/Http/Controllers/Rule/EditController.php @@ -36,8 +36,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Throwable; /** @@ -139,6 +139,32 @@ class EditController extends Controller return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount')); } + /** + * Update the rule. + * + * @param RuleFormRequest $request + * @param Rule $rule + * + * @return RedirectResponse|Redirector + */ + public function update(RuleFormRequest $request, Rule $rule) + { + $data = $request->getRuleData(); + + $this->ruleRepos->update($rule, $data); + + session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title])); + app('preferences')->mark(); + $redirect = redirect($this->getPreviousUrl('rules.edit.url')); + if (1 === (int)$request->get('return_to_edit')) { + session()->put('rules.edit.fromUpdate', true); + + $redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); + } + + return $redirect; + } + /** * @param array $submittedOperators * @@ -182,30 +208,4 @@ class EditController extends Controller return $renderedEntries; } - - /** - * Update the rule. - * - * @param RuleFormRequest $request - * @param Rule $rule - * - * @return RedirectResponse|Redirector - */ - public function update(RuleFormRequest $request, Rule $rule) - { - $data = $request->getRuleData(); - - $this->ruleRepos->update($rule, $data); - - session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title])); - app('preferences')->mark(); - $redirect = redirect($this->getPreviousUrl('rules.edit.url')); - if (1 === (int)$request->get('return_to_edit')) { - session()->put('rules.edit.fromUpdate', true); - - $redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); - } - - return $redirect; - } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 5563500c27..f945d3fbb0 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -29,8 +29,8 @@ use FireflyIII\Support\Search\SearchInterface; use Illuminate\Contracts\View\Factory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Throwable; /** diff --git a/app/Http/Controllers/System/HealthcheckController.php b/app/Http/Controllers/System/HealthcheckController.php index b6aad6e927..2a17ca2f5f 100644 --- a/app/Http/Controllers/System/HealthcheckController.php +++ b/app/Http/Controllers/System/HealthcheckController.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\System; -use FireflyIII\User; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\User; use Illuminate\Http\Response; /** diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 72023adcd0..f10d9ff6cd 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -91,6 +91,30 @@ class InstallController extends Controller return view('install.index'); } + /** + * Create specific RSA keys. + */ + public function keys(): void + { + // switch on PHP version. + $keys = []; + // switch on class existence. + Log::info('Will run PHP8 code.'); + $keys = RSA::createKey(4096); + + [$publicKey, $privateKey] = [ + Passport::keyPath('oauth-public.key'), + Passport::keyPath('oauth-private.key'), + ]; + + if (file_exists($publicKey) || file_exists($privateKey)) { + return; + } + + file_put_contents($publicKey, $keys['publickey']); + file_put_contents($privateKey, $keys['privatekey']); + } + /** * @param Request $request * @@ -161,28 +185,4 @@ class InstallController extends Controller return true; } - - /** - * Create specific RSA keys. - */ - public function keys(): void - { - // switch on PHP version. - $keys = []; - // switch on class existence. - Log::info('Will run PHP8 code.'); - $keys = RSA::createKey(4096); - - [$publicKey, $privateKey] = [ - Passport::keyPath('oauth-public.key'), - Passport::keyPath('oauth-private.key'), - ]; - - if (file_exists($publicKey) || file_exists($privateKey)) { - return; - } - - file_put_contents($publicKey, $keys['publickey']); - file_put_contents($privateKey, $keys['privatekey']); - } } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index fc35f29ee4..a73e7819b7 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -34,8 +34,8 @@ use FireflyIII\Support\Http\Controllers\PeriodOverview; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -117,6 +117,24 @@ class TagController extends Controller return view('tags.delete', compact('tag', 'subTitle')); } + /** + * Destroy a tag. + * + * @param Tag $tag + * + * @return RedirectResponse + */ + public function destroy(Tag $tag): RedirectResponse + { + $tagName = $tag->tag; + $this->repository->destroy($tag); + + session()->flash('success', (string)trans('firefly.deleted_tag', ['tag' => $tagName])); + app('preferences')->mark(); + + return redirect($this->getPreviousUrl('tags.delete.url')); + } + /** * Edit a tag. * @@ -205,24 +223,6 @@ class TagController extends Controller return redirect(route('tags.index')); } - /** - * Destroy a tag. - * - * @param Tag $tag - * - * @return RedirectResponse - */ - public function destroy(Tag $tag): RedirectResponse - { - $tagName = $tag->tag; - $this->repository->destroy($tag); - - session()->flash('success', (string)trans('firefly.deleted_tag', ['tag' => $tagName])); - app('preferences')->mark(); - - return redirect($this->getPreviousUrl('tags.delete.url')); - } - /** * Show a single tag. * diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index e9129d4f48..8e95a26a03 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -34,8 +34,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; use Illuminate\Support\Collection; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * Class BulkController @@ -152,6 +152,24 @@ class BulkController extends Controller return true; } + /** + * @param TransactionJournal $journal + * @param bool $ignoreUpdate + * @param string $category + * + * @return bool + */ + private function updateJournalCategory(TransactionJournal $journal, bool $ignoreUpdate, string $category): bool + { + if (true === $ignoreUpdate) { + return false; + } + Log::debug(sprintf('Set budget to %s', $category)); + $this->repository->updateCategory($journal, $category); + + return true; + } + /** * @param TransactionJournal $journal * @param string $action @@ -173,22 +191,4 @@ class BulkController extends Controller return true; } - - /** - * @param TransactionJournal $journal - * @param bool $ignoreUpdate - * @param string $category - * - * @return bool - */ - private function updateJournalCategory(TransactionJournal $journal, bool $ignoreUpdate, string $category): bool - { - if (true === $ignoreUpdate) { - return false; - } - Log::debug(sprintf('Set budget to %s', $category)); - $this->repository->updateCategory($journal, $category); - - return true; - } } diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index fa0ddc9ab4..334daf9e8d 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -42,8 +42,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * Class ConvertController. @@ -144,133 +144,6 @@ class ConvertController extends Controller ); } - /** - * @return array - */ - private function getValidDepositSources(): array - { - // make repositories - $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; - $accountList = $this->accountRepository - ->getActiveAccountsByType([AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); - $grouped = []; - // group accounts: - /** @var Account $account */ - foreach ($accountList as $account) { - $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); - $name = $account->name; - if ('' === $role) { - $role = 'no_account_type'; - } - - // maybe it's a liability thing: - if (in_array($account->accountType->type, $liabilityTypes, true)) { - $role = 'l_'.$account->accountType->type; - } - if (AccountType::CASH === $account->accountType->type) { - $role = 'cash_account'; - $name = sprintf('(%s)', trans('firefly.cash')); - } - if (AccountType::REVENUE === $account->accountType->type) { - $role = 'revenue_account'; - } - - $key = (string)trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $name; - } - - return $grouped; - } - - /** - * @return array - */ - private function getValidWithdrawalDests(): array - { - // make repositories - $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; - $accountList = $this->accountRepository->getActiveAccountsByType( - [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE] - ); - $grouped = []; - // group accounts: - /** @var Account $account */ - foreach ($accountList as $account) { - $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); - $name = $account->name; - if ('' === $role) { - $role = 'no_account_type'; - } - - // maybe it's a liability thing: - if (in_array($account->accountType->type, $liabilityTypes, true)) { - $role = 'l_'.$account->accountType->type; - } - if (AccountType::CASH === $account->accountType->type) { - $role = 'cash_account'; - $name = sprintf('(%s)', trans('firefly.cash')); - } - if (AccountType::EXPENSE === $account->accountType->type) { - $role = 'expense_account'; - } - - $key = (string)trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $name; - } - - return $grouped; - } - - /** - * @return array - * @throws Exception - */ - private function getLiabilities(): array - { - // make repositories - $accountList = $this->accountRepository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); - $defaultCurrency = app('amount')->getDefaultCurrency(); - $grouped = []; - // group accounts: - /** @var Account $account */ - foreach ($accountList as $account) { - $balance = app('steam')->balance($account, today()); - $currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency; - $role = 'l_'.$account->accountType->type; - $key = (string)trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; - } - - return $grouped; - } - - /** - * @return array - * @throws Exception - */ - private function getAssetAccounts(): array - { - // make repositories - $accountList = $this->accountRepository->getActiveAccountsByType([AccountType::ASSET]); - $defaultCurrency = app('amount')->getDefaultCurrency(); - $grouped = []; - // group accounts: - /** @var Account $account */ - foreach ($accountList as $account) { - $balance = app('steam')->balance($account, today()); - $currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency; - $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); - if ('' === $role) { - $role = 'no_account_type'; - } - - $key = (string)trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; - } - - return $grouped; - } - /** * Do the conversion. * @@ -361,4 +234,131 @@ class ConvertController extends Controller return $journal; } + + /** + * @return array + * @throws Exception + */ + private function getAssetAccounts(): array + { + // make repositories + $accountList = $this->accountRepository->getActiveAccountsByType([AccountType::ASSET]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, today()); + $currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); + if ('' === $role) { + $role = 'no_account_type'; + } + + $key = (string)trans('firefly.opt_group_'.$role); + $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; + } + + return $grouped; + } + + /** + * @return array + * @throws Exception + */ + private function getLiabilities(): array + { + // make repositories + $accountList = $this->accountRepository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, today()); + $currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency; + $role = 'l_'.$account->accountType->type; + $key = (string)trans('firefly.opt_group_'.$role); + $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; + } + + return $grouped; + } + + /** + * @return array + */ + private function getValidDepositSources(): array + { + // make repositories + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $accountList = $this->accountRepository + ->getActiveAccountsByType([AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); + $name = $account->name; + if ('' === $role) { + $role = 'no_account_type'; + } + + // maybe it's a liability thing: + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_'.$account->accountType->type; + } + if (AccountType::CASH === $account->accountType->type) { + $role = 'cash_account'; + $name = sprintf('(%s)', trans('firefly.cash')); + } + if (AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; + } + + $key = (string)trans('firefly.opt_group_'.$role); + $grouped[$key][$account->id] = $name; + } + + return $grouped; + } + + /** + * @return array + */ + private function getValidWithdrawalDests(): array + { + // make repositories + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $accountList = $this->accountRepository->getActiveAccountsByType( + [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE] + ); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); + $name = $account->name; + if ('' === $role) { + $role = 'no_account_type'; + } + + // maybe it's a liability thing: + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_'.$account->accountType->type; + } + if (AccountType::CASH === $account->accountType->type) { + $role = 'cash_account'; + $name = sprintf('(%s)', trans('firefly.cash')); + } + if (AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; + } + + $key = (string)trans('firefly.opt_group_'.$role); + $grouped[$key][$account->id] = $name; + } + + return $grouped; + } } diff --git a/app/Http/Controllers/Transaction/LinkController.php b/app/Http/Controllers/Transaction/LinkController.php index 168c316af1..89017870c0 100644 --- a/app/Http/Controllers/Transaction/LinkController.php +++ b/app/Http/Controllers/Transaction/LinkController.php @@ -33,8 +33,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\View\View; use Illuminate\Support\Facades\Log; +use Illuminate\View\View; /** * Class LinkController. diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 42df90c845..9b7d0c9dd1 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -39,9 +39,9 @@ use FireflyIII\Services\Internal\Update\JournalUpdateService; use Illuminate\Contracts\Foundation\Application; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; +use Illuminate\Support\Facades\Log; use Illuminate\View\View as IlluminateView; use InvalidArgumentException; -use Illuminate\Support\Facades\Log; /** * Class MassController. @@ -196,6 +196,73 @@ class MassController extends Controller return redirect($this->getPreviousUrl('transactions.mass-edit.url')); } + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $key + * + * @return Carbon|null + */ + private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $key): ?Carbon + { + $value = $request->get($key); + if (!is_array($value)) { + return null; + } + if (!array_key_exists($journalId, $value)) { + return null; + } + try { + $carbon = Carbon::parse($value[$journalId]); + } catch (InvalidArgumentException $e) { + $e->getMessage(); + + return null; + } + + return $carbon; + } + + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $string + * + * @return int|null + */ + private function getIntFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?int + { + $value = $request->get($string); + if (!is_array($value)) { + return null; + } + if (!array_key_exists($journalId, $value)) { + return null; + } + + return (int)$value[$journalId]; + } + + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $string + * + * @return string|null + */ + private function getStringFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?string + { + $value = $request->get($string); + if (!is_array($value)) { + return null; + } + if (!array_key_exists($journalId, $value)) { + return null; + } + + return (string)$value[$journalId]; + } + /** * @param int $journalId * @param MassEditJournalRequest $request @@ -232,71 +299,4 @@ class MassController extends Controller // trigger rules event(new UpdatedTransactionGroup($journal->transactionGroup, true, true)); } - - /** - * @param MassEditJournalRequest $request - * @param int $journalId - * @param string $key - * - * @return Carbon|null - */ - private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $key): ?Carbon - { - $value = $request->get($key); - if (!is_array($value)) { - return null; - } - if (!array_key_exists($journalId, $value)) { - return null; - } - try { - $carbon = Carbon::parse($value[$journalId]); - } catch (InvalidArgumentException $e) { - $e->getMessage(); - - return null; - } - - return $carbon; - } - - /** - * @param MassEditJournalRequest $request - * @param int $journalId - * @param string $string - * - * @return string|null - */ - private function getStringFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?string - { - $value = $request->get($string); - if (!is_array($value)) { - return null; - } - if (!array_key_exists($journalId, $value)) { - return null; - } - - return (string)$value[$journalId]; - } - - /** - * @param MassEditJournalRequest $request - * @param int $journalId - * @param string $string - * - * @return int|null - */ - private function getIntFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?int - { - $value = $request->get($string); - if (!is_array($value)) { - return null; - } - if (!array_key_exists($journalId, $value)) { - return null; - } - - return (int)$value[$journalId]; - } } diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index c294ff0c7e..60d5ec957c 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -139,6 +139,36 @@ class ShowController extends Controller ); } + /** + * @param array $group + * + * @return array + */ + private function getAccounts(array $group): array + { + $accounts = []; + + foreach ($group['transactions'] as $transaction) { + $accounts['source'][] = [ + 'type' => $transaction['source_type'], + 'id' => $transaction['source_id'], + 'name' => $transaction['source_name'], + 'iban' => $transaction['source_iban'], + ]; + $accounts['destination'][] = [ + 'type' => $transaction['destination_type'], + 'id' => $transaction['destination_id'], + 'name' => $transaction['destination_name'], + 'iban' => $transaction['destination_iban'], + ]; + } + + $accounts['source'] = array_unique($accounts['source'], SORT_REGULAR); + $accounts['destination'] = array_unique($accounts['destination'], SORT_REGULAR); + + return $accounts; + } + /** * @param array $group * @@ -173,34 +203,4 @@ class ShowController extends Controller return $amounts; } - - /** - * @param array $group - * - * @return array - */ - private function getAccounts(array $group): array - { - $accounts = []; - - foreach ($group['transactions'] as $transaction) { - $accounts['source'][] = [ - 'type' => $transaction['source_type'], - 'id' => $transaction['source_id'], - 'name' => $transaction['source_name'], - 'iban' => $transaction['source_iban'], - ]; - $accounts['destination'][] = [ - 'type' => $transaction['destination_type'], - 'id' => $transaction['destination_id'], - 'name' => $transaction['destination_name'], - 'iban' => $transaction['destination_iban'], - ]; - } - - $accounts['source'] = array_unique($accounts['source'], SORT_REGULAR); - $accounts['destination'] = array_unique($accounts['destination'], SORT_REGULAR); - - return $accounts; - } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 8d07abcfe4..83e302cd3f 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -74,7 +74,22 @@ class Kernel extends HttpKernel TrustProxies::class, InstallationId::class, ]; - + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $middlewareAliases + = [ + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'bindings' => Binder::class, + 'can' => Authorize::class, + 'guest' => RedirectIfAuthenticated::class, + 'throttle' => ThrottleRequests::class, + ]; /** * The application's route middleware groups. * @@ -206,20 +221,4 @@ class Kernel extends HttpKernel Binder::class, Authorize::class, ]; - /** - * The application's route middleware. - * - * These middleware may be assigned to groups or used individually. - * - * @var array - */ - protected $middlewareAliases - = [ - 'auth' => Authenticate::class, - 'auth.basic' => AuthenticateWithBasicAuth::class, - 'bindings' => Binder::class, - 'can' => Authorize::class, - 'guest' => RedirectIfAuthenticated::class, - 'throttle' => ThrottleRequests::class, - ]; } diff --git a/app/Http/Middleware/Installer.php b/app/Http/Middleware/Installer.php index d012111b14..6f79d16137 100644 --- a/app/Http/Middleware/Installer.php +++ b/app/Http/Middleware/Installer.php @@ -78,6 +78,30 @@ class Installer return $next($request); } + /** + * Is access denied error. + * + * @param string $message + * + * @return bool + */ + protected function isAccessDenied(string $message): bool + { + return false !== stripos($message, 'Access denied'); + } + + /** + * Is no tables exist error. + * + * @param string $message + * + * @return bool + */ + protected function noTablesExist(string $message): bool + { + return false !== stripos($message, 'Base table or view not found'); + } + /** * Check if the tables are created and accounted for. * @@ -114,30 +138,6 @@ class Installer return false; } - /** - * Is access denied error. - * - * @param string $message - * - * @return bool - */ - protected function isAccessDenied(string $message): bool - { - return false !== stripos($message, 'Access denied'); - } - - /** - * Is no tables exist error. - * - * @param string $message - * - * @return bool - */ - protected function noTablesExist(string $message): bool - { - return false !== stripos($message, 'Base table or view not found'); - } - /** * Check if the "db_version" variable is correct. * diff --git a/app/Http/Middleware/InterestingMessage.php b/app/Http/Middleware/InterestingMessage.php index f3e264bda7..3a2b277ee1 100644 --- a/app/Http/Middleware/InterestingMessage.php +++ b/app/Http/Middleware/InterestingMessage.php @@ -73,12 +73,31 @@ class InterestingMessage } /** + * @param Request $request + * * @return bool */ - private function testing(): bool + private function accountMessage(Request $request): bool { - // ignore middleware in test environment. - return 'testing' === config('app.env') || !auth()->check(); + // get parameters from request. + $accountId = $request->get('account_id'); + $message = $request->get('message'); + + return null !== $accountId && null !== $message; + } + + /** + * @param Request $request + * + * @return bool + */ + private function billMessage(Request $request): bool + { + // get parameters from request. + $billId = $request->get('bill_id'); + $message = $request->get('message'); + + return null !== $billId && null !== $message; } /** @@ -95,6 +114,55 @@ class InterestingMessage return null !== $transactionGroupId && null !== $message; } + /** + * @param Request $request + */ + private function handleAccountMessage(Request $request): void + { + // get parameters from request. + $accountId = $request->get('account_id'); + $message = $request->get('message'); + + /** @var Account $account */ + $account = auth()->user()->accounts()->withTrashed()->find($accountId); + + if (null === $account) { + return; + } + if ('deleted' === $message) { + session()->flash('success', (string)trans('firefly.account_deleted', ['name' => $account->name])); + } + if ('created' === $message) { + session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); + } + if ('updated' === $message) { + session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name])); + } + } + + /** + * @param Request $request + */ + private function handleBillMessage(Request $request): void + { + // get parameters from request. + $billId = $request->get('bill_id'); + $message = $request->get('message'); + + /** @var Bill $bill */ + $bill = auth()->user()->bills()->withTrashed()->find($billId); + + if (null === $bill) { + return; + } + if ('deleted' === $message) { + session()->flash('success', (string)trans('firefly.deleted_bill', ['name' => $bill->name])); + } + if ('created' === $message) { + session()->flash('success', (string)trans('firefly.stored_new_bill', ['name' => $bill->name])); + } + } + /** * @param Request $request */ @@ -136,97 +204,6 @@ class InterestingMessage } } - /** - * @param Request $request - * - * @return bool - */ - private function accountMessage(Request $request): bool - { - // get parameters from request. - $accountId = $request->get('account_id'); - $message = $request->get('message'); - - return null !== $accountId && null !== $message; - } - - /** - * @param Request $request - */ - private function handleAccountMessage(Request $request): void - { - // get parameters from request. - $accountId = $request->get('account_id'); - $message = $request->get('message'); - - /** @var Account $account */ - $account = auth()->user()->accounts()->withTrashed()->find($accountId); - - if (null === $account) { - return; - } - if ('deleted' === $message) { - session()->flash('success', (string)trans('firefly.account_deleted', ['name' => $account->name])); - } - if ('created' === $message) { - session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); - } - if ('updated' === $message) { - session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name])); - } - } - - /** - * @param Request $request - * - * @return bool - */ - private function billMessage(Request $request): bool - { - // get parameters from request. - $billId = $request->get('bill_id'); - $message = $request->get('message'); - - return null !== $billId && null !== $message; - } - - /** - * @param Request $request - */ - private function handleBillMessage(Request $request): void - { - // get parameters from request. - $billId = $request->get('bill_id'); - $message = $request->get('message'); - - /** @var Bill $bill */ - $bill = auth()->user()->bills()->withTrashed()->find($billId); - - if (null === $bill) { - return; - } - if ('deleted' === $message) { - session()->flash('success', (string)trans('firefly.deleted_bill', ['name' => $bill->name])); - } - if ('created' === $message) { - session()->flash('success', (string)trans('firefly.stored_new_bill', ['name' => $bill->name])); - } - } - - /** - * @param Request $request - * - * @return bool - */ - private function webhookMessage(Request $request): bool - { - // get parameters from request. - $billId = $request->get('webhook_id'); - $message = $request->get('message'); - - return null !== $billId && null !== $message; - } - /** * @param Request $request */ @@ -252,4 +229,27 @@ class InterestingMessage session()->flash('success', (string)trans('firefly.stored_new_webhook', ['title' => $webhook->title])); } } + + /** + * @return bool + */ + private function testing(): bool + { + // ignore middleware in test environment. + return 'testing' === config('app.env') || !auth()->check(); + } + + /** + * @param Request $request + * + * @return bool + */ + private function webhookMessage(Request $request): bool + { + // get parameters from request. + $billId = $request->get('webhook_id'); + $message = $request->get('message'); + + return null !== $billId && null !== $message; + } } diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index 8487d084a3..bc96161593 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -63,30 +63,20 @@ class Range } /** - * Set the range for the current view. + * Configure the list length. */ - private function setRange(): void + private function configureList(): void { - // ignore preference. set the range to be the current month: - if (!app('session')->has('start') && !app('session')->has('end')) { - $viewRange = app('preferences')->get('viewRange', '1M')->data; - $today = today(config('app.timezone')); - $start = app('navigation')->updateStartDate($viewRange, $today); - $end = app('navigation')->updateEndDate($viewRange, $start); + $pref = app('preferences')->get('list-length', config('firefly.list_length', 10))->data; + app('view')->share('listLength', $pref); - app('session')->put('start', $start); - app('session')->put('end', $end); - } - if (!app('session')->has('first')) { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $journal = $repository->firstNull(); - $first = today(config('app.timezone'))->startOfYear(); - - if (null !== $journal) { - $first = $journal->date ?? $first; - } - app('session')->put('first', $first); + // share security message: + if ( + app('fireflyconfig')->has('upgrade_security_message') + && app('fireflyconfig')->has('upgrade_security_level') + ) { + app('view')->share('upgrade_security_message', app('fireflyconfig')->get('upgrade_security_message')->data); + app('view')->share('upgrade_security_level', app('fireflyconfig')->get('upgrade_security_level')->data); } } @@ -127,20 +117,30 @@ class Range } /** - * Configure the list length. + * Set the range for the current view. */ - private function configureList(): void + private function setRange(): void { - $pref = app('preferences')->get('list-length', config('firefly.list_length', 10))->data; - app('view')->share('listLength', $pref); + // ignore preference. set the range to be the current month: + if (!app('session')->has('start') && !app('session')->has('end')) { + $viewRange = app('preferences')->get('viewRange', '1M')->data; + $today = today(config('app.timezone')); + $start = app('navigation')->updateStartDate($viewRange, $today); + $end = app('navigation')->updateEndDate($viewRange, $start); - // share security message: - if ( - app('fireflyconfig')->has('upgrade_security_message') - && app('fireflyconfig')->has('upgrade_security_level') - ) { - app('view')->share('upgrade_security_message', app('fireflyconfig')->get('upgrade_security_message')->data); - app('view')->share('upgrade_security_level', app('fireflyconfig')->get('upgrade_security_level')->data); + app('session')->put('start', $start); + app('session')->put('end', $end); + } + if (!app('session')->has('first')) { + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $journal = $repository->firstNull(); + $first = today(config('app.timezone'))->startOfYear(); + + if (null !== $journal) { + $first = $journal->date ?? $first; + } + app('session')->put('first', $first); } } } diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index 274aec607d..9b99ef9d34 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -33,8 +33,8 @@ use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Validation\AccountValidator; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Class RecurrenceFormRequest @@ -149,40 +149,6 @@ class RecurrenceFormRequest extends FormRequest return $return; } - /** - * Parses repetition data. - * - * @return array - */ - private function parseRepetitionData(): array - { - $value = $this->convertString('repetition_type'); - $return = [ - 'type' => '', - 'moment' => '', - ]; - - if ('daily' === $value) { - $return['type'] = $value; - } - //monthly,17 - //ndom,3,7 - if (in_array(substr($value, 0, 6), ['yearly', 'weekly'], true)) { - $return['type'] = substr($value, 0, 6); - $return['moment'] = substr($value, 7); - } - if (str_starts_with($value, 'monthly')) { - $return['type'] = substr($value, 0, 7); - $return['moment'] = substr($value, 8); - } - if (str_starts_with($value, 'ndom')) { - $return['type'] = substr($value, 0, 4); - $return['moment'] = substr($value, 5); - } - - return $return; - } - /** * The rules for this request. * @@ -271,23 +237,6 @@ class RecurrenceFormRequest extends FormRequest return $rules; } - /** - * Configure the validator instance with special rules for after the basic validation rules. - * - * @param Validator $validator - * - * @return void - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator) { - // validate all account info - $this->validateAccountInformation($validator); - } - ); - } - /** * Validates the given account information. Switches on given transaction type. * @@ -353,4 +302,55 @@ class RecurrenceFormRequest extends FormRequest $validator->errors()->add('withdrawal_destination_id', $message); } } + + /** + * Configure the validator instance with special rules for after the basic validation rules. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + // validate all account info + $this->validateAccountInformation($validator); + } + ); + } + + /** + * Parses repetition data. + * + * @return array + */ + private function parseRepetitionData(): array + { + $value = $this->convertString('repetition_type'); + $return = [ + 'type' => '', + 'moment' => '', + ]; + + if ('daily' === $value) { + $return['type'] = $value; + } + //monthly,17 + //ndom,3,7 + if (in_array(substr($value, 0, 6), ['yearly', 'weekly'], true)) { + $return['type'] = substr($value, 0, 6); + $return['moment'] = substr($value, 7); + } + if (str_starts_with($value, 'monthly')) { + $return['type'] = substr($value, 0, 7); + $return['moment'] = substr($value, 8); + } + if (str_starts_with($value, 'ndom')) { + $return['type'] = substr($value, 0, 4); + $return['moment'] = substr($value, 5); + } + + return $return; + } } diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index facf6ceb63..2fbec2fc9f 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -38,52 +38,6 @@ class RuleFormRequest extends FormRequest use GetRuleConfiguration; use ChecksLogin; - /** - * Get all data for controller. - * - * @return array - * - */ - public function getRuleData(): array - { - return [ - 'title' => $this->convertString('title'), - 'rule_group_id' => $this->convertInteger('rule_group_id'), - 'active' => $this->boolean('active'), - 'trigger' => $this->convertString('trigger'), - 'description' => $this->stringWithNewlines('description'), - 'stop_processing' => $this->boolean('stop_processing'), - 'strict' => $this->boolean('strict'), - 'triggers' => $this->getRuleTriggerData(), - 'actions' => $this->getRuleActionData(), - ]; - } - - /** - * @return array - */ - private function getRuleTriggerData(): array - { - $return = []; - $triggerData = $this->get('triggers'); - if (is_array($triggerData)) { - foreach ($triggerData as $trigger) { - $stopProcessing = $trigger['stop_processing'] ?? '0'; - $prohibited = $trigger['prohibited'] ?? '0'; - $set = [ - 'type' => $trigger['type'] ?? 'invalid', - 'value' => $trigger['value'] ?? '', - 'stop_processing' => 1 === (int)$stopProcessing, - 'prohibited' => 1 === (int)$prohibited, - ]; - $set = self::replaceAmountTrigger($set); - $return[] = $set; - } - } - - return $return; - } - /** * @param array $array * @return array @@ -113,24 +67,24 @@ class RuleFormRequest extends FormRequest } /** + * Get all data for controller. + * * @return array + * */ - private function getRuleActionData(): array + public function getRuleData(): array { - $return = []; - $actionData = $this->get('actions'); - if (is_array($actionData)) { - foreach ($actionData as $action) { - $stopProcessing = $action['stop_processing'] ?? '0'; - $return[] = [ - 'type' => $action['type'] ?? 'invalid', - 'value' => $action['value'] ?? '', - 'stop_processing' => 1 === (int)$stopProcessing, - ]; - } - } - - return $return; + return [ + 'title' => $this->convertString('title'), + 'rule_group_id' => $this->convertInteger('rule_group_id'), + 'active' => $this->boolean('active'), + 'trigger' => $this->convertString('trigger'), + 'description' => $this->stringWithNewlines('description'), + 'stop_processing' => $this->boolean('stop_processing'), + 'strict' => $this->boolean('strict'), + 'triggers' => $this->getRuleTriggerData(), + 'actions' => $this->getRuleActionData(), + ]; } /** @@ -172,4 +126,50 @@ class RuleFormRequest extends FormRequest return $rules; } + + /** + * @return array + */ + private function getRuleActionData(): array + { + $return = []; + $actionData = $this->get('actions'); + if (is_array($actionData)) { + foreach ($actionData as $action) { + $stopProcessing = $action['stop_processing'] ?? '0'; + $return[] = [ + 'type' => $action['type'] ?? 'invalid', + 'value' => $action['value'] ?? '', + 'stop_processing' => 1 === (int)$stopProcessing, + ]; + } + } + + return $return; + } + + /** + * @return array + */ + private function getRuleTriggerData(): array + { + $return = []; + $triggerData = $this->get('triggers'); + if (is_array($triggerData)) { + foreach ($triggerData as $trigger) { + $stopProcessing = $trigger['stop_processing'] ?? '0'; + $prohibited = $trigger['prohibited'] ?? '0'; + $set = [ + 'type' => $trigger['type'] ?? 'invalid', + 'value' => $trigger['value'] ?? '', + 'stop_processing' => 1 === (int)$stopProcessing, + 'prohibited' => 1 === (int)$prohibited, + ]; + $set = self::replaceAmountTrigger($set); + $return[] = $set; + } + } + + return $return; + } } diff --git a/app/Jobs/CreateAutoBudgetLimits.php b/app/Jobs/CreateAutoBudgetLimits.php index aed894218b..23b53454c1 100644 --- a/app/Jobs/CreateAutoBudgetLimits.php +++ b/app/Jobs/CreateAutoBudgetLimits.php @@ -81,6 +81,16 @@ class CreateAutoBudgetLimits implements ShouldQueue } } + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } + /** * @param AutoBudget $autoBudget * @return void @@ -151,6 +161,120 @@ class CreateAutoBudgetLimits implements ShouldQueue Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); } + /** + * @param AutoBudget $autoBudget + * @param Carbon $start + * @param Carbon $end + * @param string|null $amount + */ + private function createBudgetLimit(AutoBudget $autoBudget, Carbon $start, Carbon $end, ?string $amount = null) + { + Log::debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); + if (null !== $amount) { + Log::debug(sprintf('Amount is overruled and will be set to %s', $amount)); + } + $budgetLimit = new BudgetLimit(); + $budgetLimit->budget()->associate($autoBudget->budget); + $budgetLimit->transactionCurrency()->associate($autoBudget->transactionCurrency); + $budgetLimit->start_date = $start; + $budgetLimit->end_date = $end; + $budgetLimit->amount = $amount ?? $autoBudget->amount; + $budgetLimit->period = $autoBudget->period; + $budgetLimit->generated = true; + $budgetLimit->save(); + + Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); + } + + /** + * @param AutoBudget $autoBudget + * + * @throws FireflyException + */ + private function createRollover(AutoBudget $autoBudget): void + { + Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); + // current period: + $start = app('navigation')->startOfPeriod($this->date, $autoBudget->period); + $end = app('navigation')->endOfPeriod($start, $autoBudget->period); + + // which means previous period: + $previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period); + $previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); + + Log::debug( + sprintf( + 'Current period is %s-%s, so previous period is %s-%s', + $start->format('Y-m-d'), + $end->format('Y-m-d'), + $previousStart->format('Y-m-d'), + $previousEnd->format('Y-m-d') + ) + ); + + // has budget limit in previous period? + $budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); + + if (null === $budgetLimit) { + Log::debug('No budget limit exists in previous period, so create one.'); + // if not, create it and we're done. + $this->createBudgetLimit($autoBudget, $start, $end); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + + return; + } + Log::debug('Budget limit exists for previous period.'); + // if has one, calculate expenses and use that as a base. + $repository = app(OperationsRepositoryInterface::class); + $repository->setUser($autoBudget->budget->user); + $spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); + $currencyId = (int)$autoBudget->transaction_currency_id; + $spentAmount = $spent[$currencyId]['sum'] ?? '0'; + Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); + + // if you spent more in previous budget period, than whatever you had previous budget period, the amount resets + // previous budget limit + spent + $budgetLeft = bcadd($budgetLimit->amount, $spentAmount); + $totalAmount = $autoBudget->amount; + Log::debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); + + if (-1 !== bccomp('0', $budgetLeft)) { + Log::info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); + } + if (1 !== bccomp('0', $budgetLeft)) { + $totalAmount = bcadd($budgetLeft, $totalAmount); + Log::info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); + } + + // create budget limit: + $this->createBudgetLimit($autoBudget, $start, $end, $totalAmount); + Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); + } + + /** + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return BudgetLimit|null + */ + private function findBudgetLimit(Budget $budget, Carbon $start, Carbon $end): ?BudgetLimit + { + Log::debug( + sprintf( + 'Going to find a budget limit for budget #%d ("%s") between %s and %s', + $budget->id, + $budget->name, + $start->format('Y-m-d'), + $end->format('Y-m-d') + ) + ); + + return $budget->budgetlimits() + ->where('start_date', $start->format('Y-m-d')) + ->where('end_date', $end->format('Y-m-d'))->first(); + } + /** * @param AutoBudget $autoBudget * @@ -267,128 +391,4 @@ class CreateAutoBudgetLimits implements ShouldQueue } throw new FireflyException(sprintf('isMagicDay() can\'t handle period "%s"', $autoBudget->period)); } - - /** - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * - * @return BudgetLimit|null - */ - private function findBudgetLimit(Budget $budget, Carbon $start, Carbon $end): ?BudgetLimit - { - Log::debug( - sprintf( - 'Going to find a budget limit for budget #%d ("%s") between %s and %s', - $budget->id, - $budget->name, - $start->format('Y-m-d'), - $end->format('Y-m-d') - ) - ); - - return $budget->budgetlimits() - ->where('start_date', $start->format('Y-m-d')) - ->where('end_date', $end->format('Y-m-d'))->first(); - } - - /** - * @param AutoBudget $autoBudget - * @param Carbon $start - * @param Carbon $end - * @param string|null $amount - */ - private function createBudgetLimit(AutoBudget $autoBudget, Carbon $start, Carbon $end, ?string $amount = null) - { - Log::debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); - if (null !== $amount) { - Log::debug(sprintf('Amount is overruled and will be set to %s', $amount)); - } - $budgetLimit = new BudgetLimit(); - $budgetLimit->budget()->associate($autoBudget->budget); - $budgetLimit->transactionCurrency()->associate($autoBudget->transactionCurrency); - $budgetLimit->start_date = $start; - $budgetLimit->end_date = $end; - $budgetLimit->amount = $amount ?? $autoBudget->amount; - $budgetLimit->period = $autoBudget->period; - $budgetLimit->generated = true; - $budgetLimit->save(); - - Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); - } - - /** - * @param AutoBudget $autoBudget - * - * @throws FireflyException - */ - private function createRollover(AutoBudget $autoBudget): void - { - Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); - // current period: - $start = app('navigation')->startOfPeriod($this->date, $autoBudget->period); - $end = app('navigation')->endOfPeriod($start, $autoBudget->period); - - // which means previous period: - $previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period); - $previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); - - Log::debug( - sprintf( - 'Current period is %s-%s, so previous period is %s-%s', - $start->format('Y-m-d'), - $end->format('Y-m-d'), - $previousStart->format('Y-m-d'), - $previousEnd->format('Y-m-d') - ) - ); - - // has budget limit in previous period? - $budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); - - if (null === $budgetLimit) { - Log::debug('No budget limit exists in previous period, so create one.'); - // if not, create it and we're done. - $this->createBudgetLimit($autoBudget, $start, $end); - Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); - - return; - } - Log::debug('Budget limit exists for previous period.'); - // if has one, calculate expenses and use that as a base. - $repository = app(OperationsRepositoryInterface::class); - $repository->setUser($autoBudget->budget->user); - $spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); - $currencyId = (int)$autoBudget->transaction_currency_id; - $spentAmount = $spent[$currencyId]['sum'] ?? '0'; - Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); - - // if you spent more in previous budget period, than whatever you had previous budget period, the amount resets - // previous budget limit + spent - $budgetLeft = bcadd($budgetLimit->amount, $spentAmount); - $totalAmount = $autoBudget->amount; - Log::debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); - - if (-1 !== bccomp('0', $budgetLeft)) { - Log::info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); - } - if (1 !== bccomp('0', $budgetLeft)) { - $totalAmount = bcadd($budgetLeft, $totalAmount); - Log::info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); - } - - // create budget limit: - $this->createBudgetLimit($autoBudget, $start, $end, $totalAmount); - Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); - } - - /** - * @param Carbon $date - */ - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 187bb4f604..c9881843eb 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -160,82 +160,29 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param Collection $recurrences - * - * @return Collection + * @param Carbon $date */ - private function filterRecurrences(Collection $recurrences): Collection + public function setDate(Carbon $date): void { - return $recurrences->filter( - function (Recurrence $recurrence) { - return $this->validRecurrence($recurrence); - } - ); + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; } /** - * Is the info in the recurrence valid? - * - * @param Recurrence $recurrence - * - * @return bool - * + * @param bool $force */ - private function validRecurrence(Recurrence $recurrence): bool + public function setForce(bool $force): void { - Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id)); - // is not active. - if (!$this->active($recurrence)) { - Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id)); + $this->force = $force; + } - return false; - } - - // has repeated X times. - $journalCount = $this->repository->getJournalCount($recurrence); - if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) { - Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions)); - - return false; - } - - // is no longer running - if ($this->repeatUntilHasPassed($recurrence)) { - Log::info( - sprintf( - 'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.', - $recurrence->id, - $recurrence->repeat_until->format('Y-m-d'), - $this->date->format('Y-m-d') - ) - ); - - return false; - } - - // first_date is in the future - if ($this->hasNotStartedYet($recurrence)) { - Log::info( - sprintf( - 'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.', - $recurrence->id, - $recurrence->first_date->format('Y-m-d'), - $this->date->format('Y-m-d') - ) - ); - - return false; - } - - // already fired today (with success): - if (false === $this->force && $this->hasFiredToday($recurrence)) { - Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id)); - - return false; - } - Log::debug('Will be included.'); - - return true; + /** + * @param Collection $recurrences + */ + public function setRecurrences(Collection $recurrences): void + { + $this->recurrences = $recurrences; } /** @@ -251,31 +198,17 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * Return true if the $repeat_until date is in the past. + * @param Collection $recurrences * - * @param Recurrence $recurrence - * - * @return bool + * @return Collection */ - private function repeatUntilHasPassed(Recurrence $recurrence): bool + private function filterRecurrences(Collection $recurrences): Collection { - // date has passed - return null !== $recurrence->repeat_until && $recurrence->repeat_until->lt($this->date); - } - - /** - * Has the recurrence started yet? - * - * @param Recurrence $recurrence - * - * @return bool - */ - private function hasNotStartedYet(Recurrence $recurrence): bool - { - $startDate = $this->getStartDate($recurrence); - Log::debug(sprintf('Start date is %s', $startDate->format('Y-m-d'))); - - return $startDate->gt($this->date); + return $recurrences->filter( + function (Recurrence $recurrence) { + return $this->validRecurrence($recurrence); + } + ); } /** @@ -296,79 +229,61 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * Has the recurrence fired today. - * - * @param Recurrence $recurrence - * - * @return bool - */ - private function hasFiredToday(Recurrence $recurrence): bool - { - return null !== $recurrence->latest_date && $recurrence->latest_date->eq($this->date); - } - - /** - * Separate method that will loop all repetitions and do something with it. Will return - * all created transaction journals. - * - * @param Recurrence $recurrence - * - * @return Collection - * @throws DuplicateTransactionException - * @throws FireflyException - */ - private function handleRepetitions(Recurrence $recurrence): Collection - { - $collection = new Collection(); - /** @var RecurrenceRepetition $repetition */ - foreach ($recurrence->recurrenceRepetitions as $repetition) { - Log::debug( - sprintf( - 'Now repeating %s with value "%s", skips every %d time(s)', - $repetition->repetition_type, - $repetition->repetition_moment, - $repetition->repetition_skip - ) - ); - - // start looping from $startDate to today perhaps we have a hit? - // add two days to $this->date, so we always include the weekend. - $includeWeekend = clone $this->date; - $includeWeekend->addDays(2); - $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); - - unset($includeWeekend); - - $result = $this->handleOccurrences($recurrence, $repetition, $occurrences); - $collection = $collection->merge($result); - } - - return $collection; - } - - /** - * Check if the occurences should be executed. + * Get transaction information from a recurring transaction. * * @param Recurrence $recurrence * @param RecurrenceRepetition $repetition - * @param array $occurrences + * @param Carbon $date + * + * @return array * - * @return Collection - * @throws DuplicateTransactionException - * @throws FireflyException */ - private function handleOccurrences(Recurrence $recurrence, RecurrenceRepetition $repetition, array $occurrences): Collection + private function getTransactionData(Recurrence $recurrence, RecurrenceRepetition $repetition, Carbon $date): array { - $collection = new Collection(); - /** @var Carbon $date */ - foreach ($occurrences as $date) { - $result = $this->handleOccurrence($recurrence, $repetition, $date); - if (null !== $result) { - $collection->push($result); - } + // total transactions expected for this recurrence: + $total = $this->repository->totalTransactions($recurrence, $repetition); + $count = $this->repository->getJournalCount($recurrence) + 1; + $transactions = $recurrence->recurrenceTransactions()->get(); + $return = []; + /** @var RecurrenceTransaction $transaction */ + foreach ($transactions as $index => $transaction) { + $single = [ + 'type' => strtolower($recurrence->transactionType->type), + 'date' => $date, + 'user' => $recurrence->user_id, + 'currency_id' => (int)$transaction->transaction_currency_id, + 'currency_code' => null, + 'description' => $transactions->first()->description, + 'amount' => $transaction->amount, + 'budget_id' => $this->repository->getBudget($transaction), + 'budget_name' => null, + 'category_id' => $this->repository->getCategoryId($transaction), + 'category_name' => $this->repository->getCategoryName($transaction), + 'source_id' => $transaction->source_id, + 'source_name' => null, + 'destination_id' => $transaction->destination_id, + 'destination_name' => null, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'foreign_currency_code' => null, + 'foreign_amount' => $transaction->foreign_amount, + 'reconciled' => false, + 'identifier' => $index, + 'recurrence_id' => (int)$recurrence->id, + 'order' => $index, + 'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), + 'tags' => $this->repository->getTags($transaction), + 'piggy_bank_id' => $this->repository->getPiggyBank($transaction), + 'piggy_bank_name' => null, + 'bill_id' => $this->repository->getBillId($transaction), + 'bill_name' => null, + 'recurrence_total' => $total, + 'recurrence_count' => $count, + 'recurrence_date' => $date, + ]; + $return[] = $single; } - return $collection; + return $return; } /** @@ -445,86 +360,171 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * Get transaction information from a recurring transaction. + * Check if the occurences should be executed. * * @param Recurrence $recurrence * @param RecurrenceRepetition $repetition - * @param Carbon $date - * - * @return array + * @param array $occurrences * + * @return Collection + * @throws DuplicateTransactionException + * @throws FireflyException */ - private function getTransactionData(Recurrence $recurrence, RecurrenceRepetition $repetition, Carbon $date): array + private function handleOccurrences(Recurrence $recurrence, RecurrenceRepetition $repetition, array $occurrences): Collection { - // total transactions expected for this recurrence: - $total = $this->repository->totalTransactions($recurrence, $repetition); - $count = $this->repository->getJournalCount($recurrence) + 1; - $transactions = $recurrence->recurrenceTransactions()->get(); - $return = []; - /** @var RecurrenceTransaction $transaction */ - foreach ($transactions as $index => $transaction) { - $single = [ - 'type' => strtolower($recurrence->transactionType->type), - 'date' => $date, - 'user' => $recurrence->user_id, - 'currency_id' => (int)$transaction->transaction_currency_id, - 'currency_code' => null, - 'description' => $transactions->first()->description, - 'amount' => $transaction->amount, - 'budget_id' => $this->repository->getBudget($transaction), - 'budget_name' => null, - 'category_id' => $this->repository->getCategoryId($transaction), - 'category_name' => $this->repository->getCategoryName($transaction), - 'source_id' => $transaction->source_id, - 'source_name' => null, - 'destination_id' => $transaction->destination_id, - 'destination_name' => null, - 'foreign_currency_id' => $transaction->foreign_currency_id, - 'foreign_currency_code' => null, - 'foreign_amount' => $transaction->foreign_amount, - 'reconciled' => false, - 'identifier' => $index, - 'recurrence_id' => (int)$recurrence->id, - 'order' => $index, - 'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), - 'tags' => $this->repository->getTags($transaction), - 'piggy_bank_id' => $this->repository->getPiggyBank($transaction), - 'piggy_bank_name' => null, - 'bill_id' => $this->repository->getBillId($transaction), - 'bill_name' => null, - 'recurrence_total' => $total, - 'recurrence_count' => $count, - 'recurrence_date' => $date, - ]; - $return[] = $single; + $collection = new Collection(); + /** @var Carbon $date */ + foreach ($occurrences as $date) { + $result = $this->handleOccurrence($recurrence, $repetition, $date); + if (null !== $result) { + $collection->push($result); + } } - return $return; + return $collection; } /** - * @param Carbon $date + * Separate method that will loop all repetitions and do something with it. Will return + * all created transaction journals. + * + * @param Recurrence $recurrence + * + * @return Collection + * @throws DuplicateTransactionException + * @throws FireflyException */ - public function setDate(Carbon $date): void + private function handleRepetitions(Recurrence $recurrence): Collection { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; + $collection = new Collection(); + /** @var RecurrenceRepetition $repetition */ + foreach ($recurrence->recurrenceRepetitions as $repetition) { + Log::debug( + sprintf( + 'Now repeating %s with value "%s", skips every %d time(s)', + $repetition->repetition_type, + $repetition->repetition_moment, + $repetition->repetition_skip + ) + ); + + // start looping from $startDate to today perhaps we have a hit? + // add two days to $this->date, so we always include the weekend. + $includeWeekend = clone $this->date; + $includeWeekend->addDays(2); + $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); + + unset($includeWeekend); + + $result = $this->handleOccurrences($recurrence, $repetition, $occurrences); + $collection = $collection->merge($result); + } + + return $collection; } /** - * @param bool $force + * Has the recurrence fired today. + * + * @param Recurrence $recurrence + * + * @return bool */ - public function setForce(bool $force): void + private function hasFiredToday(Recurrence $recurrence): bool { - $this->force = $force; + return null !== $recurrence->latest_date && $recurrence->latest_date->eq($this->date); } /** - * @param Collection $recurrences + * Has the recurrence started yet? + * + * @param Recurrence $recurrence + * + * @return bool */ - public function setRecurrences(Collection $recurrences): void + private function hasNotStartedYet(Recurrence $recurrence): bool { - $this->recurrences = $recurrences; + $startDate = $this->getStartDate($recurrence); + Log::debug(sprintf('Start date is %s', $startDate->format('Y-m-d'))); + + return $startDate->gt($this->date); + } + + /** + * Return true if the $repeat_until date is in the past. + * + * @param Recurrence $recurrence + * + * @return bool + */ + private function repeatUntilHasPassed(Recurrence $recurrence): bool + { + // date has passed + return null !== $recurrence->repeat_until && $recurrence->repeat_until->lt($this->date); + } + + /** + * Is the info in the recurrence valid? + * + * @param Recurrence $recurrence + * + * @return bool + * + */ + private function validRecurrence(Recurrence $recurrence): bool + { + Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id)); + // is not active. + if (!$this->active($recurrence)) { + Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id)); + + return false; + } + + // has repeated X times. + $journalCount = $this->repository->getJournalCount($recurrence); + if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) { + Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions)); + + return false; + } + + // is no longer running + if ($this->repeatUntilHasPassed($recurrence)) { + Log::info( + sprintf( + 'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.', + $recurrence->id, + $recurrence->repeat_until->format('Y-m-d'), + $this->date->format('Y-m-d') + ) + ); + + return false; + } + + // first_date is in the future + if ($this->hasNotStartedYet($recurrence)) { + Log::info( + sprintf( + 'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.', + $recurrence->id, + $recurrence->first_date->format('Y-m-d'), + $this->date->format('Y-m-d') + ) + ); + + return false; + } + + // already fired today (with success): + if (false === $this->force && $this->hasFiredToday($recurrence)) { + Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id)); + + return false; + } + Log::debug('Will be included.'); + + return true; } } diff --git a/app/Jobs/DownloadExchangeRates.php b/app/Jobs/DownloadExchangeRates.php index b7d014069f..7327bfb3d9 100644 --- a/app/Jobs/DownloadExchangeRates.php +++ b/app/Jobs/DownloadExchangeRates.php @@ -92,6 +92,16 @@ class DownloadExchangeRates implements ShouldQueue } } + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } + /** * @param TransactionCurrency $currency * @return void @@ -100,12 +110,12 @@ class DownloadExchangeRates implements ShouldQueue private function downloadRates(TransactionCurrency $currency): void { Log::debug(sprintf('Now downloading new exchange rates for currency %s.', $currency->code)); - $base = sprintf('%s/%s/%s', (string)config('cer.url'), $this->date->year, $this->date->isoWeek); - $client = new Client(); - $url = sprintf('%s/%s.json', $base, $currency->code); + $base = sprintf('%s/%s/%s', (string)config('cer.url'), $this->date->year, $this->date->isoWeek); + $client = new Client(); + $url = sprintf('%s/%s.json', $base, $currency->code); try { $res = $client->get($url); - } catch(RequestException $e) { + } catch (RequestException $e) { app('log')->warning(sprintf('Trying to grab "%s" resulted in error "%d".', $url, $e->getMessage())); return; } @@ -124,19 +134,6 @@ class DownloadExchangeRates implements ShouldQueue $this->saveRates($currency, $date, $json['rates']); } - private function saveRates(TransactionCurrency $currency, Carbon $date, array $rates): void - { - foreach ($rates as $code => $rate) { - $to = $this->getCurrency($code); - if (null === $to) { - Log::debug(sprintf('Currency %s is not in use, do not save rate.', $code)); - continue; - } - Log::debug(sprintf('Currency %s is in use.', $code)); - $this->saveRate($currency, $to, $date, $rate); - } - } - /** * @param string $code * @return TransactionCurrency|null @@ -178,13 +175,16 @@ class DownloadExchangeRates implements ShouldQueue } } - /** - * @param Carbon $date - */ - public function setDate(Carbon $date): void + private function saveRates(TransactionCurrency $currency, Carbon $date, array $rates): void { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; + foreach ($rates as $code => $rate) { + $to = $this->getCurrency($code); + if (null === $to) { + Log::debug(sprintf('Currency %s is not in use, do not save rate.', $code)); + continue; + } + Log::debug(sprintf('Currency %s is in use.', $code)); + $this->saveRate($currency, $to, $date, $rate); + } } } diff --git a/app/Jobs/WarnAboutBills.php b/app/Jobs/WarnAboutBills.php index e339194bc6..42eaa04a55 100644 --- a/app/Jobs/WarnAboutBills.php +++ b/app/Jobs/WarnAboutBills.php @@ -95,6 +95,36 @@ class WarnAboutBills implements ShouldQueue app('preferences')->mark(); } + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } + + /** + * @param bool $force + */ + public function setForce(bool $force): void + { + $this->force = $force; + } + + /** + * @param Bill $bill + * @param string $field + * @return int + */ + private function getDiff(Bill $bill, string $field): int + { + $today = clone $this->date; + $carbon = clone $bill->$field; + return $today->diffInDays($carbon, false); + } + /** * @param Bill $bill * @return bool @@ -131,18 +161,6 @@ class WarnAboutBills implements ShouldQueue return false; } - /** - * @param Bill $bill - * @param string $field - * @return int - */ - private function getDiff(Bill $bill, string $field): int - { - $today = clone $this->date; - $carbon = clone $bill->$field; - return $today->diffInDays($carbon, false); - } - /** * @param Bill $bill * @param string $field @@ -154,22 +172,4 @@ class WarnAboutBills implements ShouldQueue Log::debug('Will now send warning!'); event(new WarnUserAboutBill($bill, $field, $diff)); } - - /** - * @param Carbon $date - */ - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } - - /** - * @param bool $force - */ - public function setForce(bool $force): void - { - $this->force = $force; - } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 608dcdfbc6..9ce824914e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -153,11 +153,11 @@ class Account extends Model } /** - * @return BelongsTo + * @return HasMany */ - public function user(): BelongsTo + public function accountMeta(): HasMany { - return $this->belongsTo(User::class); + return $this->hasMany(AccountMeta::class); } /** @@ -191,14 +191,6 @@ class Account extends Model return $metaValue ? $metaValue->data : ''; } - /** - * @return HasMany - */ - public function accountMeta(): HasMany - { - return $this->hasMany(AccountMeta::class); - } - /** * @return string */ @@ -282,6 +274,14 @@ class Account extends Model return $this->hasMany(Transaction::class); } + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + /** * Get the virtual balance * diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 67c5a9d55b..4f8e968885 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -124,14 +124,6 @@ class Attachment extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * Get all of the owning attachable models. * @@ -160,4 +152,12 @@ class Attachment extends Model { return $this->morphMany(Note::class, 'noteable'); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/AutoBudget.php b/app/Models/AutoBudget.php index 7d1f646b5f..5714c7c71f 100644 --- a/app/Models/AutoBudget.php +++ b/app/Models/AutoBudget.php @@ -67,11 +67,10 @@ class AutoBudget extends Model { use SoftDeletes; + public const AUTO_BUDGET_ADJUSTED = 3; public const AUTO_BUDGET_RESET = 1; public const AUTO_BUDGET_ROLLOVER = 2; - public const AUTO_BUDGET_ADJUSTED = 3; - - protected $fillable = ['budget_id','amount','period']; + protected $fillable = ['budget_id', 'amount', 'period']; /** * @return BelongsTo diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 6e1fd36526..bc654e74f7 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -113,17 +113,17 @@ class AvailableBudget extends Model /** * @return BelongsTo */ - public function user(): BelongsTo + public function transactionCurrency(): BelongsTo { - return $this->belongsTo(User::class); + return $this->belongsTo(TransactionCurrency::class); } /** * @return BelongsTo */ - public function transactionCurrency(): BelongsTo + public function user(): BelongsTo { - return $this->belongsTo(TransactionCurrency::class); + return $this->belongsTo(User::class); } protected function amount(): Attribute diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 4458013bc6..a253c1b19d 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -166,14 +166,6 @@ class Bill extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -233,6 +225,14 @@ class Bill extends Model return $this->hasMany(TransactionJournal::class); } + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + /** * Get the max amount * diff --git a/app/Models/Budget.php b/app/Models/Budget.php index ff2379c88e..dee29d680b 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -126,14 +126,6 @@ class Budget extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -181,4 +173,12 @@ class Budget extends Model { return $this->belongsToMany(Transaction::class, 'budget_transaction', 'budget_id'); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index f1e1c30775..f39ddc8b32 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -73,22 +73,20 @@ class BudgetLimit extends Model * @var array */ protected $casts - = [ + = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', 'start_date' => 'date', 'end_date' => 'date', 'auto_budget' => 'boolean', ]; - - /** @var array Fields that can be filled */ - protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id']; - protected $dispatchesEvents = [ 'created' => Created::class, 'updated' => Updated::class, 'deleted' => Deleted::class, ]; + /** @var array Fields that can be filled */ + protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id']; /** * Route binder. Converts the key in the URL to the specified object (or throw 404). diff --git a/app/Models/Category.php b/app/Models/Category.php index db4b47f7c3..0cfc680ea8 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -116,14 +116,6 @@ class Category extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -155,4 +147,12 @@ class Category extends Model { return $this->belongsToMany(Transaction::class, 'category_transaction', 'category_id'); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/InvitedUser.php b/app/Models/InvitedUser.php index ec15b70ae3..8bf840361d 100644 --- a/app/Models/InvitedUser.php +++ b/app/Models/InvitedUser.php @@ -66,14 +66,6 @@ class InvitedUser extends Model ]; protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed']; - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * Route binder. Converts the key in the URL to the specified object (or throw 404). * @@ -94,4 +86,12 @@ class InvitedUser extends Model } throw new NotFoundHttpException(); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/ObjectGroup.php b/app/Models/ObjectGroup.php index 279580b499..a1717fe218 100644 --- a/app/Models/ObjectGroup.php +++ b/app/Models/ObjectGroup.php @@ -101,14 +101,6 @@ class ObjectGroup extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphToMany */ @@ -132,4 +124,12 @@ class ObjectGroup extends Model { return $this->morphedByMany(PiggyBank::class, 'object_groupable'); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index 43740a8b54..db78c9983a 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -142,14 +142,6 @@ class Recurrence extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -205,4 +197,12 @@ class Recurrence extends Model { return $this->belongsTo(TransactionType::class); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Rule.php b/app/Models/Rule.php index e3c8d4534a..350b79d2f2 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -124,14 +124,6 @@ class Rule extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return HasMany */ @@ -165,4 +157,12 @@ class Rule extends Model { $this->attributes['description'] = e($value); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index df2f95dce2..b12d2dd0b4 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -115,14 +115,6 @@ class RuleGroup extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return HasMany */ @@ -130,4 +122,12 @@ class RuleGroup extends Model { return $this->hasMany(Rule::class); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 2f985f0928..1b07b0d678 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -126,14 +126,6 @@ class Tag extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -157,4 +149,12 @@ class Tag extends Model { return $this->belongsToMany(TransactionJournal::class); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index ede4e29e4c..0d147a0f74 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -123,6 +123,29 @@ class Transaction extends Model /** @var array Hidden from view */ protected $hidden = ['encrypted']; + /** + * Check if a table is joined. + * + * @param Builder $query + * @param string $table + * + * @return bool + */ + public static function isJoined(Builder $query, string $table): bool + { + $joins = $query->getQuery()->joins; + if (null === $joins) { + return false; + } + foreach ($joins as $join) { + if ($join->table === $table) { + return true; + } + } + + return false; + } + /** * Get the account this object belongs to. * @@ -178,29 +201,6 @@ class Transaction extends Model $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); } - /** - * Check if a table is joined. - * - * @param Builder $query - * @param string $table - * - * @return bool - */ - public static function isJoined(Builder $query, string $table): bool - { - $joins = $query->getQuery()->joins; - if (null === $joins) { - return false; - } - foreach ($joins as $join) { - if ($join->table === $table) { - return true; - } - } - - return false; - } - /** * Check for transactions BEFORE the specified date. * diff --git a/app/Models/TransactionGroup.php b/app/Models/TransactionGroup.php index 69506c05b3..bf6d727b54 100644 --- a/app/Models/TransactionGroup.php +++ b/app/Models/TransactionGroup.php @@ -115,14 +115,6 @@ class TransactionGroup extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return HasMany */ @@ -130,4 +122,12 @@ class TransactionGroup extends Model { return $this->hasMany(TransactionJournal::class); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 8e2e87570c..54a37027b0 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -160,6 +160,30 @@ class TransactionJournal extends Model /** @var array Hidden from view */ protected $hidden = ['encrypted']; + /** + * Checks if tables are joined. + * + * + * @param Builder $query + * @param string $table + * + * @return bool + */ + public static function isJoined(Builder $query, string $table): bool + { + $joins = $query->getQuery()->joins; + if (null === $joins) { + return false; + } + foreach ($joins as $join) { + if ($join->table === $table) { + return true; + } + } + + return false; + } + /** * Route binder. Converts the key in the URL to the specified object (or throw 404). * @@ -184,14 +208,6 @@ class TransactionJournal extends Model throw new NotFoundHttpException(); } - /** - * @return BelongsTo - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - /** * @return MorphMany */ @@ -307,30 +323,6 @@ class TransactionJournal extends Model } } - /** - * Checks if tables are joined. - * - * - * @param Builder $query - * @param string $table - * - * @return bool - */ - public static function isJoined(Builder $query, string $table): bool - { - $joins = $query->getQuery()->joins; - if (null === $joins) { - return false; - } - foreach ($joins as $join) { - if ($join->table === $table) { - return true; - } - } - - return false; - } - /** * @return HasMany */ @@ -386,4 +378,12 @@ class TransactionJournal extends Model { return $this->hasMany(Transaction::class); } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/UserGroup.php b/app/Models/UserGroup.php index 2efe78a40b..01093513cb 100644 --- a/app/Models/UserGroup.php +++ b/app/Models/UserGroup.php @@ -49,26 +49,17 @@ use Illuminate\Support\Carbon; * @method static Builder|UserGroup whereId($value) * @method static Builder|UserGroup whereTitle($value) * @method static Builder|UserGroup whereUpdatedAt($value) - * @property-read Collection $accounts + * @property-read Collection $accounts * @property-read int|null $accounts_count - * @property-read Collection $accounts - * @property-read Collection $accounts - * @property-read Collection $accounts + * @property-read Collection $accounts + * @property-read Collection $accounts + * @property-read Collection $accounts * @mixin Eloquent */ class UserGroup extends Model { protected $fillable = ['title']; - /** - * - * @return HasMany - */ - public function groupMemberships(): HasMany - { - return $this->hasMany(GroupMembership::class); - } - /** * Link to accounts. * @@ -78,4 +69,13 @@ class UserGroup extends Model { return $this->hasMany(Account::class); } + + /** + * + * @return HasMany + */ + public function groupMemberships(): HasMany + { + return $this->hasMany(GroupMembership::class); + } } diff --git a/app/Providers/AccountServiceProvider.php b/app/Providers/AccountServiceProvider.php index d7fb61298e..b88c2befa5 100644 --- a/app/Providers/AccountServiceProvider.php +++ b/app/Providers/AccountServiceProvider.php @@ -25,12 +25,12 @@ namespace FireflyIII\Providers; use FireflyIII\Repositories\Account\AccountRepository; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Administration\Account\AccountRepository as AdminAccountRepository; -use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTasker; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Account\OperationsRepository; use FireflyIII\Repositories\Account\OperationsRepositoryInterface; +use FireflyIII\Repositories\Administration\Account\AccountRepository as AdminAccountRepository; +use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; @@ -84,7 +84,7 @@ class AccountServiceProvider extends ServiceProvider // phpstan thinks auth does not exist. if ($app->auth->check()) { // @phpstan-ignore-line $repository->setUser(auth()->user()); - $repository->setAdministrationId((int) auth()->user()->user_group_id); + $repository->setAdministrationId((int)auth()->user()->user_group_id); } return $repository; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 608cf20806..0ef77cded6 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -45,16 +45,11 @@ use FireflyIII\Events\UpdatedAccount; use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Events\UserChangedEmail; use FireflyIII\Events\WarnUserAboutBill; -use FireflyIII\Models\Budget; -use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankRepetition; -use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use Illuminate\Auth\Events\Login; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Laravel\Passport\Events\AccessTokenCreated; -use Illuminate\Support\Facades\Log; /** * Class EventServiceProvider. @@ -165,13 +160,13 @@ class EventServiceProvider extends ServiceProvider 'FireflyIII\Handlers\Events\PiggyBankEventHandler@changePiggyAmount', ], // budget related events: CRUD budget limit - Created::class => [ + Created::class => [ 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created', ], - Updated::class => [ + Updated::class => [ 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated', ], - Deleted::class => [ + Deleted::class => [ 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted', ], diff --git a/app/Providers/FireflySessionProvider.php b/app/Providers/FireflySessionProvider.php index f07474003c..46db3ff7b0 100644 --- a/app/Providers/FireflySessionProvider.php +++ b/app/Providers/FireflySessionProvider.php @@ -44,19 +44,6 @@ class FireflySessionProvider extends ServiceProvider $this->app->singleton(StartFireflySession::class); } - /** - * Register the session manager instance. - */ - protected function registerSessionManager(): void - { - $this->app->singleton( - 'session', - function ($app) { - return new SessionManager($app); - } - ); - } - /** * Register the session driver instance. */ @@ -72,4 +59,17 @@ class FireflySessionProvider extends ServiceProvider } ); } + + /** + * Register the session manager instance. + */ + protected function registerSessionManager(): void + { + $this->app->singleton( + 'session', + function ($app) { + return new SessionManager($app); + } + ); + } } diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php index 8a2ae5ae89..69b27ccf49 100644 --- a/app/Providers/JournalServiceProvider.php +++ b/app/Providers/JournalServiceProvider.php @@ -58,6 +58,44 @@ class JournalServiceProvider extends ServiceProvider $this->registerGroupCollector(); } + /** + * + */ + private function registerGroupCollector(): void + { + $this->app->bind( + GroupCollectorInterface::class, + static function (Application $app) { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollector::class); + if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth) + $collector->setUser(auth()->user()); + } + + return $collector; + } + ); + } + + /** + * Register group repos. + */ + private function registerGroupRepository(): void + { + $this->app->bind( + TransactionGroupRepositoryInterface::class, + static function (Application $app) { + /** @var TransactionGroupRepositoryInterface $repository */ + $repository = app(TransactionGroupRepository::class); + if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth) + $repository->setUser(auth()->user()); + } + + return $repository; + } + ); + } + /** * Register repository. */ @@ -104,42 +142,4 @@ class JournalServiceProvider extends ServiceProvider } ); } - - /** - * Register group repos. - */ - private function registerGroupRepository(): void - { - $this->app->bind( - TransactionGroupRepositoryInterface::class, - static function (Application $app) { - /** @var TransactionGroupRepositoryInterface $repository */ - $repository = app(TransactionGroupRepository::class); - if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth) - $repository->setUser(auth()->user()); - } - - return $repository; - } - ); - } - - /** - * - */ - private function registerGroupCollector(): void - { - $this->app->bind( - GroupCollectorInterface::class, - static function (Application $app) { - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollector::class); - if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth) - $collector->setUser(auth()->user()); - } - - return $collector; - } - ); - } } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 49056a0c20..6853c9194c 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -42,8 +42,8 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Storage; /** @@ -54,6 +54,16 @@ class AccountRepository implements AccountRepositoryInterface { private User $user; + /** + * @param array $types + * + * @return int + */ + public function count(array $types): int + { + return $this->user->accounts()->accountTypeIn($types)->count(); + } + /** * Moved here from account CRUD. * @@ -107,6 +117,16 @@ class AccountRepository implements AccountRepositoryInterface return $result; } + /** + * @param int $accountId + * + * @return Account|null + */ + public function find(int $accountId): ?Account + { + return $this->user->accounts()->find($accountId); + } + /** * @inheritDoc */ @@ -180,6 +200,28 @@ class AccountRepository implements AccountRepositoryInterface return $account; } + /** + * @param Account $account + * + * @return TransactionCurrency|null + */ + public function getAccountCurrency(Account $account): ?TransactionCurrency + { + $type = $account->accountType->type; + $list = config('firefly.valid_currency_account_types'); + + // return null if not in this list. + if (!in_array($type, $list, true)) { + return null; + } + $currencyId = (int)$this->getMetaValue($account, 'currency_id'); + if ($currencyId > 0) { + return TransactionCurrency::find($currencyId); + } + + return null; + } + /** * Return account type or null if not found. * @@ -211,6 +253,38 @@ class AccountRepository implements AccountRepositoryInterface return $query->get(['accounts.*']); } + /** + * @param array $types + * @param array|null $sort + * + * @return Collection + */ + public function getAccountsByType(array $types, ?array $sort = []): Collection + { + $res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types); + $query = $this->user->accounts(); + if (0 !== count($types)) { + $query->accountTypeIn($types); + } + + // add sort parameters. At this point they're filtered to allowed fields to sort by: + if (0 !== count($sort)) { + foreach ($sort as $param) { + $query->orderBy($param[0], $param[1]); + } + } + + if (0 === count($sort)) { + if (0 !== count($res)) { + $query->orderBy('accounts.order', 'ASC'); + } + $query->orderBy('accounts.active', 'DESC'); + $query->orderBy('accounts.name', 'ASC'); + } + + return $query->get(['accounts.*']); + } + /** * @param array $types * @@ -275,16 +349,6 @@ class AccountRepository implements AccountRepositoryInterface return $factory->findOrCreate('Cash account', $type->type); } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * @inheritDoc */ @@ -335,6 +399,31 @@ class AccountRepository implements AccountRepositoryInterface return $account->locations()->first(); } + /** + * Return meta value for account. Null if not found. + * + * @param Account $account + * @param string $field + * + * @return null|string + */ + public function getMetaValue(Account $account, string $field): ?string + { + $result = $account->accountMeta->filter( + function (AccountMeta $meta) use ($field) { + return strtolower($meta->name) === strtolower($field); + } + ); + if (0 === $result->count()) { + return null; + } + if (1 === $result->count()) { + return (string)$result->first()->data; + } + + return null; + } + /** * Get note text or null. * @@ -353,6 +442,19 @@ class AccountRepository implements AccountRepositoryInterface return $note->text; } + /** + * @param Account $account + * + * @return TransactionJournal|null + */ + public function getOpeningBalance(Account $account): ?TransactionJournal + { + return TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transactions.account_id', $account->id) + ->transactionTypes([TransactionType::OPENING_BALANCE]) + ->first(['transaction_journals.*']); + } + /** * Returns the amount of the opening balance for this account. * @@ -409,19 +511,6 @@ class AccountRepository implements AccountRepositoryInterface return $journal?->transactionGroup; } - /** - * @param Account $account - * - * @return TransactionJournal|null - */ - public function getOpeningBalance(Account $account): ?TransactionJournal - { - return TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $account->id) - ->transactionTypes([TransactionType::OPENING_BALANCE]) - ->first(['transaction_journals.*']); - } - /** * @param Account $account * @@ -475,73 +564,6 @@ class AccountRepository implements AccountRepositoryInterface return $factory->create($data); } - /** - * @param Account $account - * - * @return TransactionCurrency|null - */ - public function getAccountCurrency(Account $account): ?TransactionCurrency - { - $type = $account->accountType->type; - $list = config('firefly.valid_currency_account_types'); - - // return null if not in this list. - if (!in_array($type, $list, true)) { - return null; - } - $currencyId = (int)$this->getMetaValue($account, 'currency_id'); - if ($currencyId > 0) { - return TransactionCurrency::find($currencyId); - } - - return null; - } - - /** - * Return meta value for account. Null if not found. - * - * @param Account $account - * @param string $field - * - * @return null|string - */ - public function getMetaValue(Account $account, string $field): ?string - { - $result = $account->accountMeta->filter( - function (AccountMeta $meta) use ($field) { - return strtolower($meta->name) === strtolower($field); - } - ); - if (0 === $result->count()) { - return null; - } - if (1 === $result->count()) { - return (string)$result->first()->data; - } - - return null; - } - - /** - * @param array $types - * - * @return int - */ - public function count(array $types): int - { - return $this->user->accounts()->accountTypeIn($types)->count(); - } - - /** - * @param int $accountId - * - * @return Account|null - */ - public function find(int $accountId): ?Account - { - return $this->user->accounts()->find($accountId); - } - /** * @inheritDoc */ @@ -595,52 +617,6 @@ class AccountRepository implements AccountRepositoryInterface return $order; } - /** - * @param array $types - * @param array|null $sort - * - * @return Collection - */ - public function getAccountsByType(array $types, ?array $sort = []): Collection - { - $res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types); - $query = $this->user->accounts(); - if (0 !== count($types)) { - $query->accountTypeIn($types); - } - - // add sort parameters. At this point they're filtered to allowed fields to sort by: - if (0 !== count($sort)) { - foreach ($sort as $param) { - $query->orderBy($param[0], $param[1]); - } - } - - if (0 === count($sort)) { - if (0 !== count($res)) { - $query->orderBy('accounts.order', 'ASC'); - } - $query->orderBy('accounts.active', 'DESC'); - $query->orderBy('accounts.name', 'ASC'); - } - - return $query->get(['accounts.*']); - } - - /** - * Returns the date of the very first transaction in this account. - * - * @param Account $account - * - * @return Carbon|null - */ - public function oldestJournalDate(Account $account): ?Carbon - { - $journal = $this->oldestJournal($account); - - return $journal?->date; - } - /** * Returns the date of the very first transaction in this account. * @@ -664,6 +640,20 @@ class AccountRepository implements AccountRepositoryInterface return null; } + /** + * Returns the date of the very first transaction in this account. + * + * @param Account $account + * + * @return Carbon|null + */ + public function oldestJournalDate(Account $account): ?Carbon + { + $journal = $this->oldestJournal($account); + + return $journal?->date; + } + /** * @inheritDoc */ @@ -764,6 +754,16 @@ class AccountRepository implements AccountRepositoryInterface return $dbQuery->take($limit)->get(['accounts.*']); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php index 6a10a73080..99d1614754 100644 --- a/app/Repositories/Account/AccountTasker.php +++ b/app/Repositories/Account/AccountTasker.php @@ -32,8 +32,8 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class AccountTasker. @@ -154,6 +154,50 @@ class AccountTasker implements AccountTaskerInterface return $report; } + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + * @throws FireflyException + * @throws JsonException + */ + public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array + { + // get all incomes for the given accounts in the given period! + // also transfers! + // get all transactions: + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setDestinationAccounts($accounts)->setRange($start, $end); + $collector->excludeSourceAccounts($accounts); + $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->withAccountInformation(); + $report = $this->groupIncomeBySource($collector->getExtractedJournals()); + + // sort the result + // Obtain a list of columns + $sum = []; + foreach ($report['accounts'] as $accountId => $row) { + $sum[$accountId] = (float)$row['sum']; // intentional float + } + + array_multisort($sum, SORT_DESC, $report['accounts']); + + return $report; + } + + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $array * @@ -217,40 +261,6 @@ class AccountTasker implements AccountTaskerInterface return $report; } - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - * @throws FireflyException - * @throws JsonException - */ - public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array - { - // get all incomes for the given accounts in the given period! - // also transfers! - // get all transactions: - - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); - $collector->setDestinationAccounts($accounts)->setRange($start, $end); - $collector->excludeSourceAccounts($accounts); - $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->withAccountInformation(); - $report = $this->groupIncomeBySource($collector->getExtractedJournals()); - - // sort the result - // Obtain a list of columns - $sum = []; - foreach ($report['accounts'] as $accountId => $row) { - $sum[$accountId] = (float)$row['sum']; // intentional float - } - - array_multisort($sum, SORT_DESC, $report['accounts']); - - return $report; - } - /** * @param array $array * @@ -312,14 +322,4 @@ class AccountTasker implements AccountTaskerInterface return $report; } - - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } } diff --git a/app/Repositories/Account/OperationsRepository.php b/app/Repositories/Account/OperationsRepository.php index 739b3c2cd7..9c1ed952f2 100644 --- a/app/Repositories/Account/OperationsRepository.php +++ b/app/Repositories/Account/OperationsRepository.php @@ -57,80 +57,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $this->sortByCurrency($journals, 'negative'); } - /** - * Collect transactions with some parameters - * - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * @param string $type - * - * @return array - */ - private function getTransactions(Carbon $start, Carbon $end, Collection $accounts, string $type): array - { - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); - $collector->setUser($this->user)->setRange($start, $end)->setTypes([$type]); - $collector->setBothAccounts($accounts); - $collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation()->withTagInformation(); - - return $collector->getExtractedJournals(); - } - - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @param array $journals - * @param string $direction - * - * @return array - */ - private function sortByCurrency(array $journals, string $direction): array - { - $array = []; - foreach ($journals as $journal) { - $currencyId = (int)$journal['currency_id']; - $journalId = (int)$journal['transaction_journal_id']; - $array[$currencyId] = $array[$currencyId] ?? [ - - 'currency_id' => $journal['currency_id'], - 'currency_name' => $journal['currency_name'], - 'currency_symbol' => $journal['currency_symbol'], - 'currency_code' => $journal['currency_code'], - 'currency_decimal_places' => $journal['currency_decimal_places'], - 'transaction_journals' => [], - ]; - - $array[$currencyId]['transaction_journals'][$journalId] = [ - 'amount' => app('steam')->$direction((string)$journal['amount']), - 'date' => $journal['date'], - 'transaction_journal_id' => $journalId, - 'budget_name' => $journal['budget_name'], - 'category_name' => $journal['category_name'], - 'source_account_id' => $journal['source_account_id'], - 'source_account_name' => $journal['source_account_name'], - 'source_account_iban' => $journal['source_account_iban'], - 'destination_account_id' => $journal['destination_account_id'], - 'destination_account_name' => $journal['destination_account_name'], - 'destination_account_iban' => $journal['destination_account_iban'], - 'tags' => $journal['tags'], - 'description' => $journal['description'], - 'transaction_group_id' => $journal['transaction_group_id'], - ]; - } - - return $array; - } - /** * This method returns a list of all the deposit transaction journals (as arrays) set in that period * which have the specified accounts. It's grouped per currency, with as few details in the array @@ -149,6 +75,16 @@ class OperationsRepository implements OperationsRepositoryInterface return $this->sortByCurrency($journals, 'positive'); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @inheritDoc */ @@ -164,6 +100,112 @@ class OperationsRepository implements OperationsRepositoryInterface return $this->groupByCurrency($journals, 'negative'); } + /** + * @inheritDoc + */ + public function sumExpensesByDestination( + Carbon $start, + Carbon $end, + ?Collection $accounts = null, + ?Collection $expense = null, + ?TransactionCurrency $currency = null + ): array { + $journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency); + + return $this->groupByDirection($journals, 'destination', 'negative'); + } + + /** + * @inheritDoc + */ + public function sumExpensesBySource( + Carbon $start, + Carbon $end, + ?Collection $accounts = null, + ?Collection $expense = null, + ?TransactionCurrency $currency = null + ): array { + $journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency); + + return $this->groupByDirection($journals, 'source', 'negative'); + } + + /** + * @inheritDoc + */ + public function sumIncome( + Carbon $start, + Carbon $end, + ?Collection $accounts = null, + ?Collection $revenue = null, + ?TransactionCurrency $currency = null + ): array { + $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); + + return $this->groupByCurrency($journals, 'positive'); + } + + /** + * @inheritDoc + */ + public function sumIncomeByDestination( + Carbon $start, + Carbon $end, + ?Collection $accounts = null, + ?Collection $revenue = null, + ?TransactionCurrency $currency = null + ): array { + $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); + + return $this->groupByDirection($journals, 'destination', 'positive'); + } + + /** + * @inheritDoc + */ + public function sumIncomeBySource( + Carbon $start, + Carbon $end, + ?Collection $accounts = null, + ?Collection $revenue = null, + ?TransactionCurrency $currency = null + ): array { + $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); + + return $this->groupByDirection($journals, 'source', 'positive'); + } + + /** + * @inheritDoc + */ + public function sumTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array + { + $journals = $this->getTransactionsForSum(TransactionType::TRANSFER, $start, $end, $accounts, null, $currency); + + return $this->groupByEither($journals); + } + + /** + * Collect transactions with some parameters + * + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * @param string $type + * + * @return array + */ + private function getTransactions(Carbon $start, Carbon $end, Collection $accounts, string $type): array + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user)->setRange($start, $end)->setTypes([$type]); + $collector->setBothAccounts($accounts); + $collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation()->withTagInformation(); + + return $collector->getExtractedJournals(); + } + /** * @param Carbon $start * @param Carbon $end @@ -288,21 +330,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - /** - * @inheritDoc - */ - public function sumExpensesByDestination( - Carbon $start, - Carbon $end, - ?Collection $accounts = null, - ?Collection $expense = null, - ?TransactionCurrency $currency = null - ): array { - $journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency); - - return $this->groupByDirection($journals, 'destination', 'negative'); - } - /** * @param array $journals * @param string $direction @@ -350,76 +377,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - /** - * @inheritDoc - */ - public function sumExpensesBySource( - Carbon $start, - Carbon $end, - ?Collection $accounts = null, - ?Collection $expense = null, - ?TransactionCurrency $currency = null - ): array { - $journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency); - - return $this->groupByDirection($journals, 'source', 'negative'); - } - - /** - * @inheritDoc - */ - public function sumIncome( - Carbon $start, - Carbon $end, - ?Collection $accounts = null, - ?Collection $revenue = null, - ?TransactionCurrency $currency = null - ): array { - $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); - - return $this->groupByCurrency($journals, 'positive'); - } - - /** - * @inheritDoc - */ - public function sumIncomeByDestination( - Carbon $start, - Carbon $end, - ?Collection $accounts = null, - ?Collection $revenue = null, - ?TransactionCurrency $currency = null - ): array { - $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); - - return $this->groupByDirection($journals, 'destination', 'positive'); - } - - /** - * @inheritDoc - */ - public function sumIncomeBySource( - Carbon $start, - Carbon $end, - ?Collection $accounts = null, - ?Collection $revenue = null, - ?TransactionCurrency $currency = null - ): array { - $journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency); - - return $this->groupByDirection($journals, 'source', 'positive'); - } - - /** - * @inheritDoc - */ - public function sumTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array - { - $journals = $this->getTransactionsForSum(TransactionType::TRANSFER, $start, $end, $accounts, null, $currency); - - return $this->groupByEither($journals); - } - /** * @param array $journals * @@ -540,4 +497,47 @@ class OperationsRepository implements OperationsRepositoryInterface return $return; } + + /** + * @param array $journals + * @param string $direction + * + * @return array + */ + private function sortByCurrency(array $journals, string $direction): array + { + $array = []; + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + $journalId = (int)$journal['transaction_journal_id']; + $array[$currencyId] = $array[$currencyId] ?? [ + + 'currency_id' => $journal['currency_id'], + 'currency_name' => $journal['currency_name'], + 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_decimal_places' => $journal['currency_decimal_places'], + 'transaction_journals' => [], + ]; + + $array[$currencyId]['transaction_journals'][$journalId] = [ + 'amount' => app('steam')->$direction((string)$journal['amount']), + 'date' => $journal['date'], + 'transaction_journal_id' => $journalId, + 'budget_name' => $journal['budget_name'], + 'category_name' => $journal['category_name'], + 'source_account_id' => $journal['source_account_id'], + 'source_account_name' => $journal['source_account_name'], + 'source_account_iban' => $journal['source_account_iban'], + 'destination_account_id' => $journal['destination_account_id'], + 'destination_account_name' => $journal['destination_account_name'], + 'destination_account_iban' => $journal['destination_account_iban'], + 'tags' => $journal['tags'], + 'description' => $journal['description'], + 'transaction_group_id' => $journal['transaction_group_id'], + ]; + } + + return $array; + } } diff --git a/app/Repositories/Administration/Account/AccountRepository.php b/app/Repositories/Administration/Account/AccountRepository.php index df4e834d6e..0739e10863 100644 --- a/app/Repositories/Administration/Account/AccountRepository.php +++ b/app/Repositories/Administration/Account/AccountRepository.php @@ -40,11 +40,11 @@ class AccountRepository implements AccountRepositoryInterface { // search by group, not by user $dbQuery = $this->userGroup->accounts() - ->where('active', true) - ->orderBy('accounts.order', 'ASC') - ->orderBy('accounts.account_type_id', 'ASC') - ->orderBy('accounts.name', 'ASC') - ->with(['accountType']); + ->where('active', true) + ->orderBy('accounts.order', 'ASC') + ->orderBy('accounts.account_type_id', 'ASC') + ->orderBy('accounts.name', 'ASC') + ->with(['accountType']); if ('' !== $query) { // split query on spaces just in case: $parts = explode(' ', $query); diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 2d1f89e464..2bc7c79258 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -34,9 +34,9 @@ use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use League\Flysystem\UnableToDeleteFile; -use Illuminate\Support\Facades\Log; use LogicException; /** @@ -70,6 +70,27 @@ class AttachmentRepository implements AttachmentRepositoryInterface return true; } + /** + * @param Attachment $attachment + * + * @return bool + */ + public function exists(Attachment $attachment): bool + { + /** @var Storage $disk */ + $disk = Storage::disk('upload'); + + return $disk->exists($attachment->fileName()); + } + + /** + * @return Collection + */ + public function get(): Collection + { + return $this->user->attachments()->get(); + } + /** * @param Attachment $attachment * @@ -95,27 +116,6 @@ class AttachmentRepository implements AttachmentRepositoryInterface return $unencryptedContent; } - /** - * @param Attachment $attachment - * - * @return bool - */ - public function exists(Attachment $attachment): bool - { - /** @var Storage $disk */ - $disk = Storage::disk('upload'); - - return $disk->exists($attachment->fileName()); - } - - /** - * @return Collection - */ - public function get(): Collection - { - return $this->user->attachments()->get(); - } - /** * Get attachment note text or empty string. * @@ -133,6 +133,16 @@ class AttachmentRepository implements AttachmentRepositoryInterface return null; } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * @@ -152,16 +162,6 @@ class AttachmentRepository implements AttachmentRepositoryInterface return $result; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * @param Attachment $attachment * @param array $data diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 874cc92ff5..5ba53678ea 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -43,8 +43,8 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Storage; /** @@ -108,107 +108,6 @@ class BillRepository implements BillRepositoryInterface return $bills; } - /** - * @return Collection - */ - public function getActiveBills(): Collection - { - return $this->user->bills() - ->where('active', true) - ->orderBy('bills.name', 'ASC') - ->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]); - } - - /** - * Between start and end, tells you on which date(s) the bill is expected to hit. - * - * @param Bill $bill - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection - { - $set = new Collection(); - $currentStart = clone $start; - //Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq)); - //Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d'))); - - while ($currentStart <= $end) { - //Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); - $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); - //Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); - if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue - break; - } - $set->push(clone $nextExpectedMatch); - //Log::debug(sprintf('Now %d dates in set.', $set->count())); - $nextExpectedMatch->addDay(); - - //Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); - - $currentStart = clone $nextExpectedMatch; - } - - return $set; - } - - /** - * Given a bill and a date, this method will tell you at which moment this bill expects its next - * transaction. Whether or not it is there already, is not relevant. - * - * @param Bill $bill - * @param Carbon $date - * - * @return Carbon - */ - public function nextDateMatch(Bill $bill, Carbon $date): Carbon - { - $cache = new CacheProperties(); - $cache->addProperty($bill->id); - $cache->addProperty('nextDateMatch'); - $cache->addProperty($date); - if ($cache->has()) { - return $cache->get(); - } - // find the most recent date for this bill NOT in the future. Cache this date: - $start = clone $bill->date; - - while ($start < $date) { - $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); - } - $cache->store($start); - - return $start; - } - - /** - * @param array $data - * - * @return Bill - * @throws FireflyException - * @throws JsonException - */ - public function store(array $data): Bill - { - /** @var BillFactory $factory */ - $factory = app(BillFactory::class); - $factory->setUser($this->user); - - return $factory->create($data); - } - - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * Correct order of piggies in case of issues. */ @@ -249,6 +148,18 @@ class BillRepository implements BillRepositoryInterface $this->user->bills()->delete(); } + /** + * Find a bill by ID. + * + * @param int $billId + * + * @return Bill|null + */ + public function find(int $billId): ?Bill + { + return $this->user->bills()->find($billId); + } + /** * Find bill by parameters. * @@ -280,18 +191,6 @@ class BillRepository implements BillRepositoryInterface return null; } - /** - * Find a bill by ID. - * - * @param int $billId - * - * @return Bill|null - */ - public function find(int $billId): ?Bill - { - return $this->user->bills()->find($billId); - } - /** * Find a bill by name. * @@ -304,6 +203,17 @@ class BillRepository implements BillRepositoryInterface return $this->user->bills()->where('name', $name)->first(['bills.*']); } + /** + * @return Collection + */ + public function getActiveBills(): Collection + { + return $this->user->bills() + ->where('active', true) + ->orderBy('bills.name', 'ASC') + ->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]); + } + /** * Get all attachments. * @@ -623,13 +533,48 @@ class BillRepository implements BillRepositoryInterface return $bill->transactionJournals() ->before($end)->after($start)->get( [ - 'transaction_journals.id', - 'transaction_journals.date', - 'transaction_journals.transaction_group_id', - ] + 'transaction_journals.id', + 'transaction_journals.date', + 'transaction_journals.transaction_group_id', + ] ); } + /** + * Between start and end, tells you on which date(s) the bill is expected to hit. + * + * @param Bill $bill + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection + { + $set = new Collection(); + $currentStart = clone $start; + //Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq)); + //Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d'))); + + while ($currentStart <= $end) { + //Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); + $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + //Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue + break; + } + $set->push(clone $nextExpectedMatch); + //Log::debug(sprintf('Now %d dates in set.', $set->count())); + $nextExpectedMatch->addDay(); + + //Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + + $currentStart = clone $nextExpectedMatch; + } + + return $set; + } + /** * Return all rules for one bill * @@ -747,6 +692,35 @@ class BillRepository implements BillRepositoryInterface } } + /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction. Whether or not it is there already, is not relevant. + * + * @param Bill $bill + * @param Carbon $date + * + * @return Carbon + */ + public function nextDateMatch(Bill $bill, Carbon $date): Carbon + { + $cache = new CacheProperties(); + $cache->addProperty($bill->id); + $cache->addProperty('nextDateMatch'); + $cache->addProperty($date); + if ($cache->has()) { + return $cache->get(); + } + // find the most recent date for this bill NOT in the future. Cache this date: + $start = clone $bill->date; + + while ($start < $date) { + $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); + } + $cache->store($start); + + return $start; + } + /** * Given the date in $date, this method will return a moment in the future where the bill is expected to be paid. * @@ -839,6 +813,32 @@ class BillRepository implements BillRepositoryInterface $bill->save(); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + + /** + * @param array $data + * + * @return Bill + * @throws FireflyException + * @throws JsonException + */ + public function store(array $data): Bill + { + /** @var BillFactory $factory */ + $factory = app(BillFactory::class); + $factory->setUser($this->user); + + return $factory->create($data); + } + /** * @inheritDoc */ diff --git a/app/Repositories/Budget/AvailableBudgetRepository.php b/app/Repositories/Budget/AvailableBudgetRepository.php index 99f1547ce9..179c9fca87 100644 --- a/app/Repositories/Budget/AvailableBudgetRepository.php +++ b/app/Repositories/Budget/AvailableBudgetRepository.php @@ -55,14 +55,6 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface $availableBudget->delete(); } - /** - * @inheritDoc - */ - public function findById(int $id): ?AvailableBudget - { - return $this->user->availableBudgets->find($id); - } - /** * Find existing AB. * @@ -81,6 +73,37 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface ->first(); } + /** + * @inheritDoc + */ + public function findById(int $id): ?AvailableBudget + { + return $this->user->availableBudgets->find($id); + } + + /** + * Return a list of all available budgets (in all currencies) (for the selected period). + * + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function get(?Carbon $start = null, ?Carbon $end = null): Collection + { + $query = $this->user->availableBudgets()->with(['transactionCurrency']); + if (null !== $start && null !== $end) { + $query->where( + static function (Builder $q1) use ($start, $end) { + $q1->where('start_date', '=', $start->format('Y-m-d')); + $q1->where('end_date', '=', $end->format('Y-m-d')); + } + ); + } + + return $query->get(['available_budgets.*']); + } + /** * @param TransactionCurrency $currency * @param Carbon $start @@ -122,29 +145,6 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface return $return; } - /** - * Return a list of all available budgets (in all currencies) (for the selected period). - * - * @param Carbon|null $start - * @param Carbon|null $end - * - * @return Collection - */ - public function get(?Carbon $start = null, ?Carbon $end = null): Collection - { - $query = $this->user->availableBudgets()->with(['transactionCurrency']); - if (null !== $start && null !== $end) { - $query->where( - static function (Builder $q1) use ($start, $end) { - $q1->where('start_date', '=', $start->format('Y-m-d')); - $q1->where('end_date', '=', $end->format('Y-m-d')); - } - ); - } - - return $query->get(['available_budgets.*']); - } - /** * Returns all available budget objects. * diff --git a/app/Repositories/Budget/BudgetLimitRepository.php b/app/Repositories/Budget/BudgetLimitRepository.php index 74422c6ef6..f80a99f305 100644 --- a/app/Repositories/Budget/BudgetLimitRepository.php +++ b/app/Repositories/Budget/BudgetLimitRepository.php @@ -33,8 +33,8 @@ use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * @@ -128,19 +128,19 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface } /** + * @param Budget $budget * @param TransactionCurrency $currency - * @param Carbon|null $start - * @param Carbon|null $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return BudgetLimit|null */ - public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection + public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit { - return $this->getAllBudgetLimits($start, $end)->filter( - static function (BudgetLimit $budgetLimit) use ($currency) { - return $budgetLimit->transaction_currency_id === $currency->id; - } - ); + return $budget->budgetlimits() + ->where('transaction_currency_id', $currency->id) + ->where('start_date', $start->format('Y-m-d')) + ->where('end_date', $end->format('Y-m-d'))->first(); } /** @@ -211,6 +211,22 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface )->get(['budget_limits.*']); } + /** + * @param TransactionCurrency $currency + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection + { + return $this->getAllBudgetLimits($start, $end)->filter( + static function (BudgetLimit $budgetLimit) use ($currency) { + return $budgetLimit->transaction_currency_id === $currency->id; + } + ); + } + /** * @param Budget $budget * @param Carbon|null $start @@ -330,22 +346,6 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface return $limit; } - /** - * @param Budget $budget - * @param TransactionCurrency $currency - * @param Carbon $start - * @param Carbon $end - * - * @return BudgetLimit|null - */ - public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit - { - return $budget->budgetlimits() - ->where('transaction_currency_id', $currency->id) - ->where('start_date', $start->format('Y-m-d')) - ->where('end_date', $end->format('Y-m-d'))->first(); - } - /** * @param BudgetLimit $budgetLimit * @param array $data diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 0257d08d01..7f55513f71 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -44,8 +44,8 @@ use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\QueryException; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Storage; /** @@ -144,65 +144,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $return; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @return Collection - */ - public function getActiveBudgets(): Collection - { - return $this->user->budgets()->where('active', true) - ->orderBy('order', 'ASC') - ->orderBy('name', 'ASC') - ->get(); - } - - /** - * How many days of this budget limit are between start and end? - * - * @param BudgetLimit $limit - * @param Carbon $start - * @param Carbon $end - * @return int - */ - private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int - { - // start1 = $start - // start2 = $limit->start_date - // start1 = $end - // start2 = $limit->end_date - - // limit is larger than start and end (inclusive) - // |-----------| - // |----------------| - if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) { - return $start->diffInDays($end) + 1; // add one day - } - // limit starts earlier and limit ends first: - // |-----------| - // |-------| - if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) { - // return days in the range $start-$limit_end - return $start->diffInDays($limit->end_date) + 1; // add one day, the day itself - } - // limit starts later and limit ends earlier - // |-----------| - // |-------| - if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) { - // return days in the range $limit_start - $end - return $limit->start_date->diffInDays($end) + 1; // add one day, the day itself - } - return 0; - } - /** * @inheritDoc */ @@ -280,190 +221,6 @@ class BudgetRepository implements BudgetRepositoryInterface return true; } - /** - * @param Budget $budget - * @param array $data - * - * @return Budget - * @throws FireflyException - * @throws JsonException - */ - public function update(Budget $budget, array $data): Budget - { - Log::debug('Now in update()'); - - $oldName = $budget->name; - if (array_key_exists('name', $data)) { - $budget->name = $data['name']; - $this->updateRuleActions($oldName, $budget->name); - $this->updateRuleTriggers($oldName, $budget->name); - } - if (array_key_exists('active', $data)) { - $budget->active = $data['active']; - } - if (array_key_exists('notes', $data)) { - $this->setNoteText($budget, (string)$data['notes']); - } - $budget->save(); - - // update or create auto-budget: - $autoBudget = $this->getAutoBudget($budget); - - // first things first: delete when no longer required: - $autoBudgetType = array_key_exists('auto_budget_type', $data) ? $data['auto_budget_type'] : null; - - if (0 === $autoBudgetType && null !== $autoBudget) { - // delete! - $autoBudget->delete(); - - return $budget; - } - if (0 === $autoBudgetType && null === $autoBudget) { - return $budget; - } - if (null === $autoBudgetType && null === $autoBudget) { - return $budget; - } - $this->updateAutoBudget($budget, $data); - - return $budget; - } - - /** - * @param string $oldName - * @param string $newName - */ - private function updateRuleActions(string $oldName, string $newName): void - { - $types = ['set_budget',]; - $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') - ->where('rules.user_id', $this->user->id) - ->whereIn('rule_actions.action_type', $types) - ->where('rule_actions.action_value', $oldName) - ->get(['rule_actions.*']); - Log::debug(sprintf('Found %d actions to update.', $actions->count())); - /** @var RuleAction $action */ - foreach ($actions as $action) { - $action->action_value = $newName; - $action->save(); - Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); - } - } - - /** - * @param string $oldName - * @param string $newName - */ - private function updateRuleTriggers(string $oldName, string $newName): void - { - $types = ['budget_is',]; - $triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rules.user_id', $this->user->id) - ->whereIn('rule_triggers.trigger_type', $types) - ->where('rule_triggers.trigger_value', $oldName) - ->get(['rule_triggers.*']); - Log::debug(sprintf('Found %d triggers to update.', $triggers->count())); - /** @var RuleTrigger $trigger */ - foreach ($triggers as $trigger) { - $trigger->trigger_value = $newName; - $trigger->save(); - Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); - } - } - - /** - * @param Budget $budget - * @param string $text - * @return void - */ - private function setNoteText(Budget $budget, string $text): void - { - $dbNote = $budget->notes()->first(); - if ('' !== $text) { - if (null === $dbNote) { - $dbNote = new Note(); - $dbNote->noteable()->associate($budget); - } - $dbNote->text = trim($text); - $dbNote->save(); - - return; - } - if (null !== $dbNote) { - $dbNote->delete(); - } - } - - /** - * @inheritDoc - */ - public function getAutoBudget(Budget $budget): ?AutoBudget - { - return $budget->autoBudgets()->first(); - } - - /** - * @param Budget $budget - * @param array $data - * @throws FireflyException - * @throws JsonException - */ - private function updateAutoBudget(Budget $budget, array $data): void - { - // update or create auto-budget: - $autoBudget = $this->getAutoBudget($budget); - - // grab default currency: - $currency = app('amount')->getDefaultCurrencyByUser($this->user); - - if (null === $autoBudget) { - // at this point it's a blind assumption auto_budget_type is 1 or 2. - $autoBudget = new AutoBudget(); - $autoBudget->auto_budget_type = $data['auto_budget_type']; - $autoBudget->budget_id = $budget->id; - $autoBudget->transaction_currency_id = $currency->id; - } - - // set or update the currency. - if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) { - $repos = app(CurrencyRepositoryInterface::class); - $currencyId = (int)($data['currency_id'] ?? 0); - $currencyCode = (string)($data['currency_code'] ?? ''); - $currency = $repos->find($currencyId); - if (null === $currency) { - $currency = $repos->findByCodeNull($currencyCode); - } - if (null !== $currency) { - $autoBudget->transaction_currency_id = $currency->id; - } - } - - // change values if submitted or presented: - if (array_key_exists('auto_budget_type', $data)) { - $autoBudget->auto_budget_type = $data['auto_budget_type']; - } - if (array_key_exists('auto_budget_amount', $data)) { - $autoBudget->amount = $data['auto_budget_amount']; - } - if (array_key_exists('auto_budget_period', $data)) { - $autoBudget->period = $data['auto_budget_period']; - } - - $autoBudget->save(); - } - - /** - * Find a budget or return NULL - * - * @param int|null $budgetId |null - * - * @return Budget|null - */ - public function find(int $budgetId = null): ?Budget - { - return $this->user->budgets()->find($budgetId); - } - /** * @param Budget $budget * @@ -494,15 +251,6 @@ class BudgetRepository implements BudgetRepositoryInterface } } - /** - * @return Collection - */ - public function getBudgets(): Collection - { - return $this->user->budgets()->orderBy('order', 'ASC') - ->orderBy('name', 'ASC')->get(); - } - /** * @inheritDoc */ @@ -514,6 +262,18 @@ class BudgetRepository implements BudgetRepositoryInterface } } + /** + * Find a budget or return NULL + * + * @param int|null $budgetId |null + * + * @return Budget|null + */ + public function find(int $budgetId = null): ?Budget + { + return $this->user->budgets()->find($budgetId); + } + /** * @param int|null $budgetId * @param string|null $budgetName @@ -572,6 +332,17 @@ class BudgetRepository implements BudgetRepositoryInterface return null; } + /** + * @return Collection + */ + public function getActiveBudgets(): Collection + { + return $this->user->budgets()->where('active', true) + ->orderBy('order', 'ASC') + ->orderBy('name', 'ASC') + ->get(); + } + /** * @inheritDoc */ @@ -593,6 +364,23 @@ class BudgetRepository implements BudgetRepositoryInterface ); } + /** + * @inheritDoc + */ + public function getAutoBudget(Budget $budget): ?AutoBudget + { + return $budget->autoBudgets()->first(); + } + + /** + * @return Collection + */ + public function getBudgets(): Collection + { + return $this->user->budgets()->orderBy('order', 'ASC') + ->orderBy('name', 'ASC')->get(); + } + /** * Get all budgets with these ID's. * @@ -615,6 +403,11 @@ class BudgetRepository implements BudgetRepositoryInterface ->orderBy('name', 'ASC')->where('active', 0)->get(); } + public function getMaxOrder(): int + { + return (int)$this->user->budgets()->max('order'); + } + /** * @inheritDoc */ @@ -656,6 +449,16 @@ class BudgetRepository implements BudgetRepositoryInterface $budget->save(); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @inheritDoc */ @@ -829,7 +632,7 @@ class BudgetRepository implements BudgetRepositoryInterface if ('rollover' === $type) { $type = AutoBudget::AUTO_BUDGET_ROLLOVER; } - if('adjusted' === $type) { + if ('adjusted' === $type) { $type = AutoBudget::AUTO_BUDGET_ADJUSTED; } @@ -873,8 +676,205 @@ class BudgetRepository implements BudgetRepositoryInterface return $newBudget; } - public function getMaxOrder(): int + /** + * @param Budget $budget + * @param array $data + * + * @return Budget + * @throws FireflyException + * @throws JsonException + */ + public function update(Budget $budget, array $data): Budget { - return (int)$this->user->budgets()->max('order'); + Log::debug('Now in update()'); + + $oldName = $budget->name; + if (array_key_exists('name', $data)) { + $budget->name = $data['name']; + $this->updateRuleActions($oldName, $budget->name); + $this->updateRuleTriggers($oldName, $budget->name); + } + if (array_key_exists('active', $data)) { + $budget->active = $data['active']; + } + if (array_key_exists('notes', $data)) { + $this->setNoteText($budget, (string)$data['notes']); + } + $budget->save(); + + // update or create auto-budget: + $autoBudget = $this->getAutoBudget($budget); + + // first things first: delete when no longer required: + $autoBudgetType = array_key_exists('auto_budget_type', $data) ? $data['auto_budget_type'] : null; + + if (0 === $autoBudgetType && null !== $autoBudget) { + // delete! + $autoBudget->delete(); + + return $budget; + } + if (0 === $autoBudgetType && null === $autoBudget) { + return $budget; + } + if (null === $autoBudgetType && null === $autoBudget) { + return $budget; + } + $this->updateAutoBudget($budget, $data); + + return $budget; + } + + /** + * How many days of this budget limit are between start and end? + * + * @param BudgetLimit $limit + * @param Carbon $start + * @param Carbon $end + * @return int + */ + private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int + { + // start1 = $start + // start2 = $limit->start_date + // start1 = $end + // start2 = $limit->end_date + + // limit is larger than start and end (inclusive) + // |-----------| + // |----------------| + if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) { + return $start->diffInDays($end) + 1; // add one day + } + // limit starts earlier and limit ends first: + // |-----------| + // |-------| + if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) { + // return days in the range $start-$limit_end + return $start->diffInDays($limit->end_date) + 1; // add one day, the day itself + } + // limit starts later and limit ends earlier + // |-----------| + // |-------| + if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) { + // return days in the range $limit_start - $end + return $limit->start_date->diffInDays($end) + 1; // add one day, the day itself + } + return 0; + } + + /** + * @param Budget $budget + * @param string $text + * @return void + */ + private function setNoteText(Budget $budget, string $text): void + { + $dbNote = $budget->notes()->first(); + if ('' !== $text) { + if (null === $dbNote) { + $dbNote = new Note(); + $dbNote->noteable()->associate($budget); + } + $dbNote->text = trim($text); + $dbNote->save(); + + return; + } + if (null !== $dbNote) { + $dbNote->delete(); + } + } + + /** + * @param Budget $budget + * @param array $data + * @throws FireflyException + * @throws JsonException + */ + private function updateAutoBudget(Budget $budget, array $data): void + { + // update or create auto-budget: + $autoBudget = $this->getAutoBudget($budget); + + // grab default currency: + $currency = app('amount')->getDefaultCurrencyByUser($this->user); + + if (null === $autoBudget) { + // at this point it's a blind assumption auto_budget_type is 1 or 2. + $autoBudget = new AutoBudget(); + $autoBudget->auto_budget_type = $data['auto_budget_type']; + $autoBudget->budget_id = $budget->id; + $autoBudget->transaction_currency_id = $currency->id; + } + + // set or update the currency. + if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) { + $repos = app(CurrencyRepositoryInterface::class); + $currencyId = (int)($data['currency_id'] ?? 0); + $currencyCode = (string)($data['currency_code'] ?? ''); + $currency = $repos->find($currencyId); + if (null === $currency) { + $currency = $repos->findByCodeNull($currencyCode); + } + if (null !== $currency) { + $autoBudget->transaction_currency_id = $currency->id; + } + } + + // change values if submitted or presented: + if (array_key_exists('auto_budget_type', $data)) { + $autoBudget->auto_budget_type = $data['auto_budget_type']; + } + if (array_key_exists('auto_budget_amount', $data)) { + $autoBudget->amount = $data['auto_budget_amount']; + } + if (array_key_exists('auto_budget_period', $data)) { + $autoBudget->period = $data['auto_budget_period']; + } + + $autoBudget->save(); + } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRuleActions(string $oldName, string $newName): void + { + $types = ['set_budget',]; + $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') + ->where('rules.user_id', $this->user->id) + ->whereIn('rule_actions.action_type', $types) + ->where('rule_actions.action_value', $oldName) + ->get(['rule_actions.*']); + Log::debug(sprintf('Found %d actions to update.', $actions->count())); + /** @var RuleAction $action */ + foreach ($actions as $action) { + $action->action_value = $newName; + $action->save(); + Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); + } + } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRuleTriggers(string $oldName, string $newName): void + { + $types = ['budget_is',]; + $triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rules.user_id', $this->user->id) + ->whereIn('rule_triggers.trigger_type', $types) + ->where('rule_triggers.trigger_value', $oldName) + ->get(['rule_triggers.*']); + Log::debug(sprintf('Found %d triggers to update.', $triggers->count())); + /** @var RuleTrigger $trigger */ + foreach ($triggers as $trigger) { + $trigger->trigger_value = $newName; + $trigger->save(); + Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); + } } } diff --git a/app/Repositories/Budget/NoBudgetRepository.php b/app/Repositories/Budget/NoBudgetRepository.php index cf80215f58..96f99746fd 100644 --- a/app/Repositories/Budget/NoBudgetRepository.php +++ b/app/Repositories/Budget/NoBudgetRepository.php @@ -86,6 +86,16 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface return $data; } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param Collection $accounts * @param Carbon $start @@ -138,16 +148,6 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface return $return; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * TODO this method does not include multi currency. It just counts. * TODO this probably also applies to the other "sumExpenses" methods. diff --git a/app/Repositories/Budget/OperationsRepository.php b/app/Repositories/Budget/OperationsRepository.php index 4bca48f1d1..12c2080081 100644 --- a/app/Repositories/Budget/OperationsRepository.php +++ b/app/Repositories/Budget/OperationsRepository.php @@ -210,17 +210,6 @@ class OperationsRepository implements OperationsRepositoryInterface } } - /** - * @return Collection - */ - private function getBudgets(): Collection - { - /** @var BudgetRepositoryInterface $repos */ - $repos = app(BudgetRepositoryInterface::class); - - return $repos->getActiveBudgets(); - } - /** * @param Collection $budgets * @param Collection $accounts @@ -403,4 +392,15 @@ class OperationsRepository implements OperationsRepositoryInterface return $blRepository->getBudgetLimits($budget, $start, $end); } + + /** + * @return Collection + */ + private function getBudgets(): Collection + { + /** @var BudgetRepositoryInterface $repos */ + $repos = app(BudgetRepositoryInterface::class); + + return $repos->getActiveBudgets(); + } } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 6b9024c82d..3e46a18f9d 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -107,13 +107,27 @@ class CategoryRepository implements CategoryRepositoryInterface } /** - * Returns a list of all the categories belonging to a user. + * Find a category or return NULL * - * @return Collection + * @param int $categoryId + * + * @return Category|null */ - public function getCategories(): Collection + public function find(int $categoryId): ?Category { - return $this->user->categories()->with(['attachments'])->orderBy('name', 'ASC')->get(); + return $this->user->categories()->find($categoryId); + } + + /** + * Find a category. + * + * @param string $name + * + * @return Category|null + */ + public function findByName(string $name): ?Category + { + return $this->user->categories()->where('name', $name)->first(['categories.*']); } /** @@ -144,90 +158,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $result; } - /** - * Find a category or return NULL - * - * @param int $categoryId - * - * @return Category|null - */ - public function find(int $categoryId): ?Category - { - return $this->user->categories()->find($categoryId); - } - - /** - * Find a category. - * - * @param string $name - * - * @return Category|null - */ - public function findByName(string $name): ?Category - { - return $this->user->categories()->where('name', $name)->first(['categories.*']); - } - - /** - * @param array $data - * - * @return Category - * @throws FireflyException - */ - public function store(array $data): Category - { - /** @var CategoryFactory $factory */ - $factory = app(CategoryFactory::class); - $factory->setUser($this->user); - - $category = $factory->findOrCreate(null, $data['name']); - - if (null === $category) { - throw new FireflyException(sprintf('400003: Could not store new category with name "%s"', $data['name'])); - } - - if (array_key_exists('notes', $data) && '' === $data['notes']) { - $this->removeNotes($category); - } - if (array_key_exists('notes', $data) && '' !== $data['notes']) { - $this->updateNotes($category, $data['notes']); - } - - return $category; - } - - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @param Category $category - */ - public function removeNotes(Category $category): void - { - $category->notes()->delete(); - } - - /** - * @inheritDoc - */ - public function updateNotes(Category $category, string $notes): void - { - $dbNote = $category->notes()->first(); - if (null === $dbNote) { - $dbNote = new Note(); - $dbNote->noteable()->associate($category); - } - $dbNote->text = trim($notes); - $dbNote->save(); - } - /** * @param Category $category * @@ -256,43 +186,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $firstJournalDate; } - /** - * @param Category $category - * - * @return Carbon|null - */ - private function getFirstJournalDate(Category $category): ?Carbon - { - $query = $category->transactionJournals()->orderBy('date', 'ASC'); - $result = $query->first(['transaction_journals.*']); - - if (null !== $result) { - return $result->date; - } - - return null; - } - - /** - * @param Category $category - * - * @return Carbon|null - */ - private function getFirstTransactionDate(Category $category): ?Carbon - { - // check transactions: - $query = $category->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'ASC'); - - $lastTransaction = $query->first(['transaction_journals.*']); - if (null !== $lastTransaction) { - return new Carbon($lastTransaction->date); - } - - return null; - } - /** * @inheritDoc */ @@ -326,6 +219,16 @@ class CategoryRepository implements CategoryRepositoryInterface return $this->user->categories()->whereIn('id', $categoryIds)->get(); } + /** + * Returns a list of all the categories belonging to a user. + * + * @return Collection + */ + public function getCategories(): Collection + { + return $this->user->categories()->with(['attachments'])->orderBy('name', 'ASC')->get(); + } + /** * @inheritDoc */ @@ -368,6 +271,135 @@ class CategoryRepository implements CategoryRepositoryInterface return $lastJournalDate; } + /** + * @param Category $category + */ + public function removeNotes(Category $category): void + { + $category->notes()->delete(); + } + + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function searchCategory(string $query, int $limit): Collection + { + $search = $this->user->categories(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); + } + + return $search->take($limit)->get(); + } + + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + + /** + * @param array $data + * + * @return Category + * @throws FireflyException + */ + public function store(array $data): Category + { + /** @var CategoryFactory $factory */ + $factory = app(CategoryFactory::class); + $factory->setUser($this->user); + + $category = $factory->findOrCreate(null, $data['name']); + + if (null === $category) { + throw new FireflyException(sprintf('400003: Could not store new category with name "%s"', $data['name'])); + } + + if (array_key_exists('notes', $data) && '' === $data['notes']) { + $this->removeNotes($category); + } + if (array_key_exists('notes', $data) && '' !== $data['notes']) { + $this->updateNotes($category, $data['notes']); + } + + return $category; + } + + /** + * @param Category $category + * @param array $data + * + * @return Category + * @throws Exception + */ + public function update(Category $category, array $data): Category + { + /** @var CategoryUpdateService $service */ + $service = app(CategoryUpdateService::class); + $service->setUser($this->user); + + return $service->update($category, $data); + } + + /** + * @inheritDoc + */ + public function updateNotes(Category $category, string $notes): void + { + $dbNote = $category->notes()->first(); + if (null === $dbNote) { + $dbNote = new Note(); + $dbNote->noteable()->associate($category); + } + $dbNote->text = trim($notes); + $dbNote->save(); + } + + /** + * @param Category $category + * + * @return Carbon|null + */ + private function getFirstJournalDate(Category $category): ?Carbon + { + $query = $category->transactionJournals()->orderBy('date', 'ASC'); + $result = $query->first(['transaction_journals.*']); + + if (null !== $result) { + return $result->date; + } + + return null; + } + + /** + * @param Category $category + * + * @return Carbon|null + */ + private function getFirstTransactionDate(Category $category): ?Carbon + { + // check transactions: + $query = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'ASC'); + + $lastTransaction = $query->first(['transaction_journals.*']); + if (null !== $lastTransaction) { + return new Carbon($lastTransaction->date); + } + + return null; + } + /** * @param Category $category * @param Collection $accounts @@ -417,36 +449,4 @@ class CategoryRepository implements CategoryRepositoryInterface return null; } - - /** - * @param string $query - * @param int $limit - * - * @return Collection - */ - public function searchCategory(string $query, int $limit): Collection - { - $search = $this->user->categories(); - if ('' !== $query) { - $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); - } - - return $search->take($limit)->get(); - } - - /** - * @param Category $category - * @param array $data - * - * @return Category - * @throws Exception - */ - public function update(Category $category, array $data): Category - { - /** @var CategoryUpdateService $service */ - $service = app(CategoryUpdateService::class); - $service->setUser($this->user); - - return $service->update($category, $data); - } } diff --git a/app/Repositories/Category/NoCategoryRepository.php b/app/Repositories/Category/NoCategoryRepository.php index a8746e3687..1b5d64b2b0 100644 --- a/app/Repositories/Category/NoCategoryRepository.php +++ b/app/Repositories/Category/NoCategoryRepository.php @@ -90,16 +90,6 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface return $array; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * This method returns a list of all the deposit transaction journals (as arrays) set in that period * which have no category set to them. It's grouped per currency, with as few details in the array @@ -152,6 +142,16 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface return $array; } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * Sum of withdrawal journals in period without a category, grouped per currency. Amounts are always negative. * diff --git a/app/Repositories/Category/OperationsRepository.php b/app/Repositories/Category/OperationsRepository.php index 09aa138ca2..25770c93fd 100644 --- a/app/Repositories/Category/OperationsRepository.php +++ b/app/Repositories/Category/OperationsRepository.php @@ -116,26 +116,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * Returns a list of all the categories belonging to a user. - * - * @return Collection - */ - private function getCategories(): Collection - { - return $this->user->categories()->get(); - } - /** * This method returns a list of all the deposit transaction journals (as arrays) set in that period * which have the specified category set to them. It's grouped per currency, with as few details in the array @@ -341,6 +321,16 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * Sum of withdrawal journals in period for a set of categories, grouped per currency. Amounts are always negative. * @@ -470,4 +460,14 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } + + /** + * Returns a list of all the categories belonging to a user. + * + * @return Collection + */ + private function getCategories(): Collection + { + return $this->user->categories()->get(); + } } diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index a414936728..64b60f284f 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -41,8 +41,8 @@ use FireflyIII\Services\Internal\Update\CurrencyUpdateService; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class CurrencyRepository. @@ -51,6 +51,19 @@ class CurrencyRepository implements CurrencyRepositoryInterface { private User $user; + /** + * @param TransactionCurrency $currency + * + * @return int + */ + public function countJournals(TransactionCurrency $currency): int + { + $count = $currency->transactions()->whereNull('deleted_at')->count() + $currency->transactionJournals()->whereNull('deleted_at')->count(); + + // also count foreign: + return $count + Transaction::where('foreign_currency_id', $currency->id)->count(); + } + /** * @param TransactionCurrency $currency * @@ -152,35 +165,6 @@ class CurrencyRepository implements CurrencyRepositoryInterface return null; } - /** - * @param TransactionCurrency $currency - * - * @return int - */ - public function countJournals(TransactionCurrency $currency): int - { - $count = $currency->transactions()->whereNull('deleted_at')->count() + $currency->transactionJournals()->whereNull('deleted_at')->count(); - - // also count foreign: - return $count + Transaction::where('foreign_currency_id', $currency->id)->count(); - } - - /** - * @return Collection - */ - public function getAll(): Collection - { - return TransactionCurrency::orderBy('code', 'ASC')->get(); - } - - /** - * @return Collection - */ - public function get(): Collection - { - return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); - } - /** * @param TransactionCurrency $currency * @@ -210,6 +194,16 @@ class CurrencyRepository implements CurrencyRepositoryInterface $currency->save(); } + /** + * @param TransactionCurrency $currency + * Enables a currency + */ + public function enable(TransactionCurrency $currency): void + { + $currency->enabled = true; + $currency->save(); + } + /** * @inheritDoc */ @@ -230,13 +224,27 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** - * @param TransactionCurrency $currency - * Enables a currency + * Find by ID, return NULL if not found. + * + * @param int $currencyId + * + * @return TransactionCurrency|null */ - public function enable(TransactionCurrency $currency): void + public function find(int $currencyId): ?TransactionCurrency { - $currency->enabled = true; - $currency->save(); + return TransactionCurrency::find($currencyId); + } + + /** + * Find by currency code, return NULL if unfound. + * + * @param string $currencyCode + * + * @return TransactionCurrency|null + */ + public function findByCode(string $currencyCode): ?TransactionCurrency + { + return TransactionCurrency::where('code', $currencyCode)->first(); } /** @@ -362,27 +370,19 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** - * Find by ID, return NULL if not found. - * - * @param int $currencyId - * - * @return TransactionCurrency|null + * @return Collection */ - public function find(int $currencyId): ?TransactionCurrency + public function get(): Collection { - return TransactionCurrency::find($currencyId); + return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); } /** - * Find by currency code, return NULL if unfound. - * - * @param string $currencyCode - * - * @return TransactionCurrency|null + * @return Collection */ - public function findByCode(string $currencyCode): ?TransactionCurrency + public function getAll(): Collection { - return TransactionCurrency::where('code', $currencyCode)->first(); + return TransactionCurrency::orderBy('code', 'ASC')->get(); } /** diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index e5a3ee6985..6b5fd4a6b1 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -70,6 +70,18 @@ class JournalRepository implements JournalRepositoryInterface $service->destroy($journal); } + /** + * Find a specific journal. + * + * @param int $journalId + * + * @return TransactionJournal|null + */ + public function find(int $journalId): ?TransactionJournal + { + return $this->user->transactionJournals()->find($journalId); + } + /** * @inheritDoc */ @@ -221,18 +233,6 @@ class JournalRepository implements JournalRepositoryInterface $journal?->transactions()->update(['reconciled' => true]); } - /** - * Find a specific journal. - * - * @param int $journalId - * - * @return TransactionJournal|null - */ - public function find(int $journalId): ?TransactionJournal - { - return $this->user->transactionJournals()->find($journalId); - } - /** * Search in journal descriptions. * diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index c068e453c9..83cf35e81b 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -68,28 +68,6 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return true; } - /** - * @param LinkType $linkType - * @param array $data - * - * @return LinkType - */ - public function update(LinkType $linkType, array $data): LinkType - { - if (array_key_exists('name', $data) && '' !== (string)$data['name']) { - $linkType->name = $data['name']; - } - if (array_key_exists('inward', $data) && '' !== (string)$data['inward']) { - $linkType->inward = $data['inward']; - } - if (array_key_exists('outward', $data) && '' !== (string)$data['outward']) { - $linkType->outward = $data['outward']; - } - $linkType->save(); - - return $linkType; - } - /** * @param TransactionJournalLink $link * @@ -104,6 +82,30 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return true; } + /** + * @param int $linkTypeId + * + * @return LinkType|null + */ + public function find(int $linkTypeId): ?LinkType + { + return LinkType::find($linkTypeId); + } + + /** + * @param string|null $name + * + * @return LinkType|null + */ + public function findByName(string $name = null): ?LinkType + { + if (null === $name) { + return null; + } + + return LinkType::where('name', $name)->first(); + } + /** * Check if link exists between journals. * @@ -121,6 +123,30 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $count + $opposingCount > 0; } + /** + * See if such a link already exists (and get it). + * + * @param LinkType $linkType + * @param TransactionJournal $inward + * @param TransactionJournal $outward + * + * @return TransactionJournalLink|null + */ + public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink + { + return TransactionJournalLink::where('link_type_id', $linkType->id) + ->where('source_id', $inward->id) + ->where('destination_id', $outward->id)->first(); + } + + /** + * @return Collection + */ + public function get(): Collection + { + return LinkType::orderBy('name', 'ASC')->get(); + } + /** * Return array of all journal ID's for this type of link. * @@ -137,14 +163,6 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return array_unique(array_merge($sources, $destinations)); } - /** - * @return Collection - */ - public function get(): Collection - { - return LinkType::orderBy('name', 'ASC')->get(); - } - /** * Returns all the journal links (of a specific type). * @@ -275,66 +293,19 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $link; } - /** - * @param int $linkTypeId - * - * @return LinkType|null - */ - public function find(int $linkTypeId): ?LinkType - { - return LinkType::find($linkTypeId); - } - - /** - * @param string|null $name - * - * @return LinkType|null - */ - public function findByName(string $name = null): ?LinkType - { - if (null === $name) { - return null; - } - - return LinkType::where('name', $name)->first(); - } - - /** - * See if such a link already exists (and get it). - * - * @param LinkType $linkType - * @param TransactionJournal $inward - * @param TransactionJournal $outward - * - * @return TransactionJournalLink|null - */ - public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink - { - return TransactionJournalLink::where('link_type_id', $linkType->id) - ->where('source_id', $inward->id) - ->where('destination_id', $outward->id)->first(); - } - /** * @param TransactionJournalLink $link - * @param string $text * - * @throws Exception + * @return bool */ - private function setNoteText(TransactionJournalLink $link, string $text): void + public function switchLink(TransactionJournalLink $link): bool { - $dbNote = $link->notes()->first(); - if ('' !== $text) { - if (null === $dbNote) { - $dbNote = new Note(); - $dbNote->noteable()->associate($link); - } - $dbNote->text = trim($text); - $dbNote->save(); + $source = $link->source_id; + $link->source_id = $link->destination_id; + $link->destination_id = $source; + $link->save(); - return; - } - $dbNote?->delete(); + return true; } /** @@ -352,18 +323,25 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface } /** - * @param TransactionJournalLink $link + * @param LinkType $linkType + * @param array $data * - * @return bool + * @return LinkType */ - public function switchLink(TransactionJournalLink $link): bool + public function update(LinkType $linkType, array $data): LinkType { - $source = $link->source_id; - $link->source_id = $link->destination_id; - $link->destination_id = $source; - $link->save(); + if (array_key_exists('name', $data) && '' !== (string)$data['name']) { + $linkType->name = $data['name']; + } + if (array_key_exists('inward', $data) && '' !== (string)$data['inward']) { + $linkType->inward = $data['inward']; + } + if (array_key_exists('outward', $data) && '' !== (string)$data['outward']) { + $linkType->outward = $data['outward']; + } + $linkType->save(); - return true; + return $linkType; } /** @@ -397,4 +375,26 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $journalLink; } + + /** + * @param TransactionJournalLink $link + * @param string $text + * + * @throws Exception + */ + private function setNoteText(TransactionJournalLink $link, string $text): void + { + $dbNote = $link->notes()->first(); + if ('' !== $text) { + if (null === $dbNote) { + $dbNote = new Note(); + $dbNote->noteable()->associate($link); + } + $dbNote->text = trim($text); + $dbNote->save(); + + return; + } + $dbNote?->delete(); + } } diff --git a/app/Repositories/ObjectGroup/CreatesObjectGroups.php b/app/Repositories/ObjectGroup/CreatesObjectGroups.php index 1150528516..d4736cb73d 100644 --- a/app/Repositories/ObjectGroup/CreatesObjectGroups.php +++ b/app/Repositories/ObjectGroup/CreatesObjectGroups.php @@ -31,6 +31,16 @@ use FireflyIII\Models\ObjectGroup; */ trait CreatesObjectGroups { + /** + * @param string $title + * + * @return null|ObjectGroup + */ + protected function findObjectGroup(string $title): ?ObjectGroup + { + return $this->user->objectGroups()->where('title', $title)->first(); + } + /** * @param int $groupId * @@ -80,14 +90,4 @@ trait CreatesObjectGroups { return 1 === $this->user->objectGroups()->where('title', $title)->count(); } - - /** - * @param string $title - * - * @return null|ObjectGroup - */ - protected function findObjectGroup(string $title): ?ObjectGroup - { - return $this->user->objectGroups()->where('title', $title)->first(); - } } diff --git a/app/Repositories/ObjectGroup/ObjectGroupRepository.php b/app/Repositories/ObjectGroup/ObjectGroupRepository.php index 6971b4d79a..a18feb55b1 100644 --- a/app/Repositories/ObjectGroup/ObjectGroupRepository.php +++ b/app/Repositories/ObjectGroup/ObjectGroupRepository.php @@ -53,17 +53,6 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface } } - /** - * @inheritDoc - */ - public function get(): Collection - { - return $this->user->objectGroups() - ->with(['piggyBanks', 'bills']) - ->orderBy('order', 'ASC') - ->orderBy('title', 'ASC')->get(); - } - /** * @inheritDoc */ @@ -93,6 +82,17 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface $objectGroup->delete(); } + /** + * @inheritDoc + */ + public function get(): Collection + { + return $this->user->objectGroups() + ->with(['piggyBanks', 'bills']) + ->orderBy('order', 'ASC') + ->orderBy('title', 'ASC')->get(); + } + /** * @inheritDoc */ @@ -151,34 +151,6 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface return $dbQuery->take($limit)->get(['object_groups.*']); } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @inheritDoc - */ - public function update(ObjectGroup $objectGroup, array $data): ObjectGroup - { - if (array_key_exists('title', $data)) { - $objectGroup->title = $data['title']; - } - - if (array_key_exists('order', $data)) { - $this->setOrder($objectGroup, (int)$data['order']); - } - - $objectGroup->save(); - - return $objectGroup; - } - /** * @inheritDoc */ @@ -207,4 +179,32 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface return $objectGroup; } + + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + + /** + * @inheritDoc + */ + public function update(ObjectGroup $objectGroup, array $data): ObjectGroup + { + if (array_key_exists('title', $data)) { + $objectGroup->title = $data['title']; + } + + if (array_key_exists('order', $data)) { + $this->setOrder($objectGroup, (int)$data['order']); + } + + $objectGroup->save(); + + return $objectGroup; + } } diff --git a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php index 3848cad1b1..00507a6aa0 100644 --- a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php +++ b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php @@ -43,34 +43,6 @@ trait ModifiesPiggyBanks { use CreatesObjectGroups; - public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount, TransactionJournal $journal): void - { - Log::debug(sprintf('addAmountToRepetition: %s', $amount)); - if (-1 === bccomp($amount, '0')) { - Log::debug('Remove amount.'); - $this->removeAmount($repetition->piggyBank, bcmul($amount, '-1'), $journal); - } - if (1 === bccomp($amount, '0')) { - Log::debug('Add amount.'); - $this->addAmount($repetition->piggyBank, $amount, $journal); - } - } - - public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool - { - $repetition = $this->getRepetition($piggyBank); - if (null === $repetition) { - return false; - } - $repetition->currentamount = bcsub($repetition->currentamount, $amount); - $repetition->save(); - - Log::debug('addAmount [a]: Trigger change for negative amount.'); - event(new ChangedPiggyBankAmount($piggyBank, bcmul($amount, '-1'), $journal, null)); - - return true; - } - /** * @param PiggyBank $piggyBank * @param string $amount @@ -93,6 +65,19 @@ trait ModifiesPiggyBanks return true; } + public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount, TransactionJournal $journal): void + { + Log::debug(sprintf('addAmountToRepetition: %s', $amount)); + if (-1 === bccomp($amount, '0')) { + Log::debug('Remove amount.'); + $this->removeAmount($repetition->piggyBank, bcmul($amount, '-1'), $journal); + } + if (1 === bccomp($amount, '0')) { + Log::debug('Add amount.'); + $this->addAmount($repetition->piggyBank, $amount, $journal); + } + } + /** * @param PiggyBank $piggyBank * @param string $amount @@ -155,6 +140,21 @@ trait ModifiesPiggyBanks return true; } + public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool + { + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { + return false; + } + $repetition->currentamount = bcsub($repetition->currentamount, $amount); + $repetition->save(); + + Log::debug('addAmount [a]: Trigger change for negative amount.'); + event(new ChangedPiggyBankAmount($piggyBank, bcmul($amount, '-1'), $journal, null)); + + return true; + } + /** * @inheritDoc */ @@ -165,6 +165,23 @@ trait ModifiesPiggyBanks return $piggyBank; } + /** + * Correct order of piggies in case of issues. + */ + public function resetOrder(): void + { + $set = $this->user->piggyBanks()->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']); + $current = 1; + foreach ($set as $piggyBank) { + if ((int)$piggyBank->order !== $current) { + Log::debug(sprintf('Piggy bank #%d ("%s") was at place %d but should be on %d', $piggyBank->id, $piggyBank->name, $piggyBank->order, $current)); + $piggyBank->order = $current; + $piggyBank->save(); + } + $current++; + } + } + /** * @param PiggyBank $piggyBank * @param string $amount @@ -210,6 +227,34 @@ trait ModifiesPiggyBanks return $piggyBank; } + /** + * @inheritDoc + */ + public function setOrder(PiggyBank $piggyBank, int $newOrder): bool + { + $oldOrder = (int)$piggyBank->order; + Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); + if ($newOrder > $oldOrder) { + $this->user->piggyBanks()->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) + ->where('piggy_banks.id', '!=', $piggyBank->id) + ->decrement('piggy_banks.order'); + $piggyBank->order = $newOrder; + Log::debug(sprintf('Order of piggy #%d ("%s") is now %d', $piggyBank->id, $piggyBank->name, $newOrder)); + $piggyBank->save(); + + return true; + } + + $this->user->piggyBanks()->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) + ->where('piggy_banks.id', '!=', $piggyBank->id) + ->increment('piggy_banks.order'); + $piggyBank->order = $newOrder; + Log::debug(sprintf('Order of piggy #%d ("%s") is now %d', $piggyBank->id, $piggyBank->name, $newOrder)); + $piggyBank->save(); + + return true; + } + /** * @param array $data * @@ -274,76 +319,6 @@ trait ModifiesPiggyBanks return $piggyBank; } - /** - * Correct order of piggies in case of issues. - */ - public function resetOrder(): void - { - $set = $this->user->piggyBanks()->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']); - $current = 1; - foreach ($set as $piggyBank) { - if ((int)$piggyBank->order !== $current) { - Log::debug(sprintf('Piggy bank #%d ("%s") was at place %d but should be on %d', $piggyBank->id, $piggyBank->name, $piggyBank->order, $current)); - $piggyBank->order = $current; - $piggyBank->save(); - } - $current++; - } - } - - /** - * @inheritDoc - */ - public function setOrder(PiggyBank $piggyBank, int $newOrder): bool - { - $oldOrder = (int)$piggyBank->order; - Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); - if ($newOrder > $oldOrder) { - $this->user->piggyBanks()->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) - ->where('piggy_banks.id', '!=', $piggyBank->id) - ->decrement('piggy_banks.order'); - $piggyBank->order = $newOrder; - Log::debug(sprintf('Order of piggy #%d ("%s") is now %d', $piggyBank->id, $piggyBank->name, $newOrder)); - $piggyBank->save(); - - return true; - } - - $this->user->piggyBanks()->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) - ->where('piggy_banks.id', '!=', $piggyBank->id) - ->increment('piggy_banks.order'); - $piggyBank->order = $newOrder; - Log::debug(sprintf('Order of piggy #%d ("%s") is now %d', $piggyBank->id, $piggyBank->name, $newOrder)); - $piggyBank->save(); - - return true; - } - - /** - * @param PiggyBank $piggyBank - * @param string $note - * - * @return bool - */ - private function updateNote(PiggyBank $piggyBank, string $note): bool - { - if ('' === $note) { - $dbNote = $piggyBank->notes()->first(); - $dbNote?->delete(); - - return true; - } - $dbNote = $piggyBank->notes()->first(); - if (null === $dbNote) { - $dbNote = new Note(); - $dbNote->noteable()->associate($piggyBank); - } - $dbNote->text = trim($note); - $dbNote->save(); - - return true; - } - /** * @param PiggyBank $piggyBank * @param array $data @@ -413,6 +388,31 @@ trait ModifiesPiggyBanks return $piggyBank; } + /** + * @param PiggyBank $piggyBank + * @param string $note + * + * @return bool + */ + private function updateNote(PiggyBank $piggyBank, string $note): bool + { + if ('' === $note) { + $dbNote = $piggyBank->notes()->first(); + $dbNote?->delete(); + + return true; + } + $dbNote = $piggyBank->notes()->first(); + if (null === $dbNote) { + $dbNote = new Note(); + $dbNote->noteable()->associate($piggyBank); + } + $dbNote->text = trim($note); + $dbNote->save(); + + return true; + } + /** * @param PiggyBank $piggyBank * @param array $data diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 9f03677c6a..cc69b49c8e 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -36,8 +36,8 @@ use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Storage; /** @@ -58,6 +58,29 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface $this->user->piggyBanks()->delete(); } + /** + * @param int $piggyBankId + * + * @return PiggyBank|null + */ + public function find(int $piggyBankId): ?PiggyBank + { + // phpstan doesn't get the Model. + return $this->user->piggyBanks()->find($piggyBankId); // @phpstan-ignore-line + } + + /** + * Find by name or return NULL. + * + * @param string $name + * + * @return PiggyBank|null + */ + public function findByName(string $name): ?PiggyBank + { + return $this->user->piggyBanks()->where('piggy_banks.name', $name)->first(['piggy_banks.*']); + } + /** * @param int|null $piggyBankId * @param string|null $piggyBankName @@ -89,29 +112,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return null; } - /** - * @param int $piggyBankId - * - * @return PiggyBank|null - */ - public function find(int $piggyBankId): ?PiggyBank - { - // phpstan doesn't get the Model. - return $this->user->piggyBanks()->find($piggyBankId); // @phpstan-ignore-line - } - - /** - * Find by name or return NULL. - * - * @param string $name - * - * @return PiggyBank|null - */ - public function findByName(string $name): ?PiggyBank - { - return $this->user->piggyBanks()->where('piggy_banks.name', $name)->first(['piggy_banks.*']); - } - /** * @inheritDoc */ @@ -150,16 +150,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return (string)$rep->currentamount; } - /** - * @param PiggyBank $piggyBank - * - * @return PiggyBankRepetition|null - */ - public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition - { - return $piggyBank->piggyBankRepetitions()->first(); - } - /** * @param PiggyBank $piggyBank * @@ -274,16 +264,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return (string)$amount; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * @return int */ @@ -310,6 +290,22 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $note->text; } + /** + * @return Collection + */ + public function getPiggyBanks(): Collection + { + return $this->user // @phpstan-ignore-line (phpstan does not recognize objectGroups) + ->piggyBanks() + ->with( + [ + 'account', + 'objectGroups', + ] + ) + ->orderBy('order', 'ASC')->get(); + } + /** * Also add amount in name. * @@ -331,19 +327,13 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface } /** - * @return Collection + * @param PiggyBank $piggyBank + * + * @return PiggyBankRepetition|null */ - public function getPiggyBanks(): Collection + public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition { - return $this->user // @phpstan-ignore-line (phpstan does not recognize objectGroups) - ->piggyBanks() - ->with( - [ - 'account', - 'objectGroups', - ] - ) - ->orderBy('order', 'ASC')->get(); + return $piggyBank->piggyBankRepetitions()->first(); } /** @@ -422,4 +412,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $search->take($limit)->get(); } + + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } } diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 1948b9ece4..324aedc59d 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -47,8 +47,8 @@ use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class RecurringRepository @@ -92,21 +92,6 @@ class RecurringRepository implements RecurringRepositoryInterface return false; } - /** - * Returns all of the user's recurring transactions. - * - * @return Collection - */ - public function get(): Collection - { - return $this->user->recurrences() - ->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions']) - ->orderBy('active', 'DESC') - ->orderBy('transaction_type_id', 'ASC') - ->orderBy('title', 'ASC') - ->get(); - } - /** * Destroy a recurring transaction. * @@ -127,6 +112,21 @@ class RecurringRepository implements RecurringRepositoryInterface $this->user->recurrences()->delete(); } + /** + * Returns all of the user's recurring transactions. + * + * @return Collection + */ + public function get(): Collection + { + return $this->user->recurrences() + ->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions']) + ->orderBy('active', 'DESC') + ->orderBy('transaction_type_id', 'ASC') + ->orderBy('title', 'ASC') + ->get(); + } + /** * Get ALL recurring transactions. * @@ -276,6 +276,45 @@ class RecurringRepository implements RecurringRepositoryInterface return ''; } + /** + * Generate events in the date range. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @return array + * + */ + public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array + { + $occurrences = []; + $mutator = clone $start; + $mutator->startOfDay(); + $skipMod = $repetition->repetition_skip + 1; + Log::debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type)); + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); + + if ('daily' === $repetition->repetition_type) { + $occurrences = $this->getDailyInRange($mutator, $end, $skipMod); + } + if ('weekly' === $repetition->repetition_type) { + $occurrences = $this->getWeeklyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); + } + if ('monthly' === $repetition->repetition_type) { + $occurrences = $this->getMonthlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); + } + if ('ndom' === $repetition->repetition_type) { + $occurrences = $this->getNdomInRange($mutator, $end, $skipMod, $repetition->repetition_moment); + } + if ('yearly' === $repetition->repetition_type) { + $occurrences = $this->getYearlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); + } + + // filter out all the weekend days: + return $this->filterWeekends($repetition, $occurrences); + } + /** * @param RecurrenceTransaction $transaction * @@ -345,16 +384,6 @@ class RecurringRepository implements RecurringRepositoryInterface return $collector->getPaginatedGroups(); } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * @param Recurrence $recurrence * @@ -465,27 +494,6 @@ class RecurringRepository implements RecurringRepositoryInterface return $this->filterMaxDate($repeatUntil, $occurrences); } - /** - * @param Carbon|null $max - * @param array $occurrences - * - * @return array - */ - private function filterMaxDate(?Carbon $max, array $occurrences): array - { - if (null === $max) { - return $occurrences; - } - $filtered = []; - foreach ($occurrences as $date) { - if ($date->lte($max)) { - $filtered[] = $date; - } - } - - return $filtered; - } - /** * Parse the repetition in a string that is user readable. * @@ -562,6 +570,16 @@ class RecurringRepository implements RecurringRepositoryInterface return $search->take($limit)->get(['id', 'title', 'description']); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * @@ -605,45 +623,6 @@ class RecurringRepository implements RecurringRepositoryInterface return 0; } - /** - * Generate events in the date range. - * - * @param RecurrenceRepetition $repetition - * @param Carbon $start - * @param Carbon $end - * - * @return array - * - */ - public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array - { - $occurrences = []; - $mutator = clone $start; - $mutator->startOfDay(); - $skipMod = $repetition->repetition_skip + 1; - Log::debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type)); - Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); - - if ('daily' === $repetition->repetition_type) { - $occurrences = $this->getDailyInRange($mutator, $end, $skipMod); - } - if ('weekly' === $repetition->repetition_type) { - $occurrences = $this->getWeeklyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); - } - if ('monthly' === $repetition->repetition_type) { - $occurrences = $this->getMonthlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); - } - if ('ndom' === $repetition->repetition_type) { - $occurrences = $this->getNdomInRange($mutator, $end, $skipMod, $repetition->repetition_moment); - } - if ('yearly' === $repetition->repetition_type) { - $occurrences = $this->getYearlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment); - } - - // filter out all the weekend days: - return $this->filterWeekends($repetition, $occurrences); - } - /** * Update a recurring transaction. * @@ -660,4 +639,25 @@ class RecurringRepository implements RecurringRepositoryInterface return $service->update($recurrence, $data); } + + /** + * @param Carbon|null $max + * @param array $occurrences + * + * @return array + */ + private function filterMaxDate(?Carbon $max, array $occurrences): array + { + if (null === $max) { + return $occurrences; + } + $filtered = []; + foreach ($occurrences as $date) { + if ($date->lte($max)) { + $filtered[] = $date; + } + } + + return $filtered; + } } diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index badfc08c47..afbf6ef786 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -45,6 +45,14 @@ class RuleRepository implements RuleRepositoryInterface /** @var User */ private $user; + /** + * @return int + */ + public function count(): int + { + return $this->user->rules()->count(); + } + /** * @param Rule $rule * @@ -92,6 +100,16 @@ class RuleRepository implements RuleRepositoryInterface return $newRule; } + /** + * @param int $ruleId + * + * @return Rule|null + */ + public function find(int $ruleId): ?Rule + { + return $this->user->rules()->find($ruleId); + } + /** * Get all the users rules. * @@ -139,14 +157,6 @@ class RuleRepository implements RuleRepositoryInterface return $rule->ruleTriggers()->where('trigger_type', 'user_action')->first()->trigger_value; } - /** - * @return int - */ - public function count(): int - { - return $this->user->rules()->count(); - } - /** * @param Rule $rule * @@ -244,6 +254,43 @@ class RuleRepository implements RuleRepositoryInterface return $filtered; } + /** + * @inheritDoc + */ + public function maxOrder(RuleGroup $ruleGroup): int + { + return (int)$ruleGroup->rules()->max('order'); + } + + /** + * @inheritDoc + */ + public function moveRule(Rule $rule, RuleGroup $ruleGroup, int $order): Rule + { + if ($rule->rule_group_id !== $ruleGroup->id) { + $rule->rule_group_id = $ruleGroup->id; + } + $rule->save(); + $rule->refresh(); + $this->setOrder($rule, $order); + + return $rule; + } + + /** + * @param RuleGroup $ruleGroup + * + * @return bool + */ + public function resetRuleOrder(RuleGroup $ruleGroup): bool + { + $groupRepository = app(RuleGroupRepositoryInterface::class); + $groupRepository->setUser($ruleGroup->user); + $groupRepository->resetRuleOrder($ruleGroup); + + return true; + } + /** * @inheritDoc */ @@ -259,6 +306,52 @@ class RuleRepository implements RuleRepositoryInterface return $search->take($limit)->get(['id', 'title', 'description']); } + /** + * @inheritDoc + */ + public function setOrder(Rule $rule, int $newOrder): void + { + $oldOrder = (int)$rule->order; + $groupId = (int)$rule->rule_group_id; + $maxOrder = $this->maxOrder($rule->ruleGroup); + $newOrder = $newOrder > $maxOrder ? $maxOrder + 1 : $newOrder; + Log::debug(sprintf('New order will be %d', $newOrder)); + + if ($newOrder > $oldOrder) { + $this->user->rules() + ->where('rules.rule_group_id', $groupId) + ->where('rules.order', '<=', $newOrder) + ->where('rules.order', '>', $oldOrder) + ->where('rules.id', '!=', $rule->id) + ->decrement('rules.order'); + $rule->order = $newOrder; + Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder)); + $rule->save(); + + return; + } + + $this->user->rules() + ->where('rules.rule_group_id', $groupId) + ->where('rules.order', '>=', $newOrder) + ->where('rules.order', '<', $oldOrder) + ->where('rules.id', '!=', $rule->id) + ->increment('rules.order'); + $rule->order = $newOrder; + Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder)); + $rule->save(); + } + + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * @@ -314,135 +407,23 @@ class RuleRepository implements RuleRepositoryInterface } /** - * @param int $ruleId - * - * @return Rule|null - */ - public function find(int $ruleId): ?Rule - { - return $this->user->rules()->find($ruleId); - } - - /** - * @param string $moment * @param Rule $rule - */ - private function setRuleTrigger(string $moment, Rule $rule): void - { - /** @var RuleTrigger|null $trigger */ - $trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first(); - if (null !== $trigger) { - $trigger->trigger_value = $moment; - $trigger->save(); - - return; - } - $trigger = new RuleTrigger(); - $trigger->order = 0; - $trigger->trigger_type = 'user_action'; - $trigger->trigger_value = $moment; - $trigger->rule_id = $rule->id; - $trigger->active = true; - $trigger->stop_processing = false; - $trigger->save(); - } - - /** - * @param RuleGroup $ruleGroup + * @param array $values * - * @return bool + * @return RuleAction */ - public function resetRuleOrder(RuleGroup $ruleGroup): bool + public function storeAction(Rule $rule, array $values): RuleAction { - $groupRepository = app(RuleGroupRepositoryInterface::class); - $groupRepository->setUser($ruleGroup->user); - $groupRepository->resetRuleOrder($ruleGroup); + $ruleAction = new RuleAction(); + $ruleAction->rule()->associate($rule); + $ruleAction->order = $values['order']; + $ruleAction->active = $values['active']; + $ruleAction->stop_processing = $values['stop_processing']; + $ruleAction->action_type = $values['action']; + $ruleAction->action_value = $values['value'] ?? ''; + $ruleAction->save(); - return true; - } - - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @inheritDoc - */ - public function setOrder(Rule $rule, int $newOrder): void - { - $oldOrder = (int)$rule->order; - $groupId = (int)$rule->rule_group_id; - $maxOrder = $this->maxOrder($rule->ruleGroup); - $newOrder = $newOrder > $maxOrder ? $maxOrder + 1 : $newOrder; - Log::debug(sprintf('New order will be %d', $newOrder)); - - if ($newOrder > $oldOrder) { - $this->user->rules() - ->where('rules.rule_group_id', $groupId) - ->where('rules.order', '<=', $newOrder) - ->where('rules.order', '>', $oldOrder) - ->where('rules.id', '!=', $rule->id) - ->decrement('rules.order'); - $rule->order = $newOrder; - Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder)); - $rule->save(); - - return; - } - - $this->user->rules() - ->where('rules.rule_group_id', $groupId) - ->where('rules.order', '>=', $newOrder) - ->where('rules.order', '<', $oldOrder) - ->where('rules.id', '!=', $rule->id) - ->increment('rules.order'); - $rule->order = $newOrder; - Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder)); - $rule->save(); - } - - /** - * @inheritDoc - */ - public function maxOrder(RuleGroup $ruleGroup): int - { - return (int)$ruleGroup->rules()->max('order'); - } - - /** - * @param Rule $rule - * @param array $data - * - * @return void - */ - private function storeTriggers(Rule $rule, array $data): void - { - $order = 1; - foreach ($data['triggers'] as $trigger) { - $value = $trigger['value'] ?? ''; - $stopProcessing = $trigger['stop_processing'] ?? false; - $active = $trigger['active'] ?? true; - $type = $trigger['type']; - if (true === ($trigger['prohibited'] ?? false) && !str_starts_with($type, '-')) { - $type = sprintf('-%s', $type); - } - - $triggerValues = [ - 'action' => $type, - 'value' => $value, - 'stop_processing' => $stopProcessing, - 'order' => $order, - 'active' => $active, - ]; - $this->storeTrigger($rule, $triggerValues); - ++$order; - } + return $ruleAction; } /** @@ -465,51 +446,6 @@ class RuleRepository implements RuleRepositoryInterface return $ruleTrigger; } - /** - * @param Rule $rule - * @param array $data - * - * @return void - */ - private function storeActions(Rule $rule, array $data): void - { - $order = 1; - foreach ($data['actions'] as $action) { - $value = $action['value'] ?? ''; - $stopProcessing = $action['stop_processing'] ?? false; - $active = $action['active'] ?? true; - $actionValues = [ - 'action' => $action['type'], - 'value' => $value, - 'stop_processing' => $stopProcessing, - 'order' => $order, - 'active' => $active, - ]; - $this->storeAction($rule, $actionValues); - ++$order; - } - } - - /** - * @param Rule $rule - * @param array $values - * - * @return RuleAction - */ - public function storeAction(Rule $rule, array $values): RuleAction - { - $ruleAction = new RuleAction(); - $ruleAction->rule()->associate($rule); - $ruleAction->order = $values['order']; - $ruleAction->active = $values['active']; - $ruleAction->stop_processing = $values['stop_processing']; - $ruleAction->action_type = $values['action']; - $ruleAction->action_value = $values['value'] ?? ''; - $ruleAction->save(); - - return $ruleAction; - } - /** * @param Rule $rule * @param array $data @@ -569,17 +505,81 @@ class RuleRepository implements RuleRepositoryInterface } /** - * @inheritDoc + * @param string $moment + * @param Rule $rule */ - public function moveRule(Rule $rule, RuleGroup $ruleGroup, int $order): Rule + private function setRuleTrigger(string $moment, Rule $rule): void { - if ($rule->rule_group_id !== $ruleGroup->id) { - $rule->rule_group_id = $ruleGroup->id; - } - $rule->save(); - $rule->refresh(); - $this->setOrder($rule, $order); + /** @var RuleTrigger|null $trigger */ + $trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first(); + if (null !== $trigger) { + $trigger->trigger_value = $moment; + $trigger->save(); - return $rule; + return; + } + $trigger = new RuleTrigger(); + $trigger->order = 0; + $trigger->trigger_type = 'user_action'; + $trigger->trigger_value = $moment; + $trigger->rule_id = $rule->id; + $trigger->active = true; + $trigger->stop_processing = false; + $trigger->save(); + } + + /** + * @param Rule $rule + * @param array $data + * + * @return void + */ + private function storeActions(Rule $rule, array $data): void + { + $order = 1; + foreach ($data['actions'] as $action) { + $value = $action['value'] ?? ''; + $stopProcessing = $action['stop_processing'] ?? false; + $active = $action['active'] ?? true; + $actionValues = [ + 'action' => $action['type'], + 'value' => $value, + 'stop_processing' => $stopProcessing, + 'order' => $order, + 'active' => $active, + ]; + $this->storeAction($rule, $actionValues); + ++$order; + } + } + + /** + * @param Rule $rule + * @param array $data + * + * @return void + */ + private function storeTriggers(Rule $rule, array $data): void + { + $order = 1; + foreach ($data['triggers'] as $trigger) { + $value = $trigger['value'] ?? ''; + $stopProcessing = $trigger['stop_processing'] ?? false; + $active = $trigger['active'] ?? true; + $type = $trigger['type']; + if (true === ($trigger['prohibited'] ?? false) && !str_starts_with($type, '-')) { + $type = sprintf('-%s', $type); + } + + $triggerValues = [ + 'action' => $type, + 'value' => $value, + 'stop_processing' => $stopProcessing, + 'order' => $order, + 'active' => $active, + ]; + $this->storeTrigger($rule, $triggerValues); + ++$order; + } } } diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index 91d38404f1..8d9132f2c0 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -63,14 +63,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } } - /** - * @return Collection - */ - public function get(): Collection - { - return $this->user->ruleGroups()->orderBy('order', 'ASC')->get(); - } - /** * @return int */ @@ -109,108 +101,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return true; } - /** - * @return bool - */ - public function resetOrder(): bool - { - $set = $this->user - ->ruleGroups() - ->whereNull('deleted_at') - ->orderBy('order', 'ASC') - ->orderBy('title', 'DESC') - ->get(); - $count = 1; - /** @var RuleGroup $entry */ - foreach ($set as $entry) { - if ($entry->order !== $count) { - $entry->order = $count; - $entry->save(); - } - - // also update rules in group. - $this->resetRuleOrder($entry); - - ++$count; - } - - return true; - } - - /** - * @param RuleGroup $ruleGroup - * - * @return bool - */ - public function resetRuleOrder(RuleGroup $ruleGroup): bool - { - $set = $ruleGroup->rules() - ->orderBy('order', 'ASC') - ->orderBy('title', 'DESC') - ->orderBy('updated_at', 'DESC') - ->get(['rules.*']); - $count = 1; - /** @var Rule $entry */ - foreach ($set as $entry) { - if ((int)$entry->order !== $count) { - Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); - $entry->order = $count; - $entry->save(); - } - $this->resetRuleActionOrder($entry); - $this->resetRuleTriggerOrder($entry); - - ++$count; - } - - return true; - } - - /** - * @param Rule $rule - */ - private function resetRuleActionOrder(Rule $rule): void - { - $actions = $rule->ruleActions() - ->orderBy('order', 'ASC') - ->orderBy('active', 'DESC') - ->orderBy('action_type', 'ASC') - ->get(); - $index = 1; - /** @var RuleAction $action */ - foreach ($actions as $action) { - if ((int)$action->order !== $index) { - $action->order = $index; - $action->save(); - Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); - } - $index++; - } - } - - /** - * @param Rule $rule - */ - private function resetRuleTriggerOrder(Rule $rule): void - { - $triggers = $rule->ruleTriggers() - ->orderBy('order', 'ASC') - ->orderBy('active', 'DESC') - ->orderBy('trigger_type', 'ASC') - ->get(); - $index = 1; - /** @var RuleTrigger $trigger */ - foreach ($triggers as $trigger) { - $order = (int)$trigger->order; - if ($order !== $index) { - $trigger->order = $index; - $trigger->save(); - Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); - } - $index++; - } - } - /** * @inheritDoc */ @@ -244,6 +134,14 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $this->user->ruleGroups()->where('title', $title)->first(); } + /** + * @return Collection + */ + public function get(): Collection + { + return $this->user->ruleGroups()->orderBy('order', 'ASC')->get(); + } + /** * @return Collection */ @@ -428,6 +326,63 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return (int)$this->user->ruleGroups()->where('active', true)->max('order'); } + /** + * @return bool + */ + public function resetOrder(): bool + { + $set = $this->user + ->ruleGroups() + ->whereNull('deleted_at') + ->orderBy('order', 'ASC') + ->orderBy('title', 'DESC') + ->get(); + $count = 1; + /** @var RuleGroup $entry */ + foreach ($set as $entry) { + if ($entry->order !== $count) { + $entry->order = $count; + $entry->save(); + } + + // also update rules in group. + $this->resetRuleOrder($entry); + + ++$count; + } + + return true; + } + + /** + * @param RuleGroup $ruleGroup + * + * @return bool + */ + public function resetRuleOrder(RuleGroup $ruleGroup): bool + { + $set = $ruleGroup->rules() + ->orderBy('order', 'ASC') + ->orderBy('title', 'DESC') + ->orderBy('updated_at', 'DESC') + ->get(['rules.*']); + $count = 1; + /** @var Rule $entry */ + foreach ($set as $entry) { + if ((int)$entry->order !== $count) { + Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); + $entry->order = $count; + $entry->save(); + } + $this->resetRuleActionOrder($entry); + $this->resetRuleTriggerOrder($entry); + + ++$count; + } + + return true; + } + /** * @inheritDoc */ @@ -443,6 +398,32 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $search->take($limit)->get(['id', 'title', 'description']); } + /** + * @inheritDoc + */ + public function setOrder(RuleGroup $ruleGroup, int $newOrder): void + { + $oldOrder = (int)$ruleGroup->order; + + if ($newOrder > $oldOrder) { + $this->user->ruleGroups()->where('rule_groups.order', '<=', $newOrder)->where('rule_groups.order', '>', $oldOrder) + ->where('rule_groups.id', '!=', $ruleGroup->id) + ->decrement('order'); + $ruleGroup->order = $newOrder; + Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); + $ruleGroup->save(); + + return; + } + + $this->user->ruleGroups()->where('rule_groups.order', '>=', $newOrder)->where('rule_groups.order', '<', $oldOrder) + ->where('rule_groups.id', '!=', $ruleGroup->id) + ->increment('order'); + $ruleGroup->order = $newOrder; + Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); + $ruleGroup->save(); + } + /** * @param User|Authenticatable|null $user */ @@ -478,32 +459,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $newRuleGroup; } - /** - * @inheritDoc - */ - public function setOrder(RuleGroup $ruleGroup, int $newOrder): void - { - $oldOrder = (int)$ruleGroup->order; - - if ($newOrder > $oldOrder) { - $this->user->ruleGroups()->where('rule_groups.order', '<=', $newOrder)->where('rule_groups.order', '>', $oldOrder) - ->where('rule_groups.id', '!=', $ruleGroup->id) - ->decrement('order'); - $ruleGroup->order = $newOrder; - Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); - $ruleGroup->save(); - - return; - } - - $this->user->ruleGroups()->where('rule_groups.order', '>=', $newOrder)->where('rule_groups.order', '<', $oldOrder) - ->where('rule_groups.id', '!=', $ruleGroup->id) - ->increment('order'); - $ruleGroup->order = $newOrder; - Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); - $ruleGroup->save(); - } - /** * @param RuleGroup $ruleGroup * @param array $data @@ -532,4 +487,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $ruleGroup; } + + /** + * @param Rule $rule + */ + private function resetRuleActionOrder(Rule $rule): void + { + $actions = $rule->ruleActions() + ->orderBy('order', 'ASC') + ->orderBy('active', 'DESC') + ->orderBy('action_type', 'ASC') + ->get(); + $index = 1; + /** @var RuleAction $action */ + foreach ($actions as $action) { + if ((int)$action->order !== $index) { + $action->order = $index; + $action->save(); + Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); + } + $index++; + } + } + + /** + * @param Rule $rule + */ + private function resetRuleTriggerOrder(Rule $rule): void + { + $triggers = $rule->ruleTriggers() + ->orderBy('order', 'ASC') + ->orderBy('active', 'DESC') + ->orderBy('trigger_type', 'ASC') + ->get(); + $index = 1; + /** @var RuleTrigger $trigger */ + foreach ($triggers as $trigger) { + $order = (int)$trigger->order; + if ($order !== $index) { + $trigger->order = $index; + $trigger->save(); + Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); + } + $index++; + } + } } diff --git a/app/Repositories/Tag/OperationsRepository.php b/app/Repositories/Tag/OperationsRepository.php index 5e09c262c3..dad14b79ed 100644 --- a/app/Repositories/Tag/OperationsRepository.php +++ b/app/Repositories/Tag/OperationsRepository.php @@ -119,28 +119,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * @return Collection - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - private function getTags(): Collection - { - $repository = app(TagRepositoryInterface::class); - - return $repository->get(); - } - /** * This method returns a list of all the deposit transaction journals (as arrays) set in that period * which have the specified tag(s) set to them. It's grouped per currency, with as few details in the array @@ -219,6 +197,16 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * Sum of withdrawal journals in period for a set of tags, grouped per currency. Amounts are always negative. * @@ -250,4 +238,16 @@ class OperationsRepository implements OperationsRepositoryInterface { throw new FireflyException(sprintf('%s is not yet implemented.', __METHOD__)); } + + /** + * @return Collection + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function getTags(): Collection + { + $repository = app(TagRepositoryInterface::class); + + return $repository->get(); + } } diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index e63c512b18..2691cb8a31 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -83,14 +83,6 @@ class TagRepository implements TagRepositoryInterface } } - /** - * @return Collection - */ - public function get(): Collection - { - return $this->user->tags()->orderBy('tag', 'ASC')->get(); - } - /** * @param Tag $tag * @param Carbon $start @@ -109,16 +101,6 @@ class TagRepository implements TagRepositoryInterface return $collector->getExtractedJournals(); } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - /** * @param int $tagId * @@ -151,6 +133,14 @@ class TagRepository implements TagRepositoryInterface return $tag->transactionJournals()->orderBy('date', 'ASC')->first()?->date; } + /** + * @return Collection + */ + public function get(): Collection + { + return $this->user->tags()->orderBy('tag', 'ASC')->get(); + } + /** * @inheritDoc */ @@ -171,6 +161,15 @@ class TagRepository implements TagRepositoryInterface ); } + /** + * @inheritDoc + */ + public function getLocation(Tag $tag): ?Location + { + /** @var Location|null */ + return $tag->locations()->first(); + } + /** * @param int|null $year * @@ -291,6 +290,16 @@ class TagRepository implements TagRepositoryInterface return $tags->take($limit)->get('tags.*'); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * @@ -446,13 +455,4 @@ class TagRepository implements TagRepositoryInterface return $tag; } - - /** - * @inheritDoc - */ - public function getLocation(Tag $tag): ?Location - { - /** @var Location|null */ - return $tag->locations()->first(); - } } diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index 33308d004b..442b408a06 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -48,8 +48,8 @@ use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class TransactionGroupRepository @@ -69,18 +69,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $journal->attachments()->count(); } - /** - * Find a transaction group by its ID. - * - * @param int $groupId - * - * @return TransactionGroup|null - */ - public function find(int $groupId): ?TransactionGroup - { - return $this->user->transactionGroups()->find($groupId); - } - /** * @param TransactionGroup $group */ @@ -107,53 +95,15 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface } /** - * @param TransactionJournal $journal + * Find a transaction group by its ID. * - * @return array - */ - private function expandJournal(TransactionJournal $journal): array - { - $array = $journal->toArray(); - $array['transactions'] = []; - $array['meta'] = $journal->transactionJournalMeta->toArray(); - $array['tags'] = $journal->tags->toArray(); - $array['categories'] = $journal->categories->toArray(); - $array['budgets'] = $journal->budgets->toArray(); - $array['notes'] = $journal->notes->toArray(); - $array['locations'] = []; - $array['attachments'] = $journal->attachments->toArray(); - $array['links'] = []; - $array['piggy_bank_events'] = $journal->piggyBankEvents->toArray(); - - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $array['transactions'][] = $this->expandTransaction($transaction); - } - - return $array; - } - - /** - * @param Transaction $transaction + * @param int $groupId * - * @return array + * @return TransactionGroup|null */ - private function expandTransaction(Transaction $transaction): array + public function find(int $groupId): ?TransactionGroup { - $array = $transaction->toArray(); - $array['account'] = $transaction->account->toArray(); - $array['budgets'] = []; - $array['categories'] = []; - - foreach ($transaction->categories as $category) { - $array['categories'][] = $category->toArray(); - } - - foreach ($transaction->budgets as $budget) { - $array['budgets'][] = $budget->toArray(); - } - - return $array; + return $this->user->transactionGroups()->find($groupId); } /** @@ -189,36 +139,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $result; } - /** - * @param User|Authenticatable|null $user - */ - public function setUser(User|Authenticatable|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } - - /** - * Get the note text for a journal (by ID). - * - * @param int $journalId - * - * @return string|null - */ - public function getNoteText(int $journalId): ?string - { - /** @var Note|null $note */ - $note = Note::where('noteable_id', $journalId) - ->where('noteable_type', TransactionJournal::class) - ->first(); - if (null === $note) { - return null; - } - - return $note->text; - } - /** * Return all journal links for all journals in the group. * @@ -277,58 +197,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $return; } - /** - * @param TransactionJournal $journal - * - * @return string - */ - private function getFormattedAmount(TransactionJournal $journal): string - { - /** @var Transaction $transaction */ - $transaction = $journal->transactions->first(); - $currency = $transaction->transactionCurrency; - $type = $journal->transactionType->type; - $amount = app('steam')->positive($transaction->amount); - $return = ''; - if (TransactionType::WITHDRAWAL === $type) { - $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); - } - if (TransactionType::WITHDRAWAL !== $type) { - $return = app('amount')->formatAnything($currency, $amount); - } - - return $return; - } - - /** - * @param TransactionJournal $journal - * - * @return string - */ - private function getFormattedForeignAmount(TransactionJournal $journal): string - { - /** @var Transaction $transaction */ - $transaction = $journal->transactions->first(); - if (null === $transaction->foreign_amount || '' === $transaction->foreign_amount) { - return ''; - } - if (0 === bccomp('0', $transaction->foreign_amount)) { - return ''; - } - $currency = $transaction->foreignCurrency; - $type = $journal->transactionType->type; - $amount = app('steam')->positive($transaction->foreign_amount); - $return = ''; - if (TransactionType::WITHDRAWAL === $type) { - $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); - } - if (TransactionType::WITHDRAWAL !== $type) { - $return = app('amount')->formatAnything($currency, $amount); - } - - return $return; - } - /** * @inheritDoc */ @@ -389,6 +257,26 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return new NullArrayObject($return); } + /** + * Get the note text for a journal (by ID). + * + * @param int $journalId + * + * @return string|null + */ + public function getNoteText(int $journalId): ?string + { + /** @var Note|null $note */ + $note = Note::where('noteable_id', $journalId) + ->where('noteable_type', TransactionJournal::class) + ->first(); + if (null === $note) { + return null; + } + + return $note->text; + } + /** * Return all piggy bank events for all journals in the group. * @@ -464,6 +352,16 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $result->pluck('tag')->toArray(); } + /** + * @param User|Authenticatable|null $user + */ + public function setUser(User|Authenticatable|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @param array $data * @@ -506,4 +404,106 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $service->update($transactionGroup, $data); } + + /** + * @param TransactionJournal $journal + * + * @return array + */ + private function expandJournal(TransactionJournal $journal): array + { + $array = $journal->toArray(); + $array['transactions'] = []; + $array['meta'] = $journal->transactionJournalMeta->toArray(); + $array['tags'] = $journal->tags->toArray(); + $array['categories'] = $journal->categories->toArray(); + $array['budgets'] = $journal->budgets->toArray(); + $array['notes'] = $journal->notes->toArray(); + $array['locations'] = []; + $array['attachments'] = $journal->attachments->toArray(); + $array['links'] = []; + $array['piggy_bank_events'] = $journal->piggyBankEvents->toArray(); + + /** @var Transaction $transaction */ + foreach ($journal->transactions as $transaction) { + $array['transactions'][] = $this->expandTransaction($transaction); + } + + return $array; + } + + /** + * @param Transaction $transaction + * + * @return array + */ + private function expandTransaction(Transaction $transaction): array + { + $array = $transaction->toArray(); + $array['account'] = $transaction->account->toArray(); + $array['budgets'] = []; + $array['categories'] = []; + + foreach ($transaction->categories as $category) { + $array['categories'][] = $category->toArray(); + } + + foreach ($transaction->budgets as $budget) { + $array['budgets'][] = $budget->toArray(); + } + + return $array; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + private function getFormattedAmount(TransactionJournal $journal): string + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + $currency = $transaction->transactionCurrency; + $type = $journal->transactionType->type; + $amount = app('steam')->positive($transaction->amount); + $return = ''; + if (TransactionType::WITHDRAWAL === $type) { + $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); + } + if (TransactionType::WITHDRAWAL !== $type) { + $return = app('amount')->formatAnything($currency, $amount); + } + + return $return; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + private function getFormattedForeignAmount(TransactionJournal $journal): string + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + if (null === $transaction->foreign_amount || '' === $transaction->foreign_amount) { + return ''; + } + if (0 === bccomp('0', $transaction->foreign_amount)) { + return ''; + } + $currency = $transaction->foreignCurrency; + $type = $journal->transactionType->type; + $amount = app('steam')->positive($transaction->foreign_amount); + $return = ''; + if (TransactionType::WITHDRAWAL === $type) { + $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); + } + if (TransactionType::WITHDRAWAL !== $type) { + $return = app('amount')->formatAnything($currency, $amount); + } + + return $return; + } } diff --git a/app/Repositories/TransactionType/TransactionTypeRepository.php b/app/Repositories/TransactionType/TransactionTypeRepository.php index 3b2aacce20..e7b33a689d 100644 --- a/app/Repositories/TransactionType/TransactionTypeRepository.php +++ b/app/Repositories/TransactionType/TransactionTypeRepository.php @@ -32,6 +32,18 @@ use Illuminate\Support\Facades\Log; */ class TransactionTypeRepository implements TransactionTypeRepositoryInterface { + /** + * @param string $type + * + * @return TransactionType|null + */ + public function findByType(string $type): ?TransactionType + { + $search = ucfirst($type); + + return TransactionType::whereType($search)->first(); + } + /** * @param TransactionType|null $type * @param string|null $typeString @@ -56,18 +68,6 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface return $search; } - /** - * @param string $type - * - * @return TransactionType|null - */ - public function findByType(string $type): ?TransactionType - { - $search = ucfirst($type); - - return TransactionType::whereType($search)->first(); - } - /** * @param string $query * @param int $limit diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index cc7972ba5b..7edcb9b254 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -43,6 +43,39 @@ use Str; */ class UserRepository implements UserRepositoryInterface { + /** + * @return Collection + */ + public function all(): Collection + { + return User::orderBy('id', 'DESC')->get(['users.*']); + } + + /** + * @param User $user + * @param string $role + * + * @return bool + */ + public function attachRole(User $user, string $role): bool + { + $roleObject = Role::where('name', $role)->first(); + if (null === $roleObject) { + Log::error(sprintf('Could not find role "%s" in attachRole()', $role)); + + return false; + } + + try { + $user->roles()->attach($roleObject); + } catch (QueryException $e) { + // don't care + Log::error(sprintf('Query exception when giving user a role: %s', $e->getMessage())); + } + + return true; + } + /** * This updates the users email address and records some things so it can be confirmed or undone later. * The user is blocked until the change is confirmed. @@ -107,6 +140,14 @@ class UserRepository implements UserRepositoryInterface return true; } + /** + * @return int + */ + public function count(): int + { + return $this->all()->count(); + } + /** * @param string $name * @param string $displayName @@ -119,6 +160,22 @@ class UserRepository implements UserRepositoryInterface return Role::create(['name' => $name, 'display_name' => $displayName, 'description' => $description]); } + /** + * @inheritDoc + */ + public function deleteEmptyGroups(): void + { + $groups = UserGroup::get(); + /** @var UserGroup $group */ + foreach ($groups as $group) { + $count = $group->groupMemberships()->count(); + if (0 === $count) { + Log::info(sprintf('Deleted empty group #%d ("%s")', $group->id, $group->title)); + $group->delete(); + } + } + } + /** * @inheritDoc */ @@ -146,35 +203,13 @@ class UserRepository implements UserRepositoryInterface } /** - * @inheritDoc + * @param int $userId + * + * @return User|null */ - public function deleteEmptyGroups(): void + public function find(int $userId): ?User { - $groups = UserGroup::get(); - /** @var UserGroup $group */ - foreach ($groups as $group) { - $count = $group->groupMemberships()->count(); - if (0 === $count) { - Log::info(sprintf('Deleted empty group #%d ("%s")', $group->id, $group->title)); - $group->delete(); - } - } - } - - /** - * @return int - */ - public function count(): int - { - return $this->all()->count(); - } - - /** - * @return Collection - */ - public function all(): Collection - { - return User::orderBy('id', 'DESC')->get(['users.*']); + return User::find($userId); } /** @@ -205,6 +240,16 @@ class UserRepository implements UserRepositoryInterface return InvitedUser::with('user')->get(); } + /** + * @param string $role + * + * @return Role|null + */ + public function getRole(string $role): ?Role + { + return Role::where('name', $role)->first(); + } + /** * @param User $user * @@ -242,16 +287,6 @@ class UserRepository implements UserRepositoryInterface return $roles; } - /** - * @param int $userId - * - * @return User|null - */ - public function find(int $userId): ?User - { - return User::find($userId); - } - /** * Return basic user information. * @@ -340,6 +375,21 @@ class UserRepository implements UserRepositoryInterface } } + /** + * Remove any role the user has. + * + * @param User $user + * @param string $role + */ + public function removeRole(User $user, string $role): void + { + $roleObj = $this->getRole($role); + if (null === $roleObj) { + return; + } + $user->roles()->detach($roleObj->id); + } + /** * Set MFA code. * @@ -375,31 +425,6 @@ class UserRepository implements UserRepositoryInterface return $user; } - /** - * @param User $user - * @param string $role - * - * @return bool - */ - public function attachRole(User $user, string $role): bool - { - $roleObject = Role::where('name', $role)->first(); - if (null === $roleObject) { - Log::error(sprintf('Could not find role "%s" in attachRole()', $role)); - - return false; - } - - try { - $user->roles()->attach($roleObject); - } catch (QueryException $e) { - // don't care - Log::error(sprintf('Query exception when giving user a role: %s', $e->getMessage())); - } - - return true; - } - /** * @param User $user */ @@ -466,31 +491,6 @@ class UserRepository implements UserRepositoryInterface return true; } - /** - * Remove any role the user has. - * - * @param User $user - * @param string $role - */ - public function removeRole(User $user, string $role): void - { - $roleObj = $this->getRole($role); - if (null === $roleObj) { - return; - } - $user->roles()->detach($roleObj->id); - } - - /** - * @param string $role - * - * @return Role|null - */ - public function getRole(string $role): ?Role - { - return Role::where('name', $role)->first(); - } - /** * @inheritDoc */ diff --git a/app/Rules/BelongsUser.php b/app/Rules/BelongsUser.php index f951b92d06..02edad3149 100644 --- a/app/Rules/BelongsUser.php +++ b/app/Rules/BelongsUser.php @@ -92,50 +92,6 @@ class BelongsUser implements Rule }; } - /** - * @param string $attribute - * - * @return string - */ - private function parseAttribute(string $attribute): string - { - $parts = explode('.', $attribute); - if (1 === count($parts)) { - return $attribute; - } - if (3 === count($parts)) { - return $parts[2]; - } - - return $attribute; - } - - /** - * @param int $value - * - * @return bool - */ - private function validatePiggyBankId(int $value): bool - { - $count = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') - ->where('piggy_banks.id', '=', $value) - ->where('accounts.user_id', '=', auth()->user()->id)->count(); - - return 1 === $count; - } - - /** - * @param string $value - * - * @return bool - */ - private function validatePiggyBankName(string $value): bool - { - $count = $this->countField(PiggyBank::class, 'name', $value); - - return 1 === $count; - } - /** * @param string $class * @param string $field @@ -169,6 +125,40 @@ class BelongsUser implements Rule return $count; } + /** + * @param string $attribute + * + * @return string + */ + private function parseAttribute(string $attribute): string + { + $parts = explode('.', $attribute); + if (1 === count($parts)) { + return $attribute; + } + if (3 === count($parts)) { + return $parts[2]; + } + + return $attribute; + } + + /** + * @param int $value + * + * @return bool + */ + private function validateAccountId(int $value): bool + { + if (0 === $value) { + // its ok to submit 0. other checks will fail. + return true; + } + $count = Account::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); + + return 1 === $count; + } + /** * @param int $value * @@ -184,21 +174,6 @@ class BelongsUser implements Rule return 1 === $count; } - /** - * @param int $value - * - * @return bool - */ - private function validateJournalId(int $value): bool - { - if (0 === $value) { - return true; - } - $count = TransactionJournal::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); - - return 1 === $count; - } - /** * @param string $value * @@ -227,18 +202,6 @@ class BelongsUser implements Rule return 1 === $count; } - /** - * @param int $value - * - * @return bool - */ - private function validateCategoryId(int $value): bool - { - $count = Category::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); - - return 1 === $count; - } - /** * @param string $value * @@ -256,13 +219,50 @@ class BelongsUser implements Rule * * @return bool */ - private function validateAccountId(int $value): bool + private function validateCategoryId(int $value): bool + { + $count = Category::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); + + return 1 === $count; + } + + /** + * @param int $value + * + * @return bool + */ + private function validateJournalId(int $value): bool { if (0 === $value) { - // its ok to submit 0. other checks will fail. return true; } - $count = Account::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); + $count = TransactionJournal::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count(); + + return 1 === $count; + } + + /** + * @param int $value + * + * @return bool + */ + private function validatePiggyBankId(int $value): bool + { + $count = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') + ->where('piggy_banks.id', '=', $value) + ->where('accounts.user_id', '=', auth()->user()->id)->count(); + + return 1 === $count; + } + + /** + * @param string $value + * + * @return bool + */ + private function validatePiggyBankName(string $value): bool + { + $count = $this->countField(PiggyBank::class, 'name', $value); return 1 === $count; } diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php index 5b4cf7af9d..0628d959da 100644 --- a/app/Rules/IsValidAttachmentModel.php +++ b/app/Rules/IsValidAttachmentModel.php @@ -62,20 +62,6 @@ class IsValidAttachmentModel implements Rule $this->model = $model; } - /** - * @param string $model - * - * @return string - */ - private function normalizeModel(string $model): string - { - $search = ['FireflyIII\Models\\']; - $replace = ''; - $model = str_replace($search, $replace, $model); - - return sprintf('FireflyIII\Models\%s', $model); - } - /** * Get the validation error message. * @@ -119,6 +105,20 @@ class IsValidAttachmentModel implements Rule return $this->$method((int)$value); } + /** + * @param string $model + * + * @return string + */ + private function normalizeModel(string $model): string + { + $search = ['FireflyIII\Models\\']; + $replace = ''; + $model = str_replace($search, $replace, $model); + + return sprintf('FireflyIII\Models\%s', $model); + } + /** * @param int $value * diff --git a/app/Rules/UniqueAccountNumber.php b/app/Rules/UniqueAccountNumber.php index 925fa23f4c..8124146bea 100644 --- a/app/Rules/UniqueAccountNumber.php +++ b/app/Rules/UniqueAccountNumber.php @@ -114,6 +114,28 @@ class UniqueAccountNumber implements Rule return true; } + /** + * @param string $type + * @param string $accountNumber + * + * @return int + */ + private function countHits(string $type, string $accountNumber): int + { + $query = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->where('accounts.user_id', auth()->user()->id) + ->where('account_types.type', $type) + ->where('account_meta.name', '=', 'account_number') + ->where('account_meta.data', json_encode($accountNumber)); + + if (null !== $this->account) { + $query->where('accounts.id', '!=', $this->account->id); + } + + return $query->count(); + } + /** * @return array * @@ -139,26 +161,4 @@ class UniqueAccountNumber implements Rule return $maxCounts; } - - /** - * @param string $type - * @param string $accountNumber - * - * @return int - */ - private function countHits(string $type, string $accountNumber): int - { - $query = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where('accounts.user_id', auth()->user()->id) - ->where('account_types.type', $type) - ->where('account_meta.name', '=', 'account_number') - ->where('account_meta.data', json_encode($accountNumber)); - - if (null !== $this->account) { - $query->where('accounts.id', '!=', $this->account->id); - } - - return $query->count(); - } } diff --git a/app/Rules/UniqueIban.php b/app/Rules/UniqueIban.php index 3cba5d930c..ed3c5879e7 100644 --- a/app/Rules/UniqueIban.php +++ b/app/Rules/UniqueIban.php @@ -26,7 +26,6 @@ namespace FireflyIII\Rules; use Closure; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Log; @@ -120,30 +119,13 @@ class UniqueIban implements ValidationRule } /** - * @return array - * + * @inheritDoc */ - private function getMaxOccurrences(): array + public function validate(string $attribute, mixed $value, Closure $fail): void { - $maxCounts = [ - AccountType::ASSET => 0, - AccountType::EXPENSE => 0, - AccountType::REVENUE => 0, - 'liabilities' => 0, - ]; - - if (in_array('expense', $this->expectedTypes, true) || in_array(AccountType::EXPENSE, $this->expectedTypes, true)) { - // IBAN should be unique amongst expense and asset accounts. - // may appear once in revenue accounts - $maxCounts[AccountType::REVENUE] = 1; + if (!$this->passes($attribute, $value)) { + $fail((string)trans('validation.unique_iban_for_user')); } - if (in_array('revenue', $this->expectedTypes, true) || in_array(AccountType::REVENUE, $this->expectedTypes, true)) { - // IBAN should be unique amongst revenue and asset accounts. - // may appear once in expense accounts - $maxCounts[AccountType::EXPENSE] = 1; - } - - return $maxCounts; } /** @@ -173,12 +155,29 @@ class UniqueIban implements ValidationRule } /** - * @inheritDoc + * @return array + * */ - public function validate(string $attribute, mixed $value, Closure $fail): void + private function getMaxOccurrences(): array { - if (!$this->passes($attribute, $value)) { - $fail((string)trans('validation.unique_iban_for_user')); + $maxCounts = [ + AccountType::ASSET => 0, + AccountType::EXPENSE => 0, + AccountType::REVENUE => 0, + 'liabilities' => 0, + ]; + + if (in_array('expense', $this->expectedTypes, true) || in_array(AccountType::EXPENSE, $this->expectedTypes, true)) { + // IBAN should be unique amongst expense and asset accounts. + // may appear once in revenue accounts + $maxCounts[AccountType::REVENUE] = 1; } + if (in_array('revenue', $this->expectedTypes, true) || in_array(AccountType::REVENUE, $this->expectedTypes, true)) { + // IBAN should be unique amongst revenue and asset accounts. + // may appear once in expense accounts + $maxCounts[AccountType::EXPENSE] = 1; + } + + return $maxCounts; } } diff --git a/app/Rules/ValidRecurrenceRepetitionValue.php b/app/Rules/ValidRecurrenceRepetitionValue.php index 247b5d715e..0d696bb5fe 100644 --- a/app/Rules/ValidRecurrenceRepetitionValue.php +++ b/app/Rules/ValidRecurrenceRepetitionValue.php @@ -25,8 +25,8 @@ namespace FireflyIII\Rules; use Carbon\Carbon; use Illuminate\Contracts\Validation\Rule; -use InvalidArgumentException; use Illuminate\Support\Facades\Log; +use InvalidArgumentException; /** * Class ValidRecurrenceRepetitionValue diff --git a/app/Services/FireflyIIIOrg/Update/UpdateRequest.php b/app/Services/FireflyIIIOrg/Update/UpdateRequest.php index c0dfc41884..f6359ed824 100644 --- a/app/Services/FireflyIIIOrg/Update/UpdateRequest.php +++ b/app/Services/FireflyIIIOrg/Update/UpdateRequest.php @@ -28,8 +28,8 @@ use Carbon\Carbon; use FireflyIII\Events\NewVersionAvailable; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class UpdateRequest diff --git a/app/Services/Internal/Destroy/AccountDestroyService.php b/app/Services/Internal/Destroy/AccountDestroyService.php index 683077ee06..49b4b17902 100644 --- a/app/Services/Internal/Destroy/AccountDestroyService.php +++ b/app/Services/Internal/Destroy/AccountDestroyService.php @@ -70,6 +70,59 @@ class AccountDestroyService $account->delete(); } + /** + * @param Account $account + * @param Account $moveTo + */ + public function moveTransactions(Account $account, Account $moveTo): void + { + Log::debug(sprintf('Move from account #%d to #%d', $account->id, $moveTo->id)); + DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); + + $collection = Transaction::groupBy('transaction_journal_id', 'account_id') + ->where('account_id', $moveTo->id) + ->get(['transaction_journal_id', 'account_id', DB::raw('count(*) as the_count')]); + if (0 === $collection->count()) { + return; + } + + /** @var JournalDestroyService $service */ + $service = app(JournalDestroyService::class); + $user = $account->user; + /** @var stdClass $row */ + foreach ($collection as $row) { + if ((int)$row->the_count > 1) { + $journalId = (int)$row->transaction_journal_id; + $journal = $user->transactionJournals()->find($journalId); + if (null !== $journal) { + Log::debug(sprintf('Deleted journal #%d because it has the same source as destination.', $journal->id)); + $service->destroy($journal); + } + } + } + } + + /** + * @param Account $account + */ + private function destroyJournals(Account $account): void + { + /** @var JournalDestroyService $service */ + $service = app(JournalDestroyService::class); + + Log::debug('Now trigger account delete response #'.$account->id); + /** @var Transaction $transaction */ + foreach ($account->transactions()->get() as $transaction) { + Log::debug('Now at transaction #'.$transaction->id); + /** @var TransactionJournal $journal */ + $journal = $transaction->transactionJournal()->first(); + if (null !== $journal) { + Log::debug('Call for deletion of journal #'.$journal->id); + $service->destroy($journal); + } + } + } + /** * @param Account $account */ @@ -106,69 +159,6 @@ class AccountDestroyService } } - /** - * @param Account $account - * @param Account $moveTo - */ - public function moveTransactions(Account $account, Account $moveTo): void - { - Log::debug(sprintf('Move from account #%d to #%d', $account->id, $moveTo->id)); - DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); - - $collection = Transaction::groupBy('transaction_journal_id', 'account_id') - ->where('account_id', $moveTo->id) - ->get(['transaction_journal_id', 'account_id', DB::raw('count(*) as the_count')]); - if (0 === $collection->count()) { - return; - } - - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - $user = $account->user; - /** @var stdClass $row */ - foreach ($collection as $row) { - if ((int)$row->the_count > 1) { - $journalId = (int)$row->transaction_journal_id; - $journal = $user->transactionJournals()->find($journalId); - if (null !== $journal) { - Log::debug(sprintf('Deleted journal #%d because it has the same source as destination.', $journal->id)); - $service->destroy($journal); - } - } - } - } - - /** - * @param Account $account - * @param Account $moveTo - */ - private function updateRecurrences(Account $account, Account $moveTo): void - { - DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); - DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id' => $moveTo->id]); - } - - /** - * @param Account $account - */ - private function destroyJournals(Account $account): void - { - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - - Log::debug('Now trigger account delete response #'.$account->id); - /** @var Transaction $transaction */ - foreach ($account->transactions()->get() as $transaction) { - Log::debug('Now at transaction #'.$transaction->id); - /** @var TransactionJournal $journal */ - $journal = $transaction->transactionJournal()->first(); - if (null !== $journal) { - Log::debug('Call for deletion of journal #'.$journal->id); - $service->destroy($journal); - } - } - } - /** * @param Account $account */ @@ -187,4 +177,14 @@ class AccountDestroyService $destroyService->destroyById((int)$recurrenceId); } } + + /** + * @param Account $account + * @param Account $moveTo + */ + private function updateRecurrences(Account $account, Account $moveTo): void + { + DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); + DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id' => $moveTo->id]); + } } diff --git a/app/Services/Internal/Destroy/BudgetDestroyService.php b/app/Services/Internal/Destroy/BudgetDestroyService.php index c265a897d8..187319f62f 100644 --- a/app/Services/Internal/Destroy/BudgetDestroyService.php +++ b/app/Services/Internal/Destroy/BudgetDestroyService.php @@ -52,7 +52,7 @@ class BudgetDestroyService DB::table('budget_transaction')->where('budget_id', (int)$budget->id)->delete(); // also delete all budget limits - foreach($budget->budgetlimits()->get() as $limit) { + foreach ($budget->budgetlimits()->get() as $limit) { $limit->delete(); } } diff --git a/app/Services/Internal/Destroy/RecurrenceDestroyService.php b/app/Services/Internal/Destroy/RecurrenceDestroyService.php index 7e48391be0..a91a8bef43 100644 --- a/app/Services/Internal/Destroy/RecurrenceDestroyService.php +++ b/app/Services/Internal/Destroy/RecurrenceDestroyService.php @@ -31,20 +31,6 @@ use FireflyIII\Models\RecurrenceTransaction; */ class RecurrenceDestroyService { - /** - * Delete recurrence by ID - * - * @param int $recurrenceId - */ - public function destroyById(int $recurrenceId): void - { - $recurrence = Recurrence::find($recurrenceId); - if (null === $recurrence) { - return; - } - $this->destroy($recurrence); - } - /** * Delete recurrence. * @@ -67,4 +53,18 @@ class RecurrenceDestroyService // delete recurrence $recurrence->delete(); } + + /** + * Delete recurrence by ID + * + * @param int $recurrenceId + */ + public function destroyById(int $recurrenceId): void + { + $recurrence = Recurrence::find($recurrenceId); + if (null === $recurrence) { + return; + } + $this->destroy($recurrence); + } } diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index d29ceac998..8e7dc1b9f6 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -38,8 +38,8 @@ use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Validator; /** @@ -154,7 +154,7 @@ trait AccountServiceTrait if (is_bool($data[$field]) && true === $data[$field]) { $data[$field] = 1; } - if($data[$field] instanceof Carbon) { + if ($data[$field] instanceof Carbon) { $data[$field] = $data[$field]->toAtomString(); } @@ -214,6 +214,97 @@ trait AccountServiceTrait return false; } + /** + * @param Account $account + * @param string $openingBalance + * @param Carbon $openingBalanceDate + * + * @return TransactionGroup + * @throws FireflyException + * @throws JsonException + */ + protected function createCreditTransaction(Account $account, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup + { + Log::debug('Now going to create an createCreditTransaction.'); + + if (0 === bccomp($openingBalance, '0')) { + Log::debug('Amount is zero, so will not make an liability credit group.'); + throw new FireflyException('Amount for new liability credit was unexpectedly 0.'); + } + + $language = app('preferences')->getForUser($account->user, 'language', 'en_US')->data; + + // set source and/or destination based on whether the amount is positive or negative. + // first, assume the amount is positive and go from there: + // if amount is positive ("I am owed this debt"), source is special account, destination is the liability. + $sourceId = null; + $sourceName = trans('firefly.liability_credit_description', ['account' => $account->name], $language); + $destId = $account->id; + $destName = null; + if (-1 === bccomp($openingBalance, '0')) { + // amount is negative, reverse it + $sourceId = $account->id; + $sourceName = null; + $destId = null; + $destName = trans('firefly.liability_credit_description', ['account' => $account->name], $language); + } + + // amount must be positive for the transaction to work. + $amount = app('steam')->positive($openingBalance); + + // get or grab currency: + $currency = $this->accountRepository->getAccountCurrency($account); + if (null === $currency) { + $currency = app('default')->getDefaultCurrencyByUser($account->user); + } + + // submit to factory: + $submission = [ + 'group_title' => null, + 'user' => $account->user_id, + 'transactions' => [ + [ + 'type' => 'Liability credit', + 'date' => $openingBalanceDate, + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'destination_id' => $destId, + 'destination_name' => $destName, + 'user' => $account->user_id, + 'currency_id' => $currency->id, + 'order' => 0, + 'amount' => $amount, + 'foreign_amount' => null, + 'description' => trans('firefly.liability_credit_description', ['account' => $account->name]), + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'reconciled' => false, + 'notes' => null, + 'tags' => [], + ], + ], + ]; + Log::debug('Going for submission in createCreditTransaction', $submission); + + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + $factory->setUser($account->user); + + try { + $group = $factory->create($submission); + } catch (DuplicateTransactionException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException($e->getMessage(), 0, $e); + } + + return $group; + } + /** * @param Account $account * @param array $data @@ -307,366 +398,6 @@ trait AccountServiceTrait return $group; } - /** - * Delete TransactionGroup with liability credit in it. - * - * @param Account $account - */ - protected function deleteCreditTransaction(Account $account): void - { - Log::debug(sprintf('deleteCreditTransaction() for account #%d', $account->id)); - $creditGroup = $this->getCreditTransaction($account); - - if (null !== $creditGroup) { - Log::debug('Credit journal found, delete journal.'); - /** @var TransactionGroupDestroyService $service */ - $service = app(TransactionGroupDestroyService::class); - $service->destroy($creditGroup); - } - } - - /** - * Returns the credit transaction group, or NULL if it does not exist. - * - * @param Account $account - * - * @return TransactionGroup|null - */ - protected function getCreditTransaction(Account $account): ?TransactionGroup - { - Log::debug(sprintf('Now at %s', __METHOD__)); - - return $this->accountRepository->getCreditTransactionGroup($account); - } - - /** - * Delete TransactionGroup with opening balance in it. - * - * @param Account $account - */ - protected function deleteOBGroup(Account $account): void - { - Log::debug(sprintf('deleteOB() for account #%d', $account->id)); - $openingBalanceGroup = $this->getOBGroup($account); - - // opening balance data? update it! - if (null !== $openingBalanceGroup) { - Log::debug('Opening balance journal found, delete journal.'); - /** @var TransactionGroupDestroyService $service */ - $service = app(TransactionGroupDestroyService::class); - $service->destroy($openingBalanceGroup); - } - } - - /** - * Returns the opening balance group, or NULL if it does not exist. - * - * @param Account $account - * - * @return TransactionGroup|null - */ - protected function getOBGroup(Account $account): ?TransactionGroup - { - return $this->accountRepository->getOpeningBalanceGroup($account); - } - - /** - * @param int $currencyId - * @param string $currencyCode - * - * @return TransactionCurrency - * @throws FireflyException - * @throws JsonException - */ - protected function getCurrency(int $currencyId, string $currencyCode): TransactionCurrency - { - // find currency, or use default currency instead. - /** @var TransactionCurrencyFactory $factory */ - $factory = app(TransactionCurrencyFactory::class); - /** @var TransactionCurrency|null $currency */ - $currency = $factory->find($currencyId, $currencyCode); - - if (null === $currency) { - // use default currency: - $currency = app('amount')->getDefaultCurrencyByUser($this->user); - } - $currency->enabled = true; - $currency->save(); - - return $currency; - } - - /** - * Create the opposing "credit liability" transaction for credit liabilities. - * - * - * @throws FireflyException - */ - protected function updateCreditTransaction(Account $account, string $direction, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup - { - Log::debug(sprintf('Now in %s', __METHOD__)); - - if (0 === bccomp($openingBalance, '0')) { - Log::debug('Amount is zero, so will not update liability credit/debit group.'); - throw new FireflyException('Amount for update liability credit/debit was unexpectedly 0.'); - } - // if direction is "debit" (i owe this debt), amount is negative. - // which means the liability will have a negative balance which the user must fill. - $openingBalance = app('steam')->negative($openingBalance); - - // if direction is "credit" (I am owed this debt), amount is positive. - // which means the liability will have a positive balance which is drained when its paid back into any asset. - if ('credit' === $direction) { - $openingBalance = app('steam')->positive($openingBalance); - } - - // create if not exists: - $clGroup = $this->getCreditTransaction($account); - if (null === $clGroup) { - return $this->createCreditTransaction($account, $openingBalance, $openingBalanceDate); - } - // if exists, update: - $currency = $this->accountRepository->getAccountCurrency($account); - if (null === $currency) { - $currency = app('default')->getDefaultCurrencyByUser($account->user); - } - - // simply grab the first journal and change it: - $journal = $this->getObJournal($clGroup); - $clTransaction = $this->getOBTransaction($journal, $account); - $accountTransaction = $this->getNotOBTransaction($journal, $account); - $journal->date = $openingBalanceDate; - $journal->transactionCurrency()->associate($currency); - - // account always gains money: - $accountTransaction->amount = app('steam')->positive($openingBalance); - $accountTransaction->transaction_currency_id = $currency->id; - - // CL account always loses money: - $clTransaction->amount = app('steam')->negative($openingBalance); - $clTransaction->transaction_currency_id = $currency->id; - // save both - $accountTransaction->save(); - $clTransaction->save(); - $journal->save(); - $clGroup->refresh(); - - return $clGroup; - } - - /** - * @param Account $account - * @param string $openingBalance - * @param Carbon $openingBalanceDate - * - * @return TransactionGroup - * @throws FireflyException - * @throws JsonException - */ - protected function createCreditTransaction(Account $account, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup - { - Log::debug('Now going to create an createCreditTransaction.'); - - if (0 === bccomp($openingBalance, '0')) { - Log::debug('Amount is zero, so will not make an liability credit group.'); - throw new FireflyException('Amount for new liability credit was unexpectedly 0.'); - } - - $language = app('preferences')->getForUser($account->user, 'language', 'en_US')->data; - - // set source and/or destination based on whether the amount is positive or negative. - // first, assume the amount is positive and go from there: - // if amount is positive ("I am owed this debt"), source is special account, destination is the liability. - $sourceId = null; - $sourceName = trans('firefly.liability_credit_description', ['account' => $account->name], $language); - $destId = $account->id; - $destName = null; - if (-1 === bccomp($openingBalance, '0')) { - // amount is negative, reverse it - $sourceId = $account->id; - $sourceName = null; - $destId = null; - $destName = trans('firefly.liability_credit_description', ['account' => $account->name], $language); - } - - // amount must be positive for the transaction to work. - $amount = app('steam')->positive($openingBalance); - - // get or grab currency: - $currency = $this->accountRepository->getAccountCurrency($account); - if (null === $currency) { - $currency = app('default')->getDefaultCurrencyByUser($account->user); - } - - // submit to factory: - $submission = [ - 'group_title' => null, - 'user' => $account->user_id, - 'transactions' => [ - [ - 'type' => 'Liability credit', - 'date' => $openingBalanceDate, - 'source_id' => $sourceId, - 'source_name' => $sourceName, - 'destination_id' => $destId, - 'destination_name' => $destName, - 'user' => $account->user_id, - 'currency_id' => $currency->id, - 'order' => 0, - 'amount' => $amount, - 'foreign_amount' => null, - 'description' => trans('firefly.liability_credit_description', ['account' => $account->name]), - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'reconciled' => false, - 'notes' => null, - 'tags' => [], - ], - ], - ]; - Log::debug('Going for submission in createCreditTransaction', $submission); - - /** @var TransactionGroupFactory $factory */ - $factory = app(TransactionGroupFactory::class); - $factory->setUser($account->user); - - try { - $group = $factory->create($submission); - } catch (DuplicateTransactionException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - throw new FireflyException($e->getMessage(), 0, $e); - } - - return $group; - } - - /** - * TODO refactor to "getfirstjournal" - * - * @param TransactionGroup $group - * - * @return TransactionJournal - * @throws FireflyException - */ - private function getObJournal(TransactionGroup $group): TransactionJournal - { - /** @var TransactionJournal $journal */ - $journal = $group->transactionJournals()->first(); - if (null === $journal) { - throw new FireflyException(sprintf('Group #%d has no OB journal', $group->id)); - } - - return $journal; - } - - /** - * TODO Rename to getOpposingTransaction - * - * @param TransactionJournal $journal - * @param Account $account - * - * @return Transaction - * @throws FireflyException - */ - private function getOBTransaction(TransactionJournal $journal, Account $account): Transaction - { - /** @var Transaction $transaction */ - $transaction = $journal->transactions()->where('account_id', '!=', $account->id)->first(); - if (null === $transaction) { - throw new FireflyException(sprintf('Could not get OB transaction for journal #%d', $journal->id)); - } - - return $transaction; - } - - /** - * @param TransactionJournal $journal - * @param Account $account - * - * @return Transaction - * @throws FireflyException - */ - private function getNotOBTransaction(TransactionJournal $journal, Account $account): Transaction - { - /** @var Transaction $transaction */ - $transaction = $journal->transactions()->where('account_id', $account->id)->first(); - if (null === $transaction) { - throw new FireflyException(sprintf('Could not get non-OB transaction for journal #%d', $journal->id)); - } - - return $transaction; - } - - /** - * Update or create the opening balance group. - * Since opening balance and date can still be empty strings, it may fail. - * - * @param Account $account - * @param string $openingBalance - * @param Carbon $openingBalanceDate - * - * @return TransactionGroup - * @throws FireflyException - */ - protected function updateOBGroupV2(Account $account, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup - { - Log::debug(sprintf('Now in %s', __METHOD__)); - // create if not exists: - $obGroup = $this->getOBGroup($account); - if (null === $obGroup) { - return $this->createOBGroupV2($account, $openingBalance, $openingBalanceDate); - } - Log::debug('Update OB group'); - - // if exists, update: - $currency = $this->accountRepository->getAccountCurrency($account); - if (null === $currency) { - $currency = app('default')->getDefaultCurrencyByUser($account->user); - } - - // simply grab the first journal and change it: - $journal = $this->getObJournal($obGroup); - $obTransaction = $this->getOBTransaction($journal, $account); - $accountTransaction = $this->getNotOBTransaction($journal, $account); - $journal->date = $openingBalanceDate; - $journal->transactionCurrency()->associate($currency); - - - // if amount is negative: - if (1 === bccomp('0', $openingBalance)) { - Log::debug('Amount is negative.'); - // account transaction loses money: - $accountTransaction->amount = app('steam')->negative($openingBalance); - $accountTransaction->transaction_currency_id = $currency->id; - - // OB account transaction gains money - $obTransaction->amount = app('steam')->positive($openingBalance); - $obTransaction->transaction_currency_id = $currency->id; - } - if (-1 === bccomp('0', $openingBalance)) { - Log::debug('Amount is positive.'); - // account gains money: - $accountTransaction->amount = app('steam')->positive($openingBalance); - $accountTransaction->transaction_currency_id = $currency->id; - - // OB account loses money: - $obTransaction->amount = app('steam')->negative($openingBalance); - $obTransaction->transaction_currency_id = $currency->id; - } - // save both - $accountTransaction->save(); - $obTransaction->save(); - $journal->save(); - $obGroup->refresh(); - - return $obGroup; - } - /** * @param Account $account * @param string $openingBalance @@ -758,4 +489,273 @@ trait AccountServiceTrait return $group; } + + /** + * Delete TransactionGroup with liability credit in it. + * + * @param Account $account + */ + protected function deleteCreditTransaction(Account $account): void + { + Log::debug(sprintf('deleteCreditTransaction() for account #%d', $account->id)); + $creditGroup = $this->getCreditTransaction($account); + + if (null !== $creditGroup) { + Log::debug('Credit journal found, delete journal.'); + /** @var TransactionGroupDestroyService $service */ + $service = app(TransactionGroupDestroyService::class); + $service->destroy($creditGroup); + } + } + + /** + * Delete TransactionGroup with opening balance in it. + * + * @param Account $account + */ + protected function deleteOBGroup(Account $account): void + { + Log::debug(sprintf('deleteOB() for account #%d', $account->id)); + $openingBalanceGroup = $this->getOBGroup($account); + + // opening balance data? update it! + if (null !== $openingBalanceGroup) { + Log::debug('Opening balance journal found, delete journal.'); + /** @var TransactionGroupDestroyService $service */ + $service = app(TransactionGroupDestroyService::class); + $service->destroy($openingBalanceGroup); + } + } + + /** + * Returns the credit transaction group, or NULL if it does not exist. + * + * @param Account $account + * + * @return TransactionGroup|null + */ + protected function getCreditTransaction(Account $account): ?TransactionGroup + { + Log::debug(sprintf('Now at %s', __METHOD__)); + + return $this->accountRepository->getCreditTransactionGroup($account); + } + + /** + * @param int $currencyId + * @param string $currencyCode + * + * @return TransactionCurrency + * @throws FireflyException + * @throws JsonException + */ + protected function getCurrency(int $currencyId, string $currencyCode): TransactionCurrency + { + // find currency, or use default currency instead. + /** @var TransactionCurrencyFactory $factory */ + $factory = app(TransactionCurrencyFactory::class); + /** @var TransactionCurrency|null $currency */ + $currency = $factory->find($currencyId, $currencyCode); + + if (null === $currency) { + // use default currency: + $currency = app('amount')->getDefaultCurrencyByUser($this->user); + } + $currency->enabled = true; + $currency->save(); + + return $currency; + } + + /** + * Returns the opening balance group, or NULL if it does not exist. + * + * @param Account $account + * + * @return TransactionGroup|null + */ + protected function getOBGroup(Account $account): ?TransactionGroup + { + return $this->accountRepository->getOpeningBalanceGroup($account); + } + + /** + * Create the opposing "credit liability" transaction for credit liabilities. + * + * + * @throws FireflyException + */ + protected function updateCreditTransaction(Account $account, string $direction, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup + { + Log::debug(sprintf('Now in %s', __METHOD__)); + + if (0 === bccomp($openingBalance, '0')) { + Log::debug('Amount is zero, so will not update liability credit/debit group.'); + throw new FireflyException('Amount for update liability credit/debit was unexpectedly 0.'); + } + // if direction is "debit" (i owe this debt), amount is negative. + // which means the liability will have a negative balance which the user must fill. + $openingBalance = app('steam')->negative($openingBalance); + + // if direction is "credit" (I am owed this debt), amount is positive. + // which means the liability will have a positive balance which is drained when its paid back into any asset. + if ('credit' === $direction) { + $openingBalance = app('steam')->positive($openingBalance); + } + + // create if not exists: + $clGroup = $this->getCreditTransaction($account); + if (null === $clGroup) { + return $this->createCreditTransaction($account, $openingBalance, $openingBalanceDate); + } + // if exists, update: + $currency = $this->accountRepository->getAccountCurrency($account); + if (null === $currency) { + $currency = app('default')->getDefaultCurrencyByUser($account->user); + } + + // simply grab the first journal and change it: + $journal = $this->getObJournal($clGroup); + $clTransaction = $this->getOBTransaction($journal, $account); + $accountTransaction = $this->getNotOBTransaction($journal, $account); + $journal->date = $openingBalanceDate; + $journal->transactionCurrency()->associate($currency); + + // account always gains money: + $accountTransaction->amount = app('steam')->positive($openingBalance); + $accountTransaction->transaction_currency_id = $currency->id; + + // CL account always loses money: + $clTransaction->amount = app('steam')->negative($openingBalance); + $clTransaction->transaction_currency_id = $currency->id; + // save both + $accountTransaction->save(); + $clTransaction->save(); + $journal->save(); + $clGroup->refresh(); + + return $clGroup; + } + + /** + * Update or create the opening balance group. + * Since opening balance and date can still be empty strings, it may fail. + * + * @param Account $account + * @param string $openingBalance + * @param Carbon $openingBalanceDate + * + * @return TransactionGroup + * @throws FireflyException + */ + protected function updateOBGroupV2(Account $account, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup + { + Log::debug(sprintf('Now in %s', __METHOD__)); + // create if not exists: + $obGroup = $this->getOBGroup($account); + if (null === $obGroup) { + return $this->createOBGroupV2($account, $openingBalance, $openingBalanceDate); + } + Log::debug('Update OB group'); + + // if exists, update: + $currency = $this->accountRepository->getAccountCurrency($account); + if (null === $currency) { + $currency = app('default')->getDefaultCurrencyByUser($account->user); + } + + // simply grab the first journal and change it: + $journal = $this->getObJournal($obGroup); + $obTransaction = $this->getOBTransaction($journal, $account); + $accountTransaction = $this->getNotOBTransaction($journal, $account); + $journal->date = $openingBalanceDate; + $journal->transactionCurrency()->associate($currency); + + + // if amount is negative: + if (1 === bccomp('0', $openingBalance)) { + Log::debug('Amount is negative.'); + // account transaction loses money: + $accountTransaction->amount = app('steam')->negative($openingBalance); + $accountTransaction->transaction_currency_id = $currency->id; + + // OB account transaction gains money + $obTransaction->amount = app('steam')->positive($openingBalance); + $obTransaction->transaction_currency_id = $currency->id; + } + if (-1 === bccomp('0', $openingBalance)) { + Log::debug('Amount is positive.'); + // account gains money: + $accountTransaction->amount = app('steam')->positive($openingBalance); + $accountTransaction->transaction_currency_id = $currency->id; + + // OB account loses money: + $obTransaction->amount = app('steam')->negative($openingBalance); + $obTransaction->transaction_currency_id = $currency->id; + } + // save both + $accountTransaction->save(); + $obTransaction->save(); + $journal->save(); + $obGroup->refresh(); + + return $obGroup; + } + + /** + * @param TransactionJournal $journal + * @param Account $account + * + * @return Transaction + * @throws FireflyException + */ + private function getNotOBTransaction(TransactionJournal $journal, Account $account): Transaction + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions()->where('account_id', $account->id)->first(); + if (null === $transaction) { + throw new FireflyException(sprintf('Could not get non-OB transaction for journal #%d', $journal->id)); + } + + return $transaction; + } + + /** + * TODO Rename to getOpposingTransaction + * + * @param TransactionJournal $journal + * @param Account $account + * + * @return Transaction + * @throws FireflyException + */ + private function getOBTransaction(TransactionJournal $journal, Account $account): Transaction + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions()->where('account_id', '!=', $account->id)->first(); + if (null === $transaction) { + throw new FireflyException(sprintf('Could not get OB transaction for journal #%d', $journal->id)); + } + + return $transaction; + } + + /** + * TODO refactor to "getfirstjournal" + * + * @param TransactionGroup $group + * + * @return TransactionJournal + * @throws FireflyException + */ + private function getObJournal(TransactionGroup $group): TransactionJournal + { + /** @var TransactionJournal $journal */ + $journal = $group->transactionJournals()->first(); + if (null === $journal) { + throw new FireflyException(sprintf('Group #%d has no OB journal', $group->id)); + } + + return $journal; + } } diff --git a/app/Services/Internal/Support/CreditRecalculateService.php b/app/Services/Internal/Support/CreditRecalculateService.php index af56aca240..a22b3593c9 100644 --- a/app/Services/Internal/Support/CreditRecalculateService.php +++ b/app/Services/Internal/Support/CreditRecalculateService.php @@ -80,20 +80,19 @@ class CreditRecalculateService } /** - * + * @param Account|null $account */ - private function processGroup(): void + public function setAccount(?Account $account): void { - /** @var TransactionJournal $journal */ - foreach ($this->group->transactionJournals as $journal) { - try { - $this->findByJournal($journal); - } catch (FireflyException $e) { - Log::error($e->getTraceAsString()); - Log::error(sprintf('Could not find work account for transaction group #%d.', $this->group->id)); - } - } - Log::debug(sprintf('Done with %s', __METHOD__)); + $this->account = $account; + } + + /** + * @param TransactionGroup $group + */ + public function setGroup(TransactionGroup $group): void + { + $this->group = $group; } /** @@ -118,17 +117,6 @@ class CreditRecalculateService } } - /** - * @param TransactionJournal $journal - * - * @return Account - * @throws FireflyException - */ - private function getSourceAccount(TransactionJournal $journal): Account - { - return $this->getAccountByDirection($journal, '<'); - } - /** * @param TransactionJournal $journal * @param string $direction @@ -162,6 +150,17 @@ class CreditRecalculateService return $this->getAccountByDirection($journal, '>'); } + /** + * @param TransactionJournal $journal + * + * @return Account + * @throws FireflyException + */ + private function getSourceAccount(TransactionJournal $journal): Account + { + return $this->getAccountByDirection($journal, '<'); + } + /** * */ @@ -177,49 +176,20 @@ class CreditRecalculateService /** * */ - private function processWork(): void + private function processGroup(): void { - $this->repository = app(AccountRepositoryInterface::class); - foreach ($this->work as $account) { - $this->processWorkAccount($account); + /** @var TransactionJournal $journal */ + foreach ($this->group->transactionJournals as $journal) { + try { + $this->findByJournal($journal); + } catch (FireflyException $e) { + Log::error($e->getTraceAsString()); + Log::error(sprintf('Could not find work account for transaction group #%d.', $this->group->id)); + } } Log::debug(sprintf('Done with %s', __METHOD__)); } - /** - * @param Account $account - */ - private function processWorkAccount(Account $account): void - { - Log::debug(sprintf('Now in %s(#%d)', __METHOD__, $account->id)); - - // get opening balance (if present) - $this->repository->setUser($account->user); - $startOfDebt = $this->repository->getOpeningBalanceAmount($account) ?? '0'; - $leftOfDebt = app('steam')->positive($startOfDebt); - - /** @var AccountMetaFactory $factory */ - $factory = app(AccountMetaFactory::class); - - // amount is positive or negative, doesn't matter. - $factory->crud($account, 'start_of_debt', $startOfDebt); - - // get direction of liability: - $direction = (string)$this->repository->getMetaValue($account, 'liability_direction'); - - // now loop all transactions (except opening balance and credit thing) - $transactions = $account->transactions()->get(); - Log::debug(sprintf('Going to process %d transaction(s)', $transactions->count())); - Log::debug(sprintf('Account currency is #%d (%s)', $account->id, $this->repository->getAccountCurrency($account)?->code)); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $leftOfDebt = $this->processTransaction($account, $direction, $transaction, $leftOfDebt); - } - $factory->crud($account, 'current_debt', $leftOfDebt); - - Log::debug(sprintf('Done with %s(#%d)', __METHOD__, $account->id)); - } - /** * @param Account $account * @param string $direction @@ -341,18 +311,48 @@ class CreditRecalculateService } /** - * @param Account|null $account + * */ - public function setAccount(?Account $account): void + private function processWork(): void { - $this->account = $account; + $this->repository = app(AccountRepositoryInterface::class); + foreach ($this->work as $account) { + $this->processWorkAccount($account); + } + Log::debug(sprintf('Done with %s', __METHOD__)); } /** - * @param TransactionGroup $group + * @param Account $account */ - public function setGroup(TransactionGroup $group): void + private function processWorkAccount(Account $account): void { - $this->group = $group; + Log::debug(sprintf('Now in %s(#%d)', __METHOD__, $account->id)); + + // get opening balance (if present) + $this->repository->setUser($account->user); + $startOfDebt = $this->repository->getOpeningBalanceAmount($account) ?? '0'; + $leftOfDebt = app('steam')->positive($startOfDebt); + + /** @var AccountMetaFactory $factory */ + $factory = app(AccountMetaFactory::class); + + // amount is positive or negative, doesn't matter. + $factory->crud($account, 'start_of_debt', $startOfDebt); + + // get direction of liability: + $direction = (string)$this->repository->getMetaValue($account, 'liability_direction'); + + // now loop all transactions (except opening balance and credit thing) + $transactions = $account->transactions()->get(); + Log::debug(sprintf('Going to process %d transaction(s)', $transactions->count())); + Log::debug(sprintf('Account currency is #%d (%s)', $account->id, $this->repository->getAccountCurrency($account)?->code)); + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $leftOfDebt = $this->processTransaction($account, $direction, $transaction, $leftOfDebt); + } + $factory->crud($account, 'current_debt', $leftOfDebt); + + Log::debug(sprintf('Done with %s(#%d)', __METHOD__, $account->id)); } } diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index 70a9668b0c..5cafba52cc 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -117,240 +117,6 @@ trait JournalServiceTrait return $result; } - /** - * @param array $data - * @param array $types - * - * @return Account|null - */ - private function findAccountById(array $data, array $types): ?Account - { - // first attempt, find by ID. - if (null !== $data['id']) { - $search = $this->accountRepository->find((int)$data['id']); - if (null !== $search && in_array($search->accountType->type, $types, true)) { - Log::debug( - sprintf('Found "account_id" object: #%d, "%s" of type %s (1)', $search->id, $search->name, $search->accountType->type) - ); - return $search; - } - if (null !== $search && 0 === count($types)) { - Log::debug( - sprintf('Found "account_id" object: #%d, "%s" of type %s (2)', $search->id, $search->name, $search->accountType->type) - ); - return $search; - } - } - Log::debug(sprintf('Found no account by ID #%d of types', $data['id']), $types); - return null; - } - - /** - * @param Account|null $account - * @param array $data - * @param array $types - * - * @return Account|null - */ - private function findAccountByIban(?Account $account, array $data, array $types): ?Account - { - if (null !== $account) { - Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); - return $account; - } - if (null === $data['iban'] || '' === $data['iban']) { - Log::debug('IBAN is empty, will not search for IBAN.'); - return null; - } - // find by preferred type. - $source = $this->accountRepository->findByIbanNull($data['iban'], [$types[0]]); - // or any expected type. - $source = $source ?? $this->accountRepository->findByIbanNull($data['iban'], $types); - - if (null !== $source) { - Log::debug(sprintf('Found "account_iban" object: #%d, %s', $source->id, $source->name)); - - return $source; - } - Log::debug(sprintf('Found no account with IBAN "%s" of expected types', $data['iban']), $types); - return null; - } - - /** - * @param Account|null $account - * @param array $data - * @param array $types - * - * @return Account|null - */ - private function findAccountByNumber(?Account $account, array $data, array $types): ?Account - { - if (null !== $account) { - Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); - return $account; - } - if (null === $data['number'] || '' === $data['number']) { - Log::debug('Account number is empty, will not search for account number.'); - return null; - } - // find by preferred type. - $source = $this->accountRepository->findByAccountNumber((string)$data['number'], [$types[0]]); - - // or any expected type. - $source = $source ?? $this->accountRepository->findByAccountNumber((string)$data['number'], $types); - - if (null !== $source) { - Log::debug(sprintf('Found account: #%d, %s', $source->id, $source->name)); - - return $source; - } - - Log::debug(sprintf('Found no account with account number "%s" of expected types', $data['number']), $types); - return null; - } - - /** - * @param Account|null $account - * @param array $data - * @param array $types - * - * @return Account|null - */ - private function findAccountByName(?Account $account, array $data, array $types): ?Account - { - if (null !== $account) { - Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); - return $account; - } - if (null === $data['name'] || '' === $data['name']) { - Log::debug('Account name is empty, will not search for account name.'); - return null; - } - - // find by preferred type. - $source = $this->accountRepository->findByName($data['name'], [$types[0]]); - - // or any expected type. - $source = $source ?? $this->accountRepository->findByName($data['name'], $types); - - if (null !== $source) { - Log::debug(sprintf('Found "account_name" object: #%d, %s', $source->id, $source->name)); - - return $source; - } - Log::debug(sprintf('Found no account with account name "%s" of expected types', $data['name']), $types); - return null; - } - - /** - * @param array $types - * @return null|string - */ - private function getCreatableType(array $types): ?string - { - $result = null; - $list = config('firefly.dynamic_creation_allowed'); - /** @var string $type */ - foreach ($types as $type) { - if (true === in_array($type, $list, true)) { - $result = $type; - break; - } - } - return $result; - } - - /** - * @param Account|null $account - * @param array $data - * @param string $preferredType - * - * @return Account|null - * @throws FireflyException - */ - private function createAccount(?Account $account, array $data, string $preferredType): ?Account - { - Log::debug('Now in createAccount()', $data); - // return new account. - if (null !== $account) { - Log::debug( - sprintf( - 'Was given %s account #%d ("%s") so will simply return that.', - $account->accountType->type, - $account->id, - $account->name - ) - ); - } - if (null === $account) { - // final attempt, create it. - if (AccountType::ASSET === $preferredType) { - throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with these values: %s', json_encode($data))); - } - // fix name of account if only IBAN is given: - if ('' === (string)$data['name'] && '' !== (string)$data['iban']) { - Log::debug(sprintf('Account name is now IBAN ("%s")', $data['iban'])); - $data['name'] = $data['iban']; - } - // fix name of account if only number is given: - if ('' === (string)$data['name'] && '' !== (string)$data['number']) { - Log::debug(sprintf('Account name is now account number ("%s")', $data['number'])); - $data['name'] = $data['number']; - } - // if name is still NULL, return NULL. - if ('' === (string)$data['name']) { - Log::debug('Account name is still NULL, return NULL.'); - return null; - } - //$data['name'] = $data['name'] ?? '(no name)'; - - $account = $this->accountRepository->store( - [ - 'account_type_id' => null, - 'account_type_name' => $preferredType, - 'name' => $data['name'], - 'virtual_balance' => null, - 'active' => true, - 'iban' => $data['iban'], - 'currency_id' => $data['currency_id'] ?? null, - 'order' => $this->accountRepository->maxOrder($preferredType), - ] - ); - // store BIC - if (null !== $data['bic']) { - /** @var AccountMetaFactory $metaFactory */ - $metaFactory = app(AccountMetaFactory::class); - $metaFactory->create(['account_id' => $account->id, 'name' => 'BIC', 'data' => $data['bic']]); - } - // store account number - if (null !== $data['number']) { - /** @var AccountMetaFactory $metaFactory */ - $metaFactory = app(AccountMetaFactory::class); - $metaFactory->create(['account_id' => $account->id, 'name' => 'account_number', 'data' => $data['number']]); - } - } - - return $account; - } - - /** - * @param Account|null $account - * @param array $data - * @param array $types - * - * @return Account|null - */ - private function getCashAccount(?Account $account, array $data, array $types): ?Account - { - // return cash account. - if (null === $account && '' === (string)$data['name'] - && in_array(AccountType::CASH, $types, true)) { - $account = $this->accountRepository->getCashAccount(); - } - Log::debug('Cannot return cash account, return input instead.'); - return $account; - } - /** * @param string $amount * @@ -501,4 +267,238 @@ trait JournalServiceTrait $journal->tags()->sync($set); Log::debug('Done!'); } + + /** + * @param Account|null $account + * @param array $data + * @param string $preferredType + * + * @return Account|null + * @throws FireflyException + */ + private function createAccount(?Account $account, array $data, string $preferredType): ?Account + { + Log::debug('Now in createAccount()', $data); + // return new account. + if (null !== $account) { + Log::debug( + sprintf( + 'Was given %s account #%d ("%s") so will simply return that.', + $account->accountType->type, + $account->id, + $account->name + ) + ); + } + if (null === $account) { + // final attempt, create it. + if (AccountType::ASSET === $preferredType) { + throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with these values: %s', json_encode($data))); + } + // fix name of account if only IBAN is given: + if ('' === (string)$data['name'] && '' !== (string)$data['iban']) { + Log::debug(sprintf('Account name is now IBAN ("%s")', $data['iban'])); + $data['name'] = $data['iban']; + } + // fix name of account if only number is given: + if ('' === (string)$data['name'] && '' !== (string)$data['number']) { + Log::debug(sprintf('Account name is now account number ("%s")', $data['number'])); + $data['name'] = $data['number']; + } + // if name is still NULL, return NULL. + if ('' === (string)$data['name']) { + Log::debug('Account name is still NULL, return NULL.'); + return null; + } + //$data['name'] = $data['name'] ?? '(no name)'; + + $account = $this->accountRepository->store( + [ + 'account_type_id' => null, + 'account_type_name' => $preferredType, + 'name' => $data['name'], + 'virtual_balance' => null, + 'active' => true, + 'iban' => $data['iban'], + 'currency_id' => $data['currency_id'] ?? null, + 'order' => $this->accountRepository->maxOrder($preferredType), + ] + ); + // store BIC + if (null !== $data['bic']) { + /** @var AccountMetaFactory $metaFactory */ + $metaFactory = app(AccountMetaFactory::class); + $metaFactory->create(['account_id' => $account->id, 'name' => 'BIC', 'data' => $data['bic']]); + } + // store account number + if (null !== $data['number']) { + /** @var AccountMetaFactory $metaFactory */ + $metaFactory = app(AccountMetaFactory::class); + $metaFactory->create(['account_id' => $account->id, 'name' => 'account_number', 'data' => $data['number']]); + } + } + + return $account; + } + + /** + * @param Account|null $account + * @param array $data + * @param array $types + * + * @return Account|null + */ + private function findAccountByIban(?Account $account, array $data, array $types): ?Account + { + if (null !== $account) { + Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); + return $account; + } + if (null === $data['iban'] || '' === $data['iban']) { + Log::debug('IBAN is empty, will not search for IBAN.'); + return null; + } + // find by preferred type. + $source = $this->accountRepository->findByIbanNull($data['iban'], [$types[0]]); + // or any expected type. + $source = $source ?? $this->accountRepository->findByIbanNull($data['iban'], $types); + + if (null !== $source) { + Log::debug(sprintf('Found "account_iban" object: #%d, %s', $source->id, $source->name)); + + return $source; + } + Log::debug(sprintf('Found no account with IBAN "%s" of expected types', $data['iban']), $types); + return null; + } + + /** + * @param array $data + * @param array $types + * + * @return Account|null + */ + private function findAccountById(array $data, array $types): ?Account + { + // first attempt, find by ID. + if (null !== $data['id']) { + $search = $this->accountRepository->find((int)$data['id']); + if (null !== $search && in_array($search->accountType->type, $types, true)) { + Log::debug( + sprintf('Found "account_id" object: #%d, "%s" of type %s (1)', $search->id, $search->name, $search->accountType->type) + ); + return $search; + } + if (null !== $search && 0 === count($types)) { + Log::debug( + sprintf('Found "account_id" object: #%d, "%s" of type %s (2)', $search->id, $search->name, $search->accountType->type) + ); + return $search; + } + } + Log::debug(sprintf('Found no account by ID #%d of types', $data['id']), $types); + return null; + } + + /** + * @param Account|null $account + * @param array $data + * @param array $types + * + * @return Account|null + */ + private function findAccountByName(?Account $account, array $data, array $types): ?Account + { + if (null !== $account) { + Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); + return $account; + } + if (null === $data['name'] || '' === $data['name']) { + Log::debug('Account name is empty, will not search for account name.'); + return null; + } + + // find by preferred type. + $source = $this->accountRepository->findByName($data['name'], [$types[0]]); + + // or any expected type. + $source = $source ?? $this->accountRepository->findByName($data['name'], $types); + + if (null !== $source) { + Log::debug(sprintf('Found "account_name" object: #%d, %s', $source->id, $source->name)); + + return $source; + } + Log::debug(sprintf('Found no account with account name "%s" of expected types', $data['name']), $types); + return null; + } + + /** + * @param Account|null $account + * @param array $data + * @param array $types + * + * @return Account|null + */ + private function findAccountByNumber(?Account $account, array $data, array $types): ?Account + { + if (null !== $account) { + Log::debug(sprintf('Already have account #%d ("%s"), return that.', $account->id, $account->name)); + return $account; + } + if (null === $data['number'] || '' === $data['number']) { + Log::debug('Account number is empty, will not search for account number.'); + return null; + } + // find by preferred type. + $source = $this->accountRepository->findByAccountNumber((string)$data['number'], [$types[0]]); + + // or any expected type. + $source = $source ?? $this->accountRepository->findByAccountNumber((string)$data['number'], $types); + + if (null !== $source) { + Log::debug(sprintf('Found account: #%d, %s', $source->id, $source->name)); + + return $source; + } + + Log::debug(sprintf('Found no account with account number "%s" of expected types', $data['number']), $types); + return null; + } + + /** + * @param Account|null $account + * @param array $data + * @param array $types + * + * @return Account|null + */ + private function getCashAccount(?Account $account, array $data, array $types): ?Account + { + // return cash account. + if (null === $account && '' === (string)$data['name'] + && in_array(AccountType::CASH, $types, true)) { + $account = $this->accountRepository->getCashAccount(); + } + Log::debug('Cannot return cash account, return input instead.'); + return $account; + } + + /** + * @param array $types + * @return null|string + */ + private function getCreatableType(array $types): ?string + { + $result = null; + $list = config('firefly.dynamic_creation_allowed'); + /** @var string $type */ + foreach ($types as $type) { + if (true === in_array($type, $list, true)) { + $result = $type; + break; + } + } + return $result; + } } diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index f5d131ede0..c3518e55b3 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -40,8 +40,8 @@ use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Validation\AccountValidator; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Trait RecurringTransactionTrait @@ -179,6 +179,32 @@ trait RecurringTransactionTrait } } + /** + * @param Recurrence $recurrence + * + + */ + protected function deleteRepetitions(Recurrence $recurrence): void + { + $recurrence->recurrenceRepetitions()->delete(); + } + + /** + * @param Recurrence $recurrence + * + + */ + protected function deleteTransactions(Recurrence $recurrence): void + { + Log::debug('deleteTransactions()'); + /** @var RecurrenceTransaction $transaction */ + foreach ($recurrence->recurrenceTransactions as $transaction) { + $transaction->recurrenceTransactionMeta()->delete(); + + $transaction->delete(); + } + } + /** * @param array $expectedTypes * @param int|null $accountId @@ -230,81 +256,6 @@ trait RecurringTransactionTrait return $result ?? $repository->getCashAccount(); } - /** - * @param RecurrenceTransaction $transaction - * @param int $budgetId - */ - private function setBudget(RecurrenceTransaction $transaction, int $budgetId): void - { - $budgetFactory = app(BudgetFactory::class); - $budgetFactory->setUser($transaction->recurrence->user); - $budget = $budgetFactory->find($budgetId, null); - if (null === $budget) { - return; - } - - $meta = $transaction->recurrenceTransactionMeta()->where('name', 'budget_id')->first(); - if (null === $meta) { - $meta = new RecurrenceTransactionMeta(); - $meta->rt_id = $transaction->id; - $meta->name = 'budget_id'; - } - $meta->value = $budget->id; - $meta->save(); - } - - /** - * @param RecurrenceTransaction $transaction - * @param int $billId - */ - private function setBill(RecurrenceTransaction $transaction, int $billId): void - { - $billFactory = app(BillFactory::class); - $billFactory->setUser($transaction->recurrence->user); - $bill = $billFactory->find($billId, null); - if (null === $bill) { - return; - } - - $meta = $transaction->recurrenceTransactionMeta()->where('name', 'bill_id')->first(); - if (null === $meta) { - $meta = new RecurrenceTransactionMeta(); - $meta->rt_id = $transaction->id; - $meta->name = 'bill_id'; - } - $meta->value = $bill->id; - $meta->save(); - } - - /** - * @param RecurrenceTransaction $transaction - * @param int $categoryId - * - * @throws FireflyException - */ - private function setCategory(RecurrenceTransaction $transaction, int $categoryId): void - { - $categoryFactory = app(CategoryFactory::class); - $categoryFactory->setUser($transaction->recurrence->user); - $category = $categoryFactory->findOrCreate($categoryId, null); - if (null === $category) { - // remove category: - $transaction->recurrenceTransactionMeta()->where('name', 'category_id')->delete(); - $transaction->recurrenceTransactionMeta()->where('name', 'category_name')->delete(); - - return; - } - $transaction->recurrenceTransactionMeta()->where('name', 'category_name')->delete(); - $meta = $transaction->recurrenceTransactionMeta()->where('name', 'category_id')->first(); - if (null === $meta) { - $meta = new RecurrenceTransactionMeta(); - $meta->rt_id = $transaction->id; - $meta->name = 'category_id'; - } - $meta->value = $category->id; - $meta->save(); - } - /** * @param RecurrenceTransaction $transaction * @param int $piggyId @@ -352,28 +303,77 @@ trait RecurringTransactionTrait } /** - * @param Recurrence $recurrence - * - + * @param RecurrenceTransaction $transaction + * @param int $billId */ - protected function deleteRepetitions(Recurrence $recurrence): void + private function setBill(RecurrenceTransaction $transaction, int $billId): void { - $recurrence->recurrenceRepetitions()->delete(); + $billFactory = app(BillFactory::class); + $billFactory->setUser($transaction->recurrence->user); + $bill = $billFactory->find($billId, null); + if (null === $bill) { + return; + } + + $meta = $transaction->recurrenceTransactionMeta()->where('name', 'bill_id')->first(); + if (null === $meta) { + $meta = new RecurrenceTransactionMeta(); + $meta->rt_id = $transaction->id; + $meta->name = 'bill_id'; + } + $meta->value = $bill->id; + $meta->save(); } /** - * @param Recurrence $recurrence - * - + * @param RecurrenceTransaction $transaction + * @param int $budgetId */ - protected function deleteTransactions(Recurrence $recurrence): void + private function setBudget(RecurrenceTransaction $transaction, int $budgetId): void { - Log::debug('deleteTransactions()'); - /** @var RecurrenceTransaction $transaction */ - foreach ($recurrence->recurrenceTransactions as $transaction) { - $transaction->recurrenceTransactionMeta()->delete(); - - $transaction->delete(); + $budgetFactory = app(BudgetFactory::class); + $budgetFactory->setUser($transaction->recurrence->user); + $budget = $budgetFactory->find($budgetId, null); + if (null === $budget) { + return; } + + $meta = $transaction->recurrenceTransactionMeta()->where('name', 'budget_id')->first(); + if (null === $meta) { + $meta = new RecurrenceTransactionMeta(); + $meta->rt_id = $transaction->id; + $meta->name = 'budget_id'; + } + $meta->value = $budget->id; + $meta->save(); + } + + /** + * @param RecurrenceTransaction $transaction + * @param int $categoryId + * + * @throws FireflyException + */ + private function setCategory(RecurrenceTransaction $transaction, int $categoryId): void + { + $categoryFactory = app(CategoryFactory::class); + $categoryFactory->setUser($transaction->recurrence->user); + $category = $categoryFactory->findOrCreate($categoryId, null); + if (null === $category) { + // remove category: + $transaction->recurrenceTransactionMeta()->where('name', 'category_id')->delete(); + $transaction->recurrenceTransactionMeta()->where('name', 'category_name')->delete(); + + return; + } + $transaction->recurrenceTransactionMeta()->where('name', 'category_name')->delete(); + $meta = $transaction->recurrenceTransactionMeta()->where('name', 'category_id')->first(); + if (null === $meta) { + $meta = new RecurrenceTransactionMeta(); + $meta->rt_id = $transaction->id; + $meta->name = 'category_id'; + } + $meta->value = $category->id; + $meta->save(); } } diff --git a/app/Services/Internal/Update/AccountUpdateService.php b/app/Services/Internal/Update/AccountUpdateService.php index f1a0abb389..c2bb6276ac 100644 --- a/app/Services/Internal/Update/AccountUpdateService.php +++ b/app/Services/Internal/Update/AccountUpdateService.php @@ -31,8 +31,8 @@ use FireflyIII\Models\Location; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Support\AccountServiceTrait; use FireflyIII\User; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class AccountUpdateService @@ -63,6 +63,14 @@ class AccountUpdateService $this->accountRepository = app(AccountRepositoryInterface::class); } + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + /** * Update account data. * @@ -113,81 +121,6 @@ class AccountUpdateService return $account; } - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } - - /** - * @param Account $account - * @param array $data - * - * @return Account - */ - private function updateAccount(Account $account, array $data): Account - { - // update the account itself: - if (array_key_exists('name', $data)) { - $account->name = $data['name']; - } - if (array_key_exists('active', $data)) { - $account->active = $data['active']; - } - if (array_key_exists('iban', $data)) { - $account->iban = app('steam')->filterSpaces((string)$data['iban']); - } - - // set liability, but account must already be a liability. - //$liabilityType = $data['liability_type'] ?? ''; - if ($this->isLiability($account) && array_key_exists('liability_type', $data)) { - $type = $this->getAccountType($data['liability_type']); - $account->account_type_id = $type->id; - } - // set liability, alternative method used in v1 layout: - - if ($this->isLiability($account) && array_key_exists('account_type_id', $data)) { - $type = AccountType::find((int)$data['account_type_id']); - - if (null !== $type && in_array($type->type, config('firefly.valid_liabilities'), true)) { - $account->account_type_id = $type->id; - } - } - - // update virtual balance (could be set to zero if empty string). - if (array_key_exists('virtual_balance', $data) && null !== $data['virtual_balance']) { - $account->virtual_balance = '' === trim($data['virtual_balance']) ? '0' : $data['virtual_balance']; - } - - $account->save(); - - return $account; - } - - /** - * @param Account $account - * - * @return bool - */ - private function isLiability(Account $account): bool - { - $type = $account->accountType->type; - - return in_array($type, [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], true); - } - - /** - * @param string $type - * - * @return AccountType - */ - private function getAccountType(string $type): AccountType - { - return AccountType::whereType(ucfirst($type))->first(); - } - /** * @param Account $account * @param array $data @@ -241,6 +174,16 @@ class AccountUpdateService return $account; } + /** + * @param string $type + * + * @return AccountType + */ + private function getAccountType(string $type): AccountType + { + return AccountType::whereType(ucfirst($type))->first(); + } + private function getTypeIds(array $array): array { $return = []; @@ -254,6 +197,94 @@ class AccountUpdateService return $return; } + /** + * @param Account $account + * + * @return bool + */ + private function isLiability(Account $account): bool + { + $type = $account->accountType->type; + + return in_array($type, [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], true); + } + + /** + * @param Account $account + * @param array $data + * + * @return Account + */ + private function updateAccount(Account $account, array $data): Account + { + // update the account itself: + if (array_key_exists('name', $data)) { + $account->name = $data['name']; + } + if (array_key_exists('active', $data)) { + $account->active = $data['active']; + } + if (array_key_exists('iban', $data)) { + $account->iban = app('steam')->filterSpaces((string)$data['iban']); + } + + // set liability, but account must already be a liability. + //$liabilityType = $data['liability_type'] ?? ''; + if ($this->isLiability($account) && array_key_exists('liability_type', $data)) { + $type = $this->getAccountType($data['liability_type']); + $account->account_type_id = $type->id; + } + // set liability, alternative method used in v1 layout: + + if ($this->isLiability($account) && array_key_exists('account_type_id', $data)) { + $type = AccountType::find((int)$data['account_type_id']); + + if (null !== $type && in_array($type->type, config('firefly.valid_liabilities'), true)) { + $account->account_type_id = $type->id; + } + } + + // update virtual balance (could be set to zero if empty string). + if (array_key_exists('virtual_balance', $data) && null !== $data['virtual_balance']) { + $account->virtual_balance = '' === trim($data['virtual_balance']) ? '0' : $data['virtual_balance']; + } + + $account->save(); + + return $account; + } + + /** + * @param Account $account + * @param array $data + * + * @throws FireflyException + * @deprecated In Firefly III v5.8.0 and onwards, credit transactions for liabilities are no longer created. + */ + private function updateCreditLiability(Account $account, array $data): void + { + $type = $account->accountType; + $valid = config('firefly.valid_liabilities'); + if (in_array($type->type, $valid, true)) { + $direction = array_key_exists('liability_direction', $data) ? $data['liability_direction'] : 'empty'; + // check if is submitted as empty, that makes it valid: + if ($this->validOBData($data) && !$this->isEmptyOBData($data)) { + $openingBalance = $data['opening_balance']; + $openingBalanceDate = $data['opening_balance_date']; + if ('credit' === $direction) { + $this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate); + } + } + + if (!$this->validOBData($data) && $this->isEmptyOBData($data)) { + $this->deleteCreditTransaction($account); + } + if ($this->validOBData($data) && !$this->isEmptyOBData($data) && 'credit' !== $direction) { + $this->deleteCreditTransaction($account); + } + } + } + /** * @param Account $account * @param array $data @@ -349,35 +380,4 @@ class AccountUpdateService Log::debug('Final new array is', $new); app('preferences')->setForUser($account->user, 'frontpageAccounts', $new); } - - /** - * @param Account $account - * @param array $data - * - * @throws FireflyException - * @deprecated In Firefly III v5.8.0 and onwards, credit transactions for liabilities are no longer created. - */ - private function updateCreditLiability(Account $account, array $data): void - { - $type = $account->accountType; - $valid = config('firefly.valid_liabilities'); - if (in_array($type->type, $valid, true)) { - $direction = array_key_exists('liability_direction', $data) ? $data['liability_direction'] : 'empty'; - // check if is submitted as empty, that makes it valid: - if ($this->validOBData($data) && !$this->isEmptyOBData($data)) { - $openingBalance = $data['opening_balance']; - $openingBalanceDate = $data['opening_balance_date']; - if ('credit' === $direction) { - $this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate); - } - } - - if (!$this->validOBData($data) && $this->isEmptyOBData($data)) { - $this->deleteCreditTransaction($account); - } - if ($this->validOBData($data) && !$this->isEmptyOBData($data) && 'credit' !== $direction) { - $this->deleteCreditTransaction($account); - } - } - } } diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index 841cec3473..d53bcf32e7 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -33,8 +33,8 @@ use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; use FireflyIII\Services\Internal\Support\BillServiceTrait; use FireflyIII\User; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class BillUpdateService @@ -140,6 +140,17 @@ class BillUpdateService return $bill; } + /** + * @param Rule $rule + * @param string $key + * + * @return RuleTrigger|null + */ + private function getRuleTrigger(Rule $rule, string $key): ?RuleTrigger + { + return $rule->ruleTriggers()->where('trigger_type', $key)->first(); + } + /** * @param Bill $bill * @param array $data @@ -184,29 +195,6 @@ class BillUpdateService return $bill; } - /** - * @param Bill $bill - * @param int $oldOrder - * @param int $newOrder - */ - private function updateOrder(Bill $bill, int $oldOrder, int $newOrder): void - { - if ($newOrder > $oldOrder) { - $this->user->bills()->where('order', '<=', $newOrder)->where('order', '>', $oldOrder) - ->where('bills.id', '!=', $bill->id) - ->decrement('bills.order'); - $bill->order = $newOrder; - $bill->save(); - } - if ($newOrder < $oldOrder) { - $this->user->bills()->where('order', '>=', $newOrder)->where('order', '<', $oldOrder) - ->where('bills.id', '!=', $bill->id) - ->increment('bills.order'); - $bill->order = $newOrder; - $bill->save(); - } - } - /** * @param Bill $bill * @param array $oldData @@ -243,6 +231,29 @@ class BillUpdateService } } + /** + * @param Bill $bill + * @param int $oldOrder + * @param int $newOrder + */ + private function updateOrder(Bill $bill, int $oldOrder, int $newOrder): void + { + if ($newOrder > $oldOrder) { + $this->user->bills()->where('order', '<=', $newOrder)->where('order', '>', $oldOrder) + ->where('bills.id', '!=', $bill->id) + ->decrement('bills.order'); + $bill->order = $newOrder; + $bill->save(); + } + if ($newOrder < $oldOrder) { + $this->user->bills()->where('order', '>=', $newOrder)->where('order', '<', $oldOrder) + ->where('bills.id', '!=', $bill->id) + ->increment('bills.order'); + $bill->order = $newOrder; + $bill->save(); + } + } + /** * @param Collection $rules * @param string $key @@ -268,15 +279,4 @@ class BillUpdateService } } } - - /** - * @param Rule $rule - * @param string $key - * - * @return RuleTrigger|null - */ - private function getRuleTrigger(Rule $rule, string $key): ?RuleTrigger - { - return $rule->ruleTriggers()->where('trigger_type', $key)->first(); - } } diff --git a/app/Services/Internal/Update/CategoryUpdateService.php b/app/Services/Internal/Update/CategoryUpdateService.php index 569ebbc3bb..63c14e3b73 100644 --- a/app/Services/Internal/Update/CategoryUpdateService.php +++ b/app/Services/Internal/Update/CategoryUpdateService.php @@ -82,62 +82,6 @@ class CategoryUpdateService return $category; } - /** - * @param string $oldName - * @param string $newName - */ - private function updateRuleTriggers(string $oldName, string $newName): void - { - $types = ['category_is',]; - $triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rules.user_id', $this->user->id) - ->whereIn('rule_triggers.trigger_type', $types) - ->where('rule_triggers.trigger_value', $oldName) - ->get(['rule_triggers.*']); - Log::debug(sprintf('Found %d triggers to update.', $triggers->count())); - /** @var RuleTrigger $trigger */ - foreach ($triggers as $trigger) { - $trigger->trigger_value = $newName; - $trigger->save(); - Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); - } - } - - /** - * @param string $oldName - * @param string $newName - */ - private function updateRuleActions(string $oldName, string $newName): void - { - $types = ['set_category',]; - $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') - ->where('rules.user_id', $this->user->id) - ->whereIn('rule_actions.action_type', $types) - ->where('rule_actions.action_value', $oldName) - ->get(['rule_actions.*']); - Log::debug(sprintf('Found %d actions to update.', $actions->count())); - /** @var RuleAction $action */ - foreach ($actions as $action) { - $action->action_value = $newName; - $action->save(); - Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); - } - } - - /** - * @param string $oldName - * @param string $newName - */ - private function updateRecurrences(string $oldName, string $newName): void - { - RecurrenceTransactionMeta::leftJoin('recurrences_transactions', 'rt_meta.rt_id', '=', 'recurrences_transactions.id') - ->leftJoin('recurrences', 'recurrences.id', '=', 'recurrences_transactions.recurrence_id') - ->where('recurrences.user_id', $this->user->id) - ->where('rt_meta.name', 'category_name') - ->where('rt_meta.value', $oldName) - ->update(['rt_meta.value' => $newName]); - } - /** * @param Category $category * @param array $data @@ -166,4 +110,60 @@ class CategoryUpdateService $dbNote->text = trim($note); $dbNote->save(); } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRecurrences(string $oldName, string $newName): void + { + RecurrenceTransactionMeta::leftJoin('recurrences_transactions', 'rt_meta.rt_id', '=', 'recurrences_transactions.id') + ->leftJoin('recurrences', 'recurrences.id', '=', 'recurrences_transactions.recurrence_id') + ->where('recurrences.user_id', $this->user->id) + ->where('rt_meta.name', 'category_name') + ->where('rt_meta.value', $oldName) + ->update(['rt_meta.value' => $newName]); + } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRuleActions(string $oldName, string $newName): void + { + $types = ['set_category',]; + $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') + ->where('rules.user_id', $this->user->id) + ->whereIn('rule_actions.action_type', $types) + ->where('rule_actions.action_value', $oldName) + ->get(['rule_actions.*']); + Log::debug(sprintf('Found %d actions to update.', $actions->count())); + /** @var RuleAction $action */ + foreach ($actions as $action) { + $action->action_value = $newName; + $action->save(); + Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); + } + } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRuleTriggers(string $oldName, string $newName): void + { + $types = ['category_is',]; + $triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rules.user_id', $this->user->id) + ->whereIn('rule_triggers.trigger_type', $types) + ->where('rule_triggers.trigger_value', $oldName) + ->get(['rule_triggers.*']); + Log::debug(sprintf('Found %d triggers to update.', $triggers->count())); + /** @var RuleTrigger $trigger */ + foreach ($triggers as $trigger) { + $trigger->trigger_value = $newName; + $trigger->save(); + Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); + } + } } diff --git a/app/Services/Internal/Update/GroupCloneService.php b/app/Services/Internal/Update/GroupCloneService.php index d2a64395c0..1696ef8fbd 100644 --- a/app/Services/Internal/Update/GroupCloneService.php +++ b/app/Services/Internal/Update/GroupCloneService.php @@ -115,15 +115,16 @@ class GroupCloneService } /** - * @param Transaction $transaction + * @param TransactionJournalMeta $meta * @param TransactionJournal $newJournal */ - private function cloneTransaction(Transaction $transaction, TransactionJournal $newJournal): void + private function cloneMeta(TransactionJournalMeta $meta, TransactionJournal $newJournal): void { - $newTransaction = $transaction->replicate(); - $newTransaction->transaction_journal_id = $newJournal->id; - $newTransaction->reconciled = false; - $newTransaction->save(); + $newMeta = $meta->replicate(); + $newMeta->transaction_journal_id = $newJournal->id; + if ('recurrence_id' !== $newMeta->name) { + $newMeta->save(); + } } /** @@ -143,15 +144,14 @@ class GroupCloneService } /** - * @param TransactionJournalMeta $meta + * @param Transaction $transaction * @param TransactionJournal $newJournal */ - private function cloneMeta(TransactionJournalMeta $meta, TransactionJournal $newJournal): void + private function cloneTransaction(Transaction $transaction, TransactionJournal $newJournal): void { - $newMeta = $meta->replicate(); - $newMeta->transaction_journal_id = $newJournal->id; - if ('recurrence_id' !== $newMeta->name) { - $newMeta->save(); - } + $newTransaction = $transaction->replicate(); + $newTransaction->transaction_journal_id = $newJournal->id; + $newTransaction->reconciled = false; + $newTransaction->save(); } } diff --git a/app/Services/Internal/Update/GroupUpdateService.php b/app/Services/Internal/Update/GroupUpdateService.php index 997d5d5351..ea3c4d9657 100644 --- a/app/Services/Internal/Update/GroupUpdateService.php +++ b/app/Services/Internal/Update/GroupUpdateService.php @@ -29,8 +29,8 @@ use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Services\Internal\Destroy\JournalDestroyService; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class GroupUpdateService @@ -110,6 +110,45 @@ class GroupUpdateService return $transactionGroup; } + /** + * @param TransactionGroup $transactionGroup + * @param array $data + * + * @return TransactionJournal|null + * + * @throws DuplicateTransactionException + * @throws FireflyException + * @throws JsonException + */ + private function createTransactionJournal(TransactionGroup $transactionGroup, array $data): ?TransactionJournal + { + $submission = [ + 'transactions' => [ + $data, + ], + ]; + /** @var TransactionJournalFactory $factory */ + $factory = app(TransactionJournalFactory::class); + $factory->setUser($transactionGroup->user); + try { + $collection = $factory->create($submission); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException(sprintf('Could not create new transaction journal: %s', $e->getMessage()), 0, $e); + } + $collection->each( + function (TransactionJournal $journal) use ($transactionGroup) { + $transactionGroup->transactionJournals()->save($journal); + } + ); + if (0 === $collection->count()) { + return null; + } + + return $collection->first(); + } + /** * Update single journal. * @@ -190,43 +229,4 @@ class GroupUpdateService return $updated; } - - /** - * @param TransactionGroup $transactionGroup - * @param array $data - * - * @return TransactionJournal|null - * - * @throws DuplicateTransactionException - * @throws FireflyException - * @throws JsonException - */ - private function createTransactionJournal(TransactionGroup $transactionGroup, array $data): ?TransactionJournal - { - $submission = [ - 'transactions' => [ - $data, - ], - ]; - /** @var TransactionJournalFactory $factory */ - $factory = app(TransactionJournalFactory::class); - $factory->setUser($transactionGroup->user); - try { - $collection = $factory->create($submission); - } catch (FireflyException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - throw new FireflyException(sprintf('Could not create new transaction journal: %s', $e->getMessage()), 0, $e); - } - $collection->each( - function (TransactionJournal $journal) use ($transactionGroup) { - $transactionGroup->transactionJournals()->save($journal); - } - ); - if (0 === $collection->count()) { - return null; - } - - return $collection->first(); - } } diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index b21abba98a..4203783627 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -498,9 +498,9 @@ class JournalUpdateService $type = $this->transactionJournal->transactionType->type; if (( array_key_exists('bill_id', $this->data) - || array_key_exists('bill_name', $this->data) + || array_key_exists('bill_name', $this->data) ) - && TransactionType::WITHDRAWAL === $type + && TransactionType::WITHDRAWAL === $type ) { $billId = (int)($this->data['bill_id'] ?? 0); $billName = (string)($this->data['bill_name'] ?? ''); diff --git a/app/Services/Internal/Update/RecurrenceUpdateService.php b/app/Services/Internal/Update/RecurrenceUpdateService.php index d6c0de05a9..455de69768 100644 --- a/app/Services/Internal/Update/RecurrenceUpdateService.php +++ b/app/Services/Internal/Update/RecurrenceUpdateService.php @@ -32,8 +32,8 @@ use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class RecurrenceUpdateService @@ -114,6 +114,75 @@ class RecurrenceUpdateService return $recurrence; } + /** + * @param Recurrence $recurrence + * @param array $data + * + * @return RecurrenceRepetition|null + */ + private function matchRepetition(Recurrence $recurrence, array $data): ?RecurrenceRepetition + { + $originalCount = $recurrence->recurrenceRepetitions()->count(); + if (1 === $originalCount) { + Log::debug('Return the first one'); + /** @var RecurrenceRepetition $result */ + $result = $recurrence->recurrenceRepetitions()->first(); + return $result; + } + // find it: + $fields = [ + 'id' => 'id', + 'type' => 'repetition_type', + 'moment' => 'repetition_moment', + 'skip' => 'repetition_skip', + 'weekend' => 'weekend', + ]; + $query = $recurrence->recurrenceRepetitions(); + foreach ($fields as $field => $column) { + if (array_key_exists($field, $data)) { + $query->where($column, $data[$field]); + } + } + /** @var RecurrenceRepetition|null */ + return $query->first(); + } + + /** + * @param Recurrence $recurrence + * @param array $data + * + * @return RecurrenceTransaction|null + */ + private function matchTransaction(Recurrence $recurrence, array $data): ?RecurrenceTransaction + { + Log::debug('Now in matchTransaction()'); + $originalCount = $recurrence->recurrenceTransactions()->count(); + if (1 === $originalCount) { + Log::debug('Return the first one.'); + /** @var RecurrenceTransaction|null */ + return $recurrence->recurrenceTransactions()->first(); + } + // find it based on data + $fields = [ + 'id' => 'id', + 'currency_id' => 'transaction_currency_id', + 'foreign_currency_id' => 'foreign_currency_id', + 'source_id' => 'source_id', + 'destination_id' => 'destination_id', + 'amount' => 'amount', + 'foreign_amount' => 'foreign_amount', + 'description' => 'description', + ]; + $query = $recurrence->recurrenceTransactions(); + foreach ($fields as $field => $column) { + if (array_key_exists($field, $data)) { + $query->where($column, $data[$field]); + } + } + /** @var RecurrenceTransaction|null */ + return $query->first(); + } + /** * @param Recurrence $recurrence * @param string $text @@ -178,39 +247,6 @@ class RecurrenceUpdateService } } - /** - * @param Recurrence $recurrence - * @param array $data - * - * @return RecurrenceRepetition|null - */ - private function matchRepetition(Recurrence $recurrence, array $data): ?RecurrenceRepetition - { - $originalCount = $recurrence->recurrenceRepetitions()->count(); - if (1 === $originalCount) { - Log::debug('Return the first one'); - /** @var RecurrenceRepetition $result */ - $result = $recurrence->recurrenceRepetitions()->first(); - return $result; - } - // find it: - $fields = [ - 'id' => 'id', - 'type' => 'repetition_type', - 'moment' => 'repetition_moment', - 'skip' => 'repetition_skip', - 'weekend' => 'weekend', - ]; - $query = $recurrence->recurrenceRepetitions(); - foreach ($fields as $field => $column) { - if (array_key_exists($field, $data)) { - $query->where($column, $data[$field]); - } - } - /** @var RecurrenceRepetition|null */ - return $query->first(); - } - /** * TODO this method is very complex. * @@ -310,40 +346,4 @@ class RecurrenceUpdateService } } } - - /** - * @param Recurrence $recurrence - * @param array $data - * - * @return RecurrenceTransaction|null - */ - private function matchTransaction(Recurrence $recurrence, array $data): ?RecurrenceTransaction - { - Log::debug('Now in matchTransaction()'); - $originalCount = $recurrence->recurrenceTransactions()->count(); - if (1 === $originalCount) { - Log::debug('Return the first one.'); - /** @var RecurrenceTransaction|null */ - return $recurrence->recurrenceTransactions()->first(); - } - // find it based on data - $fields = [ - 'id' => 'id', - 'currency_id' => 'transaction_currency_id', - 'foreign_currency_id' => 'foreign_currency_id', - 'source_id' => 'source_id', - 'destination_id' => 'destination_id', - 'amount' => 'amount', - 'foreign_amount' => 'foreign_amount', - 'description' => 'description', - ]; - $query = $recurrence->recurrenceTransactions(); - foreach ($fields as $field => $column) { - if (array_key_exists($field, $data)) { - $query->where($column, $data[$field]); - } - } - /** @var RecurrenceTransaction|null */ - return $query->first(); - } } diff --git a/app/Services/Webhook/StandardWebhookSender.php b/app/Services/Webhook/StandardWebhookSender.php index 2258742362..c4d44fcd44 100644 --- a/app/Services/Webhook/StandardWebhookSender.php +++ b/app/Services/Webhook/StandardWebhookSender.php @@ -30,8 +30,8 @@ use FireflyIII\Models\WebhookMessage; use GuzzleHttp\Client; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Class StandardWebhookSender diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 1956633760..f52dbda434 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -40,6 +40,80 @@ use Psr\Container\NotFoundExceptionInterface; */ class Amount { + /** + * bool $sepBySpace is $localeconv['n_sep_by_space'] + * int $signPosn = $localeconv['n_sign_posn'] + * string $sign = $localeconv['negative_sign'] + * bool $csPrecedes = $localeconv['n_cs_precedes']. + * + * @param bool $sepBySpace + * @param int $signPosn + * @param string $sign + * @param bool $csPrecedes + * + * @return string + * + */ + public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string + { + // negative first: + $space = ' '; + + // require space between symbol and amount? + if (false === $sepBySpace) { + $space = ''; // no + } + + // there are five possible positions for the "+" or "-" sign (if it is even used) + // pos_a and pos_e could be the ( and ) symbol. + $posA = ''; // before everything + $posB = ''; // before currency symbol + $posC = ''; // after currency symbol + $posD = ''; // before amount + $posE = ''; // after everything + + // format would be (currency before amount) + // AB%sC_D%vE + // or: + // AD%v_B%sCE (amount before currency) + // the _ is the optional space + + // switch on how to display amount: + switch ($signPosn) { + default: + case 0: + // ( and ) around the whole thing + $posA = '('; + $posE = ')'; + break; + case 1: + // The sign string precedes the quantity and currency_symbol + $posA = $sign; + break; + case 2: + // The sign string succeeds the quantity and currency_symbol + $posE = $sign; + break; + case 3: + // The sign string immediately precedes the currency_symbol + $posB = $sign; + break; + case 4: + // The sign string immediately succeeds the currency_symbol + $posC = $sign; + } + + // default is amount before currency + $format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE; + + if ($csPrecedes) { + // alternative is currency before amount + $format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE; + } + + return $format; + } + /** * This method will properly format the given number, in color or "black and white", * as a currency, given two things: the currency required and the current locale. @@ -181,22 +255,6 @@ class Amount return $currency; } - /** - * @param string $value - * - * @return string - */ - private function tryDecrypt(string $value): string - { - try { - $value = Crypt::decrypt($value); // verified - } catch (DecryptException $e) { - // @ignoreException - } - - return $value; - } - /** * This method returns the correct format rules required by accounting.js, * the library used to format amounts in charts. @@ -223,6 +281,26 @@ class Amount ]; } + /** + * @return TransactionCurrency + */ + public function getSystemCurrency(): TransactionCurrency + { + return TransactionCurrency::where('code', 'EUR')->first(); + } + + /** + * @param array $info + * @param string $field + * + * @return bool + */ + private function getLocaleField(array $info, string $field): bool + { + return (is_bool($info[$field]) && true === $info[$field]) + || (is_int($info[$field]) && 1 === $info[$field]); + } + /** * @return array * @throws FireflyException @@ -252,96 +330,18 @@ class Amount } /** - * @param array $info - * @param string $field - * - * @return bool - */ - private function getLocaleField(array $info, string $field): bool - { - return (is_bool($info[$field]) && true === $info[$field]) - || (is_int($info[$field]) && 1 === $info[$field]); - } - - /** - * bool $sepBySpace is $localeconv['n_sep_by_space'] - * int $signPosn = $localeconv['n_sign_posn'] - * string $sign = $localeconv['negative_sign'] - * bool $csPrecedes = $localeconv['n_cs_precedes']. - * - * @param bool $sepBySpace - * @param int $signPosn - * @param string $sign - * @param bool $csPrecedes + * @param string $value * * @return string - * */ - public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string + private function tryDecrypt(string $value): string { - // negative first: - $space = ' '; - - // require space between symbol and amount? - if (false === $sepBySpace) { - $space = ''; // no + try { + $value = Crypt::decrypt($value); // verified + } catch (DecryptException $e) { + // @ignoreException } - // there are five possible positions for the "+" or "-" sign (if it is even used) - // pos_a and pos_e could be the ( and ) symbol. - $posA = ''; // before everything - $posB = ''; // before currency symbol - $posC = ''; // after currency symbol - $posD = ''; // before amount - $posE = ''; // after everything - - // format would be (currency before amount) - // AB%sC_D%vE - // or: - // AD%v_B%sCE (amount before currency) - // the _ is the optional space - - // switch on how to display amount: - switch ($signPosn) { - default: - case 0: - // ( and ) around the whole thing - $posA = '('; - $posE = ')'; - break; - case 1: - // The sign string precedes the quantity and currency_symbol - $posA = $sign; - break; - case 2: - // The sign string succeeds the quantity and currency_symbol - $posE = $sign; - break; - case 3: - // The sign string immediately precedes the currency_symbol - $posB = $sign; - break; - case 4: - // The sign string immediately succeeds the currency_symbol - $posC = $sign; - } - - // default is amount before currency - $format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE; - - if ($csPrecedes) { - // alternative is currency before amount - $format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE; - } - - return $format; - } - - /** - * @return TransactionCurrency - */ - public function getSystemCurrency(): TransactionCurrency - { - return TransactionCurrency::where('code', 'EUR')->first(); + return $value; } } diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php index f554295034..fced675818 100644 --- a/app/Support/Authentication/RemoteUserGuard.php +++ b/app/Support/Authentication/RemoteUserGuard.php @@ -112,15 +112,6 @@ class RemoteUserGuard implements Guard $this->user = $retrievedUser; } - /** - * @inheritDoc - */ - public function guest(): bool - { - Log::debug(sprintf('Now at %s', __METHOD__)); - return !$this->check(); - } - /** * @inheritDoc */ @@ -133,16 +124,10 @@ class RemoteUserGuard implements Guard /** * @inheritDoc */ - public function user(): ?User + public function guest(): bool { Log::debug(sprintf('Now at %s', __METHOD__)); - $user = $this->user; - if (null === $user) { - Log::debug('User is NULL'); - return null; - } - - return $user; + return !$this->check(); } /** @@ -172,6 +157,21 @@ class RemoteUserGuard implements Guard $this->user = $user; } + /** + * @inheritDoc + */ + public function user(): ?User + { + Log::debug(sprintf('Now at %s', __METHOD__)); + $user = $this->user; + if (null === $user) { + Log::debug('User is NULL'); + return null; + } + + return $user; + } + /** * @inheritDoc */ diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index b588b2bbb7..81e2721f3a 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -86,6 +86,14 @@ class CacheProperties return Cache::has($this->hash); } + /** + * @param mixed $data + */ + public function store($data): void + { + Cache::forever($this->hash, $data); + } + /** */ private function hash(): void @@ -101,12 +109,4 @@ class CacheProperties } $this->hash = substr(hash('sha256', $content), 0, 16); } - - /** - * @param mixed $data - */ - public function store($data): void - { - Cache::forever($this->hash, $data); - } } diff --git a/app/Support/Chart/Budget/FrontpageChartGenerator.php b/app/Support/Chart/Budget/FrontpageChartGenerator.php index bca53c6574..4f6ad56908 100644 --- a/app/Support/Chart/Budget/FrontpageChartGenerator.php +++ b/app/Support/Chart/Budget/FrontpageChartGenerator.php @@ -79,26 +79,53 @@ class FrontpageChartGenerator } /** - * For each budget, gets all budget limits for the current time range. - * When no limits are present, the time range is used to collect information on money spent. - * If limits are present, each limit is processed individually. + * @param Carbon $end + */ + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + /** + * @param Carbon $start + */ + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * A basic setter for the user. Also updates the repositories with the right user. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->budgetRepository->setUser($user); + $this->blRepository->setUser($user); + $this->opsRepository->setUser($user); + + $locale = app('steam')->getLocale(); + $this->monthAndDayFormat = (string)trans('config.month_and_day_js', [], $locale); + } + + /** + * If a budget has budget limit, each limit is processed individually. * * @param array $data * @param Budget $budget + * @param Collection $limits * * @return array */ - private function processBudget(array $data, Budget $budget): array + private function budgetLimits(array $data, Budget $budget, Collection $limits): array { - // get all limits: - $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); - - // if no limits - if (0 === $limits->count()) { - return $this->noBudgetLimits($data, $budget); + /** @var BudgetLimit $limit */ + foreach ($limits as $limit) { + $data = $this->processLimit($data, $budget, $limit); } - return $this->budgetLimits($data, $budget, $limits); + return $data; } /** @@ -125,22 +152,26 @@ class FrontpageChartGenerator } /** - * If a budget has budget limit, each limit is processed individually. + * For each budget, gets all budget limits for the current time range. + * When no limits are present, the time range is used to collect information on money spent. + * If limits are present, each limit is processed individually. * * @param array $data * @param Budget $budget - * @param Collection $limits * * @return array */ - private function budgetLimits(array $data, Budget $budget, Collection $limits): array + private function processBudget(array $data, Budget $budget): array { - /** @var BudgetLimit $limit */ - foreach ($limits as $limit) { - $data = $this->processLimit($data, $budget, $limit); + // get all limits: + $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); + + // if no limits + if (0 === $limits->count()) { + return $this->noBudgetLimits($data, $budget); } - return $data; + return $this->budgetLimits($data, $budget, $limits); } /** @@ -199,35 +230,4 @@ class FrontpageChartGenerator return $data; } - - /** - * @param Carbon $end - */ - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - /** - * @param Carbon $start - */ - public function setStart(Carbon $start): void - { - $this->start = $start; - } - - /** - * A basic setter for the user. Also updates the repositories with the right user. - * - * @param User $user - */ - public function setUser(User $user): void - { - $this->budgetRepository->setUser($user); - $this->blRepository->setUser($user); - $this->opsRepository->setUser($user); - - $locale = app('steam')->getLocale(); - $this->monthAndDayFormat = (string)trans('config.month_and_day_js', [], $locale); - } } diff --git a/app/Support/Chart/Category/FrontpageChartGenerator.php b/app/Support/Chart/Category/FrontpageChartGenerator.php index ab1a9985f9..824e848e9e 100644 --- a/app/Support/Chart/Category/FrontpageChartGenerator.php +++ b/app/Support/Chart/Category/FrontpageChartGenerator.php @@ -98,6 +98,22 @@ class FrontpageChartGenerator return $this->insertValues($currencyData, $tempData); } + /** + * @param array $currency + */ + private function addCurrency(array $currency): void + { + $currencyId = (int)$currency['currency_id']; + + $this->currencies[$currencyId] = $this->currencies[$currencyId] ?? [ + 'currency_id' => $currencyId, + 'currency_name' => $currency['currency_name'], + 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], + 'currency_decimal_places' => $currency['currency_decimal_places'], + ]; + } + /** * @param Category $category * @param Collection $accounts @@ -121,22 +137,6 @@ class FrontpageChartGenerator return $tempData; } - /** - * @param array $currency - */ - private function addCurrency(array $currency): void - { - $currencyId = (int)$currency['currency_id']; - - $this->currencies[$currencyId] = $this->currencies[$currencyId] ?? [ - 'currency_id' => $currencyId, - 'currency_name' => $currency['currency_name'], - 'currency_symbol' => $currency['currency_symbol'], - 'currency_code' => $currency['currency_code'], - 'currency_decimal_places' => $currency['currency_decimal_places'], - ]; - } - /** * @param Collection $accounts * diff --git a/app/Support/Export/ExportDataGenerator.php b/app/Support/Export/ExportDataGenerator.php index cf24132ebe..0c5298a842 100644 --- a/app/Support/Export/ExportDataGenerator.php +++ b/app/Support/Export/ExportDataGenerator.php @@ -140,6 +140,126 @@ class ExportDataGenerator return $return; } + /** + * @inheritDoc + */ + public function get(string $key, mixed $default = null): mixed + { + return null; + } + + /** + * @inheritDoc + */ + public function has(mixed $key): mixed + { + return null; + } + + /** + * @param Collection $accounts + */ + public function setAccounts(Collection $accounts): void + { + $this->accounts = $accounts; + } + + /** + * @param Carbon $end + */ + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + /** + * @param bool $exportAccounts + */ + public function setExportAccounts(bool $exportAccounts): void + { + $this->exportAccounts = $exportAccounts; + } + + /** + * @param bool $exportBills + */ + public function setExportBills(bool $exportBills): void + { + $this->exportBills = $exportBills; + } + + /** + * @param bool $exportBudgets + */ + public function setExportBudgets(bool $exportBudgets): void + { + $this->exportBudgets = $exportBudgets; + } + + /** + * @param bool $exportCategories + */ + public function setExportCategories(bool $exportCategories): void + { + $this->exportCategories = $exportCategories; + } + + /** + * @param bool $exportPiggies + */ + public function setExportPiggies(bool $exportPiggies): void + { + $this->exportPiggies = $exportPiggies; + } + + /** + * @param bool $exportRecurring + */ + public function setExportRecurring(bool $exportRecurring): void + { + $this->exportRecurring = $exportRecurring; + } + + /** + * @param bool $exportRules + */ + public function setExportRules(bool $exportRules): void + { + $this->exportRules = $exportRules; + } + + /** + * @param bool $exportTags + */ + public function setExportTags(bool $exportTags): void + { + $this->exportTags = $exportTags; + } + + /** + * @param bool $exportTransactions + */ + public function setExportTransactions(bool $exportTransactions): void + { + $this->exportTransactions = $exportTransactions; + } + + /** + * @param Carbon $start + */ + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + /** * @return string * @throws FireflyException @@ -217,14 +337,6 @@ class ExportDataGenerator return $string; } - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } - /** * @return string * @throws FireflyException @@ -828,14 +940,6 @@ class ExportDataGenerator return $string; } - /** - * @inheritDoc - */ - public function get(string $key, mixed $default = null): mixed - { - return null; - } - /** * @return string * @throws FireflyException @@ -979,14 +1083,6 @@ class ExportDataGenerator return $string; } - /** - * @param Collection $accounts - */ - public function setAccounts(Collection $accounts): void - { - $this->accounts = $accounts; - } - /** * @param array $tags * @@ -1004,100 +1100,4 @@ class ExportDataGenerator return implode(',', $smol); } - - /** - * @inheritDoc - */ - public function has(mixed $key): mixed - { - return null; - } - - /** - * @param Carbon $end - */ - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - /** - * @param bool $exportAccounts - */ - public function setExportAccounts(bool $exportAccounts): void - { - $this->exportAccounts = $exportAccounts; - } - - /** - * @param bool $exportBills - */ - public function setExportBills(bool $exportBills): void - { - $this->exportBills = $exportBills; - } - - /** - * @param bool $exportBudgets - */ - public function setExportBudgets(bool $exportBudgets): void - { - $this->exportBudgets = $exportBudgets; - } - - /** - * @param bool $exportCategories - */ - public function setExportCategories(bool $exportCategories): void - { - $this->exportCategories = $exportCategories; - } - - /** - * @param bool $exportPiggies - */ - public function setExportPiggies(bool $exportPiggies): void - { - $this->exportPiggies = $exportPiggies; - } - - /** - * @param bool $exportRecurring - */ - public function setExportRecurring(bool $exportRecurring): void - { - $this->exportRecurring = $exportRecurring; - } - - /** - * @param bool $exportRules - */ - public function setExportRules(bool $exportRules): void - { - $this->exportRules = $exportRules; - } - - /** - * @param bool $exportTags - */ - public function setExportTags(bool $exportTags): void - { - $this->exportTags = $exportTags; - } - - /** - * @param bool $exportTransactions - */ - public function setExportTransactions(bool $exportTransactions): void - { - $this->exportTransactions = $exportTransactions; - } - - /** - * @param Carbon $start - */ - public function setStart(Carbon $start): void - { - $this->start = $start; - } } diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 3796b89a0e..c65a44c942 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -49,16 +49,6 @@ class FireflyConfig Configuration::where('name', $name)->forceDelete(); } - /** - * @param string $name - * - * @return bool - */ - public function has(string $name): bool - { - return Configuration::where('name', $name)->count() === 1; - } - /** * @param string $name * @param bool|string|int|null $default @@ -93,6 +83,47 @@ class FireflyConfig return $this->set($name, $default); } + /** + * @param string $name + * @param mixed $default + * + * @return Configuration|null + */ + public function getFresh(string $name, $default = null): ?Configuration + { + $config = Configuration::where('name', $name)->first(['id', 'name', 'data']); + if ($config) { + return $config; + } + // no preference found and default is null: + if (null === $default) { + return null; + } + + return $this->set($name, $default); + } + + /** + * @param string $name + * + * @return bool + */ + public function has(string $name): bool + { + return Configuration::where('name', $name)->count() === 1; + } + + /** + * @param string $name + * @param mixed $value + * + * @return Configuration + */ + public function put(string $name, $value): Configuration + { + return $this->set($name, $value); + } + /** * @param string $name * @param mixed $value @@ -127,35 +158,4 @@ class FireflyConfig return $config; } - - /** - * @param string $name - * @param mixed $default - * - * @return Configuration|null - */ - public function getFresh(string $name, $default = null): ?Configuration - { - $config = Configuration::where('name', $name)->first(['id', 'name', 'data']); - if ($config) { - return $config; - } - // no preference found and default is null: - if (null === $default) { - return null; - } - - return $this->set($name, $default); - } - - /** - * @param string $name - * @param mixed $value - * - * @return Configuration - */ - public function put(string $name, $value): Configuration - { - return $this->set($name, $value); - } } diff --git a/app/Support/Form/AccountForm.php b/app/Support/Form/AccountForm.php index 6134ded1cd..6225e7b6ae 100644 --- a/app/Support/Form/AccountForm.php +++ b/app/Support/Form/AccountForm.php @@ -63,36 +63,6 @@ class AccountForm return $this->select($name, $grouped, $value, $options); } - private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array - { - if (null === $repository) { - $repository = $this->getAccountRepository(); - } - $accountList = $repository->getActiveAccountsByType($types); - $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,]; - $grouped = []; - - /** @var Account $account */ - foreach ($accountList as $account) { - $role = (string)$repository->getMetaValue($account, 'account_role'); - if (in_array($account->accountType->type, $liabilityTypes, true)) { - $role = sprintf('l_%s', $account->accountType->type); - } elseif ('' === $role) { - if (AccountType::EXPENSE === $account->accountType->type) { - $role = 'expense_account'; - } elseif (AccountType::REVENUE === $account->accountType->type) { - $role = 'revenue_account'; - } else { - $role = 'no_account_type'; - } - } - $key = (string)trans(sprintf('firefly.opt_group_%s', $role)); - $grouped[$key][$account->id] = $account->name; - } - - return $grouped; - } - /** * Grouped dropdown list of all accounts that are valid as the destination of a withdrawal. * @@ -181,4 +151,34 @@ class AccountForm return $this->select($name, $grouped, $value, $options); } + + private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array + { + if (null === $repository) { + $repository = $this->getAccountRepository(); + } + $accountList = $repository->getActiveAccountsByType($types); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,]; + $grouped = []; + + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$repository->getMetaValue($account, 'account_role'); + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = sprintf('l_%s', $account->accountType->type); + } elseif ('' === $role) { + if (AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; + } elseif (AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; + } else { + $role = 'no_account_type'; + } + } + $key = (string)trans(sprintf('firefly.opt_group_%s', $role)); + $grouped[$key][$account->id] = $account->name; + } + + return $grouped; + } } diff --git a/app/Support/Form/CurrencyForm.php b/app/Support/Form/CurrencyForm.php index 67302a613e..636f235579 100644 --- a/app/Support/Form/CurrencyForm.php +++ b/app/Support/Form/CurrencyForm.php @@ -53,61 +53,6 @@ class CurrencyForm return $this->currencyField($name, 'amount', $value, $options); } - /** - * @param string $name - * @param string $view - * @param mixed $value - * @param array|null $options - * - * @return string - * @throws FireflyException - */ - protected function currencyField(string $name, string $view, mixed $value = null, array $options = null): string - { - $label = $this->label($name, $options); - $options = $this->expandOptionArray($name, $label, $options); - $classes = $this->getHolderClasses($name); - $value = $this->fillFieldValue($name, $value); - $options['step'] = 'any'; - $defaultCurrency = $options['currency'] ?? Amt::getDefaultCurrency(); - /** @var Collection $currencies */ - $currencies = app('amount')->getCurrencies(); - unset($options['currency'], $options['placeholder']); - - // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount) - $preFilled = session('preFilled'); - if (!is_array($preFilled)) { - $preFilled = []; - } - $key = 'amount_currency_id_'.$name; - $sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $defaultCurrency->id; - - Log::debug(sprintf('Sent currency ID is %d', $sentCurrencyId)); - - // find this currency in set of currencies: - foreach ($currencies as $currency) { - if ($currency->id === $sentCurrencyId) { - $defaultCurrency = $currency; - Log::debug(sprintf('default currency is now %s', $defaultCurrency->code)); - break; - } - } - - // make sure value is formatted nicely: - if (null !== $value && '' !== $value) { - $value = app('steam')->bcround($value, $defaultCurrency->decimal_places); - } - try { - $html = view('form.'.$view, compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); - } catch (Throwable $e) { - Log::debug(sprintf('Could not render currencyField(): %s', $e->getMessage())); - $html = 'Could not render currencyField.'; - throw new FireflyException($html, 0, $e); - } - - return $html; - } - /** * TODO describe and cleanup. * @@ -123,6 +68,58 @@ class CurrencyForm return $this->allCurrencyField($name, 'balance', $value, $options); } + /** + * TODO cleanup and describe + * + * @param string $name + * @param mixed $value + * @param array|null $options + * + * @return string + */ + public function currencyList(string $name, $value = null, array $options = null): string + { + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + // get all currencies: + $list = $currencyRepos->get(); + $array = []; + /** @var TransactionCurrency $currency */ + foreach ($list as $currency) { + $array[$currency->id] = $currency->name.' ('.$currency->symbol.')'; + } + + return $this->select($name, $array, $value, $options); + } + + /** + * TODO cleanup and describe + * + * @param string $name + * @param mixed $value + * @param array|null $options + * + * @return string + */ + public function currencyListEmpty(string $name, $value = null, array $options = null): string + { + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + // get all currencies: + $list = $currencyRepos->get(); + $array = [ + 0 => (string)trans('firefly.no_currency'), + ]; + /** @var TransactionCurrency $currency */ + foreach ($list as $currency) { + $array[$currency->id] = $currency->name.' ('.$currency->symbol.')'; + } + + return $this->select($name, $array, $value, $options); + } + /** * TODO describe and cleanup * @@ -181,54 +178,57 @@ class CurrencyForm } /** - * TODO cleanup and describe - * * @param string $name + * @param string $view * @param mixed $value * @param array|null $options * * @return string + * @throws FireflyException */ - public function currencyList(string $name, $value = null, array $options = null): string + protected function currencyField(string $name, string $view, mixed $value = null, array $options = null): string { - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $options['step'] = 'any'; + $defaultCurrency = $options['currency'] ?? Amt::getDefaultCurrency(); + /** @var Collection $currencies */ + $currencies = app('amount')->getCurrencies(); + unset($options['currency'], $options['placeholder']); - // get all currencies: - $list = $currencyRepos->get(); - $array = []; - /** @var TransactionCurrency $currency */ - foreach ($list as $currency) { - $array[$currency->id] = $currency->name.' ('.$currency->symbol.')'; + // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount) + $preFilled = session('preFilled'); + if (!is_array($preFilled)) { + $preFilled = []; + } + $key = 'amount_currency_id_'.$name; + $sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $defaultCurrency->id; + + Log::debug(sprintf('Sent currency ID is %d', $sentCurrencyId)); + + // find this currency in set of currencies: + foreach ($currencies as $currency) { + if ($currency->id === $sentCurrencyId) { + $defaultCurrency = $currency; + Log::debug(sprintf('default currency is now %s', $defaultCurrency->code)); + break; + } } - return $this->select($name, $array, $value, $options); - } - - /** - * TODO cleanup and describe - * - * @param string $name - * @param mixed $value - * @param array|null $options - * - * @return string - */ - public function currencyListEmpty(string $name, $value = null, array $options = null): string - { - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - - // get all currencies: - $list = $currencyRepos->get(); - $array = [ - 0 => (string)trans('firefly.no_currency'), - ]; - /** @var TransactionCurrency $currency */ - foreach ($list as $currency) { - $array[$currency->id] = $currency->name.' ('.$currency->symbol.')'; + // make sure value is formatted nicely: + if (null !== $value && '' !== $value) { + $value = app('steam')->bcround($value, $defaultCurrency->decimal_places); + } + try { + $html = view('form.'.$view, compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); + } catch (Throwable $e) { + Log::debug(sprintf('Could not render currencyField(): %s', $e->getMessage())); + $html = 'Could not render currencyField.'; + throw new FireflyException($html, 0, $e); } - return $this->select($name, $array, $value, $options); + return $html; } } diff --git a/app/Support/Form/FormSupport.php b/app/Support/Form/FormSupport.php index b1c3e35c5f..d8c7da9880 100644 --- a/app/Support/Form/FormSupport.php +++ b/app/Support/Form/FormSupport.php @@ -26,8 +26,8 @@ namespace FireflyIII\Support\Form; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use Illuminate\Support\MessageBag; use Illuminate\Support\Facades\Log; +use Illuminate\Support\MessageBag; use Throwable; /** @@ -61,23 +61,6 @@ trait FormSupport return $html; } - /** - * @param string $name - * @param array|null $options - * - * @return string - */ - protected function label(string $name, array $options = null): string - { - $options = $options ?? []; - if (array_key_exists('label', $options)) { - return $options['label']; - } - $name = str_replace('[]', '', $name); - - return (string)trans('form.'.$name); - } - /** * @param string $name * @param mixed $label @@ -97,25 +80,6 @@ trait FormSupport return $options; } - /** - * @param string $name - * - * @return string - */ - protected function getHolderClasses(string $name): string - { - // Get errors from session: - /** @var MessageBag $errors */ - $errors = session('errors'); - $classes = 'form-group'; - - if (null !== $errors && $errors->has($name)) { - $classes = 'form-group has-error has-feedback'; - } - - return $classes; - } - /** * @param string $name * @param mixed|null $value @@ -163,4 +127,40 @@ trait FormSupport return $date; } + + /** + * @param string $name + * + * @return string + */ + protected function getHolderClasses(string $name): string + { + // Get errors from session: + /** @var MessageBag $errors */ + $errors = session('errors'); + $classes = 'form-group'; + + if (null !== $errors && $errors->has($name)) { + $classes = 'form-group has-error has-feedback'; + } + + return $classes; + } + + /** + * @param string $name + * @param array|null $options + * + * @return string + */ + protected function label(string $name, array $options = null): string + { + $options = $options ?? []; + if (array_key_exists('label', $options)) { + return $options['label']; + } + $name = str_replace('[]', '', $name); + + return (string)trans('form.'.$name); + } } diff --git a/app/Support/Http/Api/ConvertsExchangeRates.php b/app/Support/Http/Api/ConvertsExchangeRates.php index 85b6bb83ce..0447677055 100644 --- a/app/Support/Http/Api/ConvertsExchangeRates.php +++ b/app/Support/Http/Api/ConvertsExchangeRates.php @@ -74,130 +74,6 @@ trait ConvertsExchangeRates return $set; } - /** - * @return void - */ - private function getPreference(): void - { - $this->enabled = true; - } - - /** - * @param int $currencyId - * @return TransactionCurrency - */ - private function getCurrency(int $currencyId): TransactionCurrency - { - $result = TransactionCurrency::find($currencyId); - if (null === $result) { - return app('amount')->getDefaultCurrency(); - } - return $result; - } - - /** - * @param TransactionCurrency $from - * @param TransactionCurrency $to - * @param Carbon $date - * @return string - */ - private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string - { - Log::debug(sprintf('getRate(%s, %s, "%s")', $from->code, $to->code, $date->format('Y-m-d'))); - /** @var CurrencyExchangeRate $result */ - $result = auth()->user() - ->currencyExchangeRates() - ->where('from_currency_id', $from->id) - ->where('to_currency_id', $to->id) - ->where('date', '<=', $date->format('Y-m-d')) - ->orderBy('date', 'DESC') - ->first(); - if (null !== $result) { - $rate = (string)$result->rate; - Log::debug(sprintf('Rate is %s', $rate)); - return $rate; - } - // no result. perhaps the other way around? - /** @var CurrencyExchangeRate $result */ - $result = auth()->user() - ->currencyExchangeRates() - ->where('from_currency_id', $to->id) - ->where('to_currency_id', $from->id) - ->where('date', '<=', $date->format('Y-m-d')) - ->orderBy('date', 'DESC') - ->first(); - if (null !== $result) { - $rate = bcdiv('1', (string)$result->rate); - Log::debug(sprintf('Reversed rate is %s', $rate)); - return $rate; - } - // try euro rates - $result1 = $this->getEuroRate($from, $date); - if ('0' === $result1) { - Log::debug(sprintf('No exchange rate between EUR and %s', $from->code)); - return '0'; - } - $result2 = $this->getEuroRate($to, $date); - if ('0' === $result2) { - Log::debug(sprintf('No exchange rate between EUR and %s', $to->code)); - return '0'; - } - // still need to inverse rate 2: - $result2 = bcdiv('1', $result2); - $rate = bcmul($result1, $result2); - Log::debug(sprintf('Rate %s to EUR is %s', $from->code, $result1)); - Log::debug(sprintf('Rate EUR to %s is %s', $to->code, $result2)); - Log::debug(sprintf('Rate for %s to %s is %s', $from->code, $to->code, $rate)); - return $rate; - } - - /** - * @param TransactionCurrency $currency - * @param Carbon $date - * @return string - */ - private function getEuroRate(TransactionCurrency $currency, Carbon $date): string - { - Log::debug(sprintf('Find rate for %s to Euro', $currency->code)); - $euro = TransactionCurrency::whereCode('EUR')->first(); - if (null === $euro) { - app('log')->warning('Cannot do indirect conversion without EUR.'); - return '0'; - } - - // try one way: - /** @var CurrencyExchangeRate $result */ - $result = auth()->user() - ->currencyExchangeRates() - ->where('from_currency_id', $currency->id) - ->where('to_currency_id', $euro->id) - ->where('date', '<=', $date->format('Y-m-d')) - ->orderBy('date', 'DESC') - ->first(); - if (null !== $result) { - $rate = (string)$result->rate; - Log::debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate)); - return $rate; - } - // try the other way around and inverse it. - /** @var CurrencyExchangeRate $result */ - $result = auth()->user() - ->currencyExchangeRates() - ->where('from_currency_id', $euro->id) - ->where('to_currency_id', $currency->id) - ->where('date', '<=', $date->format('Y-m-d')) - ->orderBy('date', 'DESC') - ->first(); - if (null !== $result) { - $rate = bcdiv('1', (string)$result->rate); - Log::debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate)); - return $rate; - } - - Log::debug(sprintf('No rate for %s to EUR.', $currency->code)); - return '0'; - } - /** * For a sum of entries, get the exchange rate to the native currency of * the user. @@ -260,4 +136,128 @@ trait ConvertsExchangeRates return bcmul($amount, $rate); } + + /** + * @param int $currencyId + * @return TransactionCurrency + */ + private function getCurrency(int $currencyId): TransactionCurrency + { + $result = TransactionCurrency::find($currencyId); + if (null === $result) { + return app('amount')->getDefaultCurrency(); + } + return $result; + } + + /** + * @param TransactionCurrency $currency + * @param Carbon $date + * @return string + */ + private function getEuroRate(TransactionCurrency $currency, Carbon $date): string + { + Log::debug(sprintf('Find rate for %s to Euro', $currency->code)); + $euro = TransactionCurrency::whereCode('EUR')->first(); + if (null === $euro) { + app('log')->warning('Cannot do indirect conversion without EUR.'); + return '0'; + } + + // try one way: + /** @var CurrencyExchangeRate $result */ + $result = auth()->user() + ->currencyExchangeRates() + ->where('from_currency_id', $currency->id) + ->where('to_currency_id', $euro->id) + ->where('date', '<=', $date->format('Y-m-d')) + ->orderBy('date', 'DESC') + ->first(); + if (null !== $result) { + $rate = (string)$result->rate; + Log::debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate)); + return $rate; + } + // try the other way around and inverse it. + /** @var CurrencyExchangeRate $result */ + $result = auth()->user() + ->currencyExchangeRates() + ->where('from_currency_id', $euro->id) + ->where('to_currency_id', $currency->id) + ->where('date', '<=', $date->format('Y-m-d')) + ->orderBy('date', 'DESC') + ->first(); + if (null !== $result) { + $rate = bcdiv('1', (string)$result->rate); + Log::debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate)); + return $rate; + } + + Log::debug(sprintf('No rate for %s to EUR.', $currency->code)); + return '0'; + } + + /** + * @return void + */ + private function getPreference(): void + { + $this->enabled = true; + } + + /** + * @param TransactionCurrency $from + * @param TransactionCurrency $to + * @param Carbon $date + * @return string + */ + private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string + { + Log::debug(sprintf('getRate(%s, %s, "%s")', $from->code, $to->code, $date->format('Y-m-d'))); + /** @var CurrencyExchangeRate $result */ + $result = auth()->user() + ->currencyExchangeRates() + ->where('from_currency_id', $from->id) + ->where('to_currency_id', $to->id) + ->where('date', '<=', $date->format('Y-m-d')) + ->orderBy('date', 'DESC') + ->first(); + if (null !== $result) { + $rate = (string)$result->rate; + Log::debug(sprintf('Rate is %s', $rate)); + return $rate; + } + // no result. perhaps the other way around? + /** @var CurrencyExchangeRate $result */ + $result = auth()->user() + ->currencyExchangeRates() + ->where('from_currency_id', $to->id) + ->where('to_currency_id', $from->id) + ->where('date', '<=', $date->format('Y-m-d')) + ->orderBy('date', 'DESC') + ->first(); + if (null !== $result) { + $rate = bcdiv('1', (string)$result->rate); + Log::debug(sprintf('Reversed rate is %s', $rate)); + return $rate; + } + // try euro rates + $result1 = $this->getEuroRate($from, $date); + if ('0' === $result1) { + Log::debug(sprintf('No exchange rate between EUR and %s', $from->code)); + return '0'; + } + $result2 = $this->getEuroRate($to, $date); + if ('0' === $result2) { + Log::debug(sprintf('No exchange rate between EUR and %s', $to->code)); + return '0'; + } + // still need to inverse rate 2: + $result2 = bcdiv('1', $result2); + $rate = bcmul($result1, $result2); + Log::debug(sprintf('Rate %s to EUR is %s', $from->code, $result1)); + Log::debug(sprintf('Rate EUR to %s is %s', $to->code, $result2)); + Log::debug(sprintf('Rate for %s to %s is %s', $from->code, $to->code, $rate)); + return $rate; + } } diff --git a/app/Support/Http/Controllers/ChartGeneration.php b/app/Support/Http/Controllers/ChartGeneration.php index 6a5165e76d..456a28fad8 100644 --- a/app/Support/Http/Controllers/ChartGeneration.php +++ b/app/Support/Http/Controllers/ChartGeneration.php @@ -31,8 +31,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; /** * Trait ChartGeneration diff --git a/app/Support/Http/Controllers/CreateStuff.php b/app/Support/Http/Controllers/CreateStuff.php index ebb5c186c1..9a8e8273ab 100644 --- a/app/Support/Http/Controllers/CreateStuff.php +++ b/app/Support/Http/Controllers/CreateStuff.php @@ -28,8 +28,8 @@ use FireflyIII\Http\Requests\NewUserFormRequest; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\User; -use Laravel\Passport\Passport; use Illuminate\Support\Facades\Log; +use Laravel\Passport\Passport; use phpseclib3\Crypt\RSA; /** diff --git a/app/Support/Http/Controllers/CronRunner.php b/app/Support/Http/Controllers/CronRunner.php index b8f2e97f6e..697d300bbb 100644 --- a/app/Support/Http/Controllers/CronRunner.php +++ b/app/Support/Http/Controllers/CronRunner.php @@ -37,6 +37,70 @@ use Psr\Container\NotFoundExceptionInterface; */ trait CronRunner { + /** + * @param bool $force + * @param Carbon $date + * + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function billWarningCronJob(bool $force, Carbon $date): array + { + /** @var BillWarningCronjob $billWarning */ + $billWarning = app(BillWarningCronjob::class); + $billWarning->setForce($force); + $billWarning->setDate($date); + try { + $billWarning->fire(); + } catch (FireflyException $e) { + return [ + 'job_fired' => false, + 'job_succeeded' => false, + 'job_errored' => true, + 'message' => $e->getMessage(), + ]; + } + + return [ + 'job_fired' => $billWarning->jobFired, + 'job_succeeded' => $billWarning->jobSucceeded, + 'job_errored' => $billWarning->jobErrored, + 'message' => $billWarning->message, + ]; + } + + /** + * @param bool $force + * @param Carbon $date + * + * @return array + */ + protected function exchangeRatesCronJob(bool $force, Carbon $date): array + { + /** @var ExchangeRatesCronjob $exchangeRates */ + $exchangeRates = app(ExchangeRatesCronjob::class); + $exchangeRates->setForce($force); + $exchangeRates->setDate($date); + try { + $exchangeRates->fire(); + } catch (FireflyException $e) { + return [ + 'job_fired' => false, + 'job_succeeded' => false, + 'job_errored' => true, + 'message' => $e->getMessage(), + ]; + } + + return [ + 'job_fired' => $exchangeRates->jobFired, + 'job_succeeded' => $exchangeRates->jobSucceeded, + 'job_errored' => $exchangeRates->jobErrored, + 'message' => $exchangeRates->message, + ]; + } + /** * @param bool $force * @param Carbon $date @@ -100,70 +164,4 @@ trait CronRunner 'message' => $recurring->message, ]; } - - - /** - * @param bool $force - * @param Carbon $date - * - * @return array - */ - protected function exchangeRatesCronJob(bool $force, Carbon $date): array - { - /** @var ExchangeRatesCronjob $exchangeRates */ - $exchangeRates = app(ExchangeRatesCronjob::class); - $exchangeRates->setForce($force); - $exchangeRates->setDate($date); - try { - $exchangeRates->fire(); - } catch (FireflyException $e) { - return [ - 'job_fired' => false, - 'job_succeeded' => false, - 'job_errored' => true, - 'message' => $e->getMessage(), - ]; - } - - return [ - 'job_fired' => $exchangeRates->jobFired, - 'job_succeeded' => $exchangeRates->jobSucceeded, - 'job_errored' => $exchangeRates->jobErrored, - 'message' => $exchangeRates->message, - ]; - } - - - /** - * @param bool $force - * @param Carbon $date - * - * @return array - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - protected function billWarningCronJob(bool $force, Carbon $date): array - { - /** @var BillWarningCronjob $billWarning */ - $billWarning = app(BillWarningCronjob::class); - $billWarning->setForce($force); - $billWarning->setDate($date); - try { - $billWarning->fire(); - } catch (FireflyException $e) { - return [ - 'job_fired' => false, - 'job_succeeded' => false, - 'job_errored' => true, - 'message' => $e->getMessage(), - ]; - } - - return [ - 'job_fired' => $billWarning->jobFired, - 'job_succeeded' => $billWarning->jobSucceeded, - 'job_errored' => $billWarning->jobErrored, - 'message' => $billWarning->message, - ]; - } } diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index 8630365ae7..9f398262a5 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -150,116 +150,6 @@ trait PeriodOverview return $entries; } - /** - * Filter a list of journals by a set of dates, and then group them by currency. - * - * @param array $array - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array - { - $result = []; - /** @var array $journal */ - foreach ($array as $journal) { - if ($journal['date'] <= $end && $journal['date'] >= $start) { - $result[] = $journal; - } - } - - return $result; - } - - /** - * Return only transactions where $account is the source. - * - * @param Account $account - * @param array $journals - * - * @return array - */ - private function filterTransferredAway(Account $account, array $journals): array - { - $return = []; - /** @var array $journal */ - foreach ($journals as $journal) { - if ($account->id === (int)$journal['source_account_id']) { - $return[] = $journal; - } - } - - return $return; - } - - /** - * Return only transactions where $account is the source. - * - * @param Account $account - * @param array $journals - * - * @return array - */ - private function filterTransferredIn(Account $account, array $journals): array - { - $return = []; - /** @var array $journal */ - foreach ($journals as $journal) { - if ($account->id === (int)$journal['destination_account_id']) { - $return[] = $journal; - } - } - - return $return; - } - - /** - * @param array $journals - * - * @return array - */ - private function groupByCurrency(array $journals): array - { - $return = []; - /** @var array $journal */ - foreach ($journals as $journal) { - $currencyId = (int)$journal['currency_id']; - $foreignCurrencyId = $journal['foreign_currency_id']; - if (!array_key_exists($currencyId, $return)) { - $return[$currencyId] = [ - 'amount' => '0', - 'count' => 0, - 'currency_id' => $currencyId, - 'currency_name' => $journal['currency_name'], - 'currency_code' => $journal['currency_code'], - 'currency_symbol' => $journal['currency_symbol'], - 'currency_decimal_places' => $journal['currency_decimal_places'], - ]; - } - $return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount'] ?? '0'); - $return[$currencyId]['count']++; - - if (null !== $foreignCurrencyId && null !== $journal['foreign_amount']) { - if (!array_key_exists($foreignCurrencyId, $return)) { - $return[$foreignCurrencyId] = [ - 'amount' => '0', - 'count' => 0, - 'currency_id' => (int)$foreignCurrencyId, - 'currency_name' => $journal['foreign_currency_name'], - 'currency_code' => $journal['foreign_currency_code'], - 'currency_symbol' => $journal['foreign_currency_symbol'], - 'currency_decimal_places' => $journal['foreign_currency_decimal_places'], - ]; - } - $return[$foreignCurrencyId]['count']++; - $return[$foreignCurrencyId]['amount'] = bcadd($return[$foreignCurrencyId]['amount'], $journal['foreign_amount']); - } - } - - return $return; - } - /** * Overview for single category. Has been refactored recently. * @@ -608,4 +498,114 @@ trait PeriodOverview return $entries; } + + /** + * Filter a list of journals by a set of dates, and then group them by currency. + * + * @param array $array + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array + { + $result = []; + /** @var array $journal */ + foreach ($array as $journal) { + if ($journal['date'] <= $end && $journal['date'] >= $start) { + $result[] = $journal; + } + } + + return $result; + } + + /** + * Return only transactions where $account is the source. + * + * @param Account $account + * @param array $journals + * + * @return array + */ + private function filterTransferredAway(Account $account, array $journals): array + { + $return = []; + /** @var array $journal */ + foreach ($journals as $journal) { + if ($account->id === (int)$journal['source_account_id']) { + $return[] = $journal; + } + } + + return $return; + } + + /** + * Return only transactions where $account is the source. + * + * @param Account $account + * @param array $journals + * + * @return array + */ + private function filterTransferredIn(Account $account, array $journals): array + { + $return = []; + /** @var array $journal */ + foreach ($journals as $journal) { + if ($account->id === (int)$journal['destination_account_id']) { + $return[] = $journal; + } + } + + return $return; + } + + /** + * @param array $journals + * + * @return array + */ + private function groupByCurrency(array $journals): array + { + $return = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + $foreignCurrencyId = $journal['foreign_currency_id']; + if (!array_key_exists($currencyId, $return)) { + $return[$currencyId] = [ + 'amount' => '0', + 'count' => 0, + 'currency_id' => $currencyId, + 'currency_name' => $journal['currency_name'], + 'currency_code' => $journal['currency_code'], + 'currency_symbol' => $journal['currency_symbol'], + 'currency_decimal_places' => $journal['currency_decimal_places'], + ]; + } + $return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount'] ?? '0'); + $return[$currencyId]['count']++; + + if (null !== $foreignCurrencyId && null !== $journal['foreign_amount']) { + if (!array_key_exists($foreignCurrencyId, $return)) { + $return[$foreignCurrencyId] = [ + 'amount' => '0', + 'count' => 0, + 'currency_id' => (int)$foreignCurrencyId, + 'currency_name' => $journal['foreign_currency_name'], + 'currency_code' => $journal['foreign_currency_code'], + 'currency_symbol' => $journal['foreign_currency_symbol'], + 'currency_decimal_places' => $journal['foreign_currency_decimal_places'], + ]; + } + $return[$foreignCurrencyId]['count']++; + $return[$foreignCurrencyId]['amount'] = bcadd($return[$foreignCurrencyId]['amount'], $journal['foreign_amount']); + } + } + + return $return; + } } diff --git a/app/Support/Http/Controllers/RequestInformation.php b/app/Support/Http/Controllers/RequestInformation.php index cca2adf519..a845d53dfc 100644 --- a/app/Support/Http/Controllers/RequestInformation.php +++ b/app/Support/Http/Controllers/RequestInformation.php @@ -59,6 +59,24 @@ trait RequestInformation return $parts['host']; } + /** + * @return string + */ + final protected function getPageName(): string // get request info + { + return str_replace('.', '_', RouteFacade::currentRouteName()); + } + + /** + * Get the specific name of a page for intro. + * + * @return string + */ + final protected function getSpecificPageName(): string // get request info + { + return null === RouteFacade::current()->parameter('objectType') ? '' : '_'.RouteFacade::current()->parameter('objectType'); + } + /** * Get a list of triggers. * @@ -115,24 +133,6 @@ trait RequestInformation return $shownDemo; } - /** - * @return string - */ - final protected function getPageName(): string // get request info - { - return str_replace('.', '_', RouteFacade::currentRouteName()); - } - - /** - * Get the specific name of a page for intro. - * - * @return string - */ - final protected function getSpecificPageName(): string // get request info - { - return null === RouteFacade::current()->parameter('objectType') ? '' : '_'.RouteFacade::current()->parameter('objectType'); - } - /** * Check if date is outside session range. * diff --git a/app/Support/Logging/AuditProcessor.php b/app/Support/Logging/AuditProcessor.php index c95482b048..12ef8f7c2d 100644 --- a/app/Support/Logging/AuditProcessor.php +++ b/app/Support/Logging/AuditProcessor.php @@ -41,7 +41,7 @@ class AuditProcessor public function __invoke(LogRecord $record): LogRecord { if (auth()->check()) { - $message = sprintf( + $message = sprintf( 'AUDIT: %s (%s (%s) -> %s:%s)', $record['message'], app('request')->ip(), diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index cb55fb25f1..c8477cdb6f 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -173,71 +173,6 @@ class Navigation return $periods; } - /** - * @param Carbon $theDate - * @param string $repeatFreq - * - * @return Carbon - */ - public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon - { - $date = clone $theDate; - - $functionMap = [ - '1D' => 'startOfDay', - 'daily' => 'startOfDay', - '1W' => 'startOfWeek', - 'week' => 'startOfWeek', - 'weekly' => 'startOfWeek', - 'month' => 'startOfMonth', - '1M' => 'startOfMonth', - 'monthly' => 'startOfMonth', - '3M' => 'firstOfQuarter', - 'quarter' => 'firstOfQuarter', - 'quarterly' => 'firstOfQuarter', - 'year' => 'startOfYear', - 'yearly' => 'startOfYear', - '1Y' => 'startOfYear', - ]; - if (array_key_exists($repeatFreq, $functionMap)) { - $function = $functionMap[$repeatFreq]; - $date->$function(); - - return $date; - } - if ('half-year' === $repeatFreq || '6M' === $repeatFreq) { - $month = $date->month; - $date->startOfYear(); - if ($month >= 7) { - $date->addMonths(6); - } - - return $date; - } - - $result = match ($repeatFreq) { - 'last7' => $date->subDays(7)->startOfDay(), - 'last30' => $date->subDays(30)->startOfDay(), - 'last90' => $date->subDays(90)->startOfDay(), - 'last365' => $date->subDays(365)->startOfDay(), - 'MTD' => $date->startOfMonth()->startOfDay(), - 'QTD' => $date->firstOfQuarter()->startOfDay(), - 'YTD' => $date->startOfYear()->startOfDay(), - default => null, - }; - if (null !== $result) { - return $result; - } - - - if ('custom' === $repeatFreq) { - return $date; // the date is already at the start. - } - Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq)); - - return $theDate; - } - /** * @param Carbon $end * @param string $repeatFreq @@ -441,29 +376,6 @@ class Navigation return $entries; } - /** - * If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year, - * method returns "Y-m". If the date difference is larger, method returns "Y". - * - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function preferredCarbonFormat(Carbon $start, Carbon $end): string - { - $format = 'Y-m-d'; - if ($start->diffInMonths($end) > 1) { - $format = 'Y-m'; - } - - if ($start->diffInMonths($end) > 12) { - $format = 'Y'; - } - - return $format; - } - /** * @param Carbon $theDate * @param string $repeatFrequency @@ -504,6 +416,29 @@ class Navigation return $date->format('Y-m-d'); } + /** + * If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year, + * method returns "Y-m". If the date difference is larger, method returns "Y". + * + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function preferredCarbonFormat(Carbon $start, Carbon $end): string + { + $format = 'Y-m-d'; + if ($start->diffInMonths($end) > 1) { + $format = 'Y-m'; + } + + if ($start->diffInMonths($end) > 12) { + $format = 'Y'; + } + + return $format; + } + /** * If the date difference between start and end is less than a month, method returns trans(config.month_and_day). If the difference is less than a year, * method returns "config.month". If the date difference is larger, method returns "config.year". @@ -597,6 +532,71 @@ class Navigation return $format; } + /** + * @param Carbon $theDate + * @param string $repeatFreq + * + * @return Carbon + */ + public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon + { + $date = clone $theDate; + + $functionMap = [ + '1D' => 'startOfDay', + 'daily' => 'startOfDay', + '1W' => 'startOfWeek', + 'week' => 'startOfWeek', + 'weekly' => 'startOfWeek', + 'month' => 'startOfMonth', + '1M' => 'startOfMonth', + 'monthly' => 'startOfMonth', + '3M' => 'firstOfQuarter', + 'quarter' => 'firstOfQuarter', + 'quarterly' => 'firstOfQuarter', + 'year' => 'startOfYear', + 'yearly' => 'startOfYear', + '1Y' => 'startOfYear', + ]; + if (array_key_exists($repeatFreq, $functionMap)) { + $function = $functionMap[$repeatFreq]; + $date->$function(); + + return $date; + } + if ('half-year' === $repeatFreq || '6M' === $repeatFreq) { + $month = $date->month; + $date->startOfYear(); + if ($month >= 7) { + $date->addMonths(6); + } + + return $date; + } + + $result = match ($repeatFreq) { + 'last7' => $date->subDays(7)->startOfDay(), + 'last30' => $date->subDays(30)->startOfDay(), + 'last90' => $date->subDays(90)->startOfDay(), + 'last365' => $date->subDays(365)->startOfDay(), + 'MTD' => $date->startOfMonth()->startOfDay(), + 'QTD' => $date->firstOfQuarter()->startOfDay(), + 'YTD' => $date->startOfYear()->startOfDay(), + default => null, + }; + if (null !== $result) { + return $result; + } + + + if ('custom' === $repeatFreq) { + return $date; // the date is already at the start. + } + Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq)); + + return $theDate; + } + /** * @param Carbon $theDate * @param string $repeatFreq diff --git a/app/Support/ParseDateString.php b/app/Support/ParseDateString.php index 02b5c8d227..ed108d802c 100644 --- a/app/Support/ParseDateString.php +++ b/app/Support/ParseDateString.php @@ -121,6 +121,183 @@ class ParseDateString throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date)); } + /** + * @param string $date + * + * @return array + */ + public function parseRange(string $date): array + { + // several types of range can be submitted + $result = [ + 'exact' => new Carbon('1984-09-17'), + ]; + switch (true) { + default: + break; + case $this->isDayRange($date): + $result = $this->parseDayRange($date); + break; + case $this->isMonthRange($date): + $result = $this->parseMonthRange($date); + break; + case $this->isYearRange($date): + $result = $this->parseYearRange($date); + break; + case $this->isMonthDayRange($date): + $result = $this->parseMonthDayRange($date); + break; + case $this->isDayYearRange($date): + $result = $this->parseDayYearRange($date); + break; + case $this->isMonthYearRange($date): + $result = $this->parseMonthYearRange($date); + break; + } + + return $result; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isDayRange(string $date): bool + { + // if regex for xxxx-xx-DD: + $pattern = '/^xxxx-xx-(0[1-9]|[12]\d|3[01])$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a day range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a day range.', $date)); + + return false; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isDayYearRange(string $date): bool + { + // if regex for YYYY-xx-DD: + $pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a day/year range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a day/year range.', $date)); + + return false; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isMonthDayRange(string $date): bool + { + // if regex for xxxx-MM-DD: + $pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a month/day range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a month/day range.', $date)); + + return false; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isMonthRange(string $date): bool + { + // if regex for xxxx-MM-xx: + $pattern = '/^xxxx-(0[1-9]|1[012])-xx$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a month range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a month range.', $date)); + + return false; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isMonthYearRange(string $date): bool + { + // if regex for YYYY-MM-xx: + $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a month/year range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a month/year range.', $date)); + + return false; + } + + /** + * @param string $date + * + * @return bool + */ + protected function isYearRange(string $date): bool + { + // if regex for YYYY-xx-xx: + $pattern = '/^(19|20)\d\d-xx-xx$/'; + if (preg_match($pattern, $date)) { + Log::debug(sprintf('"%s" is a year range.', $date)); + + return true; + } + Log::debug(sprintf('"%s" is not a year range.', $date)); + + return false; + } + + /** + * format of string is xxxx-xx-DD + * + * @param string $date + * + * @return array + */ + protected function parseDayRange(string $date): array + { + $parts = explode('-', $date); + + return [ + 'day' => $parts[2], + ]; + } + + /** + * @param string $date + * + * @return Carbon + */ + protected function parseDefaultDate(string $date): Carbon + { + return Carbon::createFromFormat('Y-m-d', $date); + } + /** * @param string $keyword * @@ -146,13 +323,38 @@ class ParseDateString } /** + * format of string is xxxx-MM-xx + * * @param string $date * - * @return Carbon + * @return array */ - protected function parseDefaultDate(string $date): Carbon + protected function parseMonthRange(string $date): array { - return Carbon::createFromFormat('Y-m-d', $date); + Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date)); + $parts = explode('-', $date); + + return [ + 'month' => $parts[1], + ]; + } + + /** + * format of string is YYYY-MM-xx + * + * @param string $date + * + * @return array + */ + protected function parseMonthYearRange(string $date): array + { + Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date)); + $parts = explode('-', $date); + + return [ + 'year' => $parts[0], + 'month' => $parts[1], + ]; } /** @@ -209,133 +411,6 @@ class ParseDateString return $today; } - /** - * @param string $date - * - * @return array - */ - public function parseRange(string $date): array - { - // several types of range can be submitted - $result = [ - 'exact' => new Carbon('1984-09-17'), - ]; - switch (true) { - default: - break; - case $this->isDayRange($date): - $result = $this->parseDayRange($date); - break; - case $this->isMonthRange($date): - $result = $this->parseMonthRange($date); - break; - case $this->isYearRange($date): - $result = $this->parseYearRange($date); - break; - case $this->isMonthDayRange($date): - $result = $this->parseMonthDayRange($date); - break; - case $this->isDayYearRange($date): - $result = $this->parseDayYearRange($date); - break; - case $this->isMonthYearRange($date): - $result = $this->parseMonthYearRange($date); - break; - } - - return $result; - } - - /** - * @param string $date - * - * @return bool - */ - protected function isDayRange(string $date): bool - { - // if regex for xxxx-xx-DD: - $pattern = '/^xxxx-xx-(0[1-9]|[12]\d|3[01])$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a day range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a day range.', $date)); - - return false; - } - - /** - * format of string is xxxx-xx-DD - * - * @param string $date - * - * @return array - */ - protected function parseDayRange(string $date): array - { - $parts = explode('-', $date); - - return [ - 'day' => $parts[2], - ]; - } - - /** - * @param string $date - * - * @return bool - */ - protected function isMonthRange(string $date): bool - { - // if regex for xxxx-MM-xx: - $pattern = '/^xxxx-(0[1-9]|1[012])-xx$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a month range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a month range.', $date)); - - return false; - } - - /** - * format of string is xxxx-MM-xx - * - * @param string $date - * - * @return array - */ - protected function parseMonthRange(string $date): array - { - Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date)); - $parts = explode('-', $date); - - return [ - 'month' => $parts[1], - ]; - } - - /** - * @param string $date - * - * @return bool - */ - protected function isYearRange(string $date): bool - { - // if regex for YYYY-xx-xx: - $pattern = '/^(19|20)\d\d-xx-xx$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a year range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a year range.', $date)); - - return false; - } - /** * format of string is YYYY-xx-xx * @@ -353,62 +428,6 @@ class ParseDateString ]; } - /** - * @param string $date - * - * @return bool - */ - protected function isMonthDayRange(string $date): bool - { - // if regex for xxxx-MM-DD: - $pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a month/day range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a month/day range.', $date)); - - return false; - } - - /** - * format of string is xxxx-MM-DD - * - * @param string $date - * - * @return array - */ - private function parseMonthDayRange(string $date): array - { - Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date)); - $parts = explode('-', $date); - - return [ - 'month' => $parts[1], - 'day' => $parts[2], - ]; - } - - /** - * @param string $date - * - * @return bool - */ - protected function isDayYearRange(string $date): bool - { - // if regex for YYYY-xx-DD: - $pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a day/year range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a day/year range.', $date)); - - return false; - } - /** * format of string is YYYY-xx-DD * @@ -428,39 +447,20 @@ class ParseDateString } /** - * @param string $date - * - * @return bool - */ - protected function isMonthYearRange(string $date): bool - { - // if regex for YYYY-MM-xx: - $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/'; - if (preg_match($pattern, $date)) { - Log::debug(sprintf('"%s" is a month/year range.', $date)); - - return true; - } - Log::debug(sprintf('"%s" is not a month/year range.', $date)); - - return false; - } - - /** - * format of string is YYYY-MM-xx + * format of string is xxxx-MM-DD * * @param string $date * * @return array */ - protected function parseMonthYearRange(string $date): array + private function parseMonthDayRange(string $date): array { - Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date)); + Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date)); $parts = explode('-', $date); return [ - 'year' => $parts[0], 'month' => $parts[1], + 'day' => $parts[2], ]; } } diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 697b7fd3f5..7b15c0a52b 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -51,6 +51,55 @@ class Preferences return Preference::where('user_id', $user->id)->get(); } + /** + * @param User $user + * @param string $search + * + * @return Collection + */ + public function beginsWith(User $user, string $search): Collection + { + return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get(); + } + + /** + * @param string $name + * + * @return bool + * @throws FireflyException + */ + public function delete(string $name): bool + { + $fullName = sprintf('preference%s%s', auth()->user()->id, $name); + if (Cache::has($fullName)) { + Cache::forget($fullName); + } + Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete(); + + return true; + } + + /** + * @param string $name + * + * @return Collection + */ + public function findByName(string $name): Collection + { + return Preference::where('name', $name)->get(); + } + + /** + * @param User $user + * @param string $name + */ + public function forget(User $user, string $name): void + { + $key = sprintf('preference%s%s', $user->id, $name); + Cache::forget($key); + Cache::put($key, '', 5); + } + /** * @param string $name * @param mixed $default @@ -72,6 +121,29 @@ class Preferences return $this->getForUser($user, $name, $default); } + /** + * @param User $user + * @param array $list + * + * @return array + */ + public function getArrayForUser(User $user, array $list): array + { + $result = []; + $preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']); + /** @var Preference $preference */ + foreach ($preferences as $preference) { + $result[$preference->name] = $preference->data; + } + foreach ($list as $name) { + if (!array_key_exists($name, $result)) { + $result[$name] = null; + } + } + + return $result; + } + /** * @param User $user * @param string $name @@ -100,117 +172,6 @@ class Preferences return $this->setForUser($user, $name, $default); } - /** - * @param string $name - * - * @return bool - * @throws FireflyException - */ - public function delete(string $name): bool - { - $fullName = sprintf('preference%s%s', auth()->user()->id, $name); - if (Cache::has($fullName)) { - Cache::forget($fullName); - } - Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete(); - - return true; - } - - /** - * @param User $user - * @param string $name - */ - public function forget(User $user, string $name): void - { - $key = sprintf('preference%s%s', $user->id, $name); - Cache::forget($key); - Cache::put($key, '', 5); - } - - /** - * @param User $user - * @param string $name - * @param mixed $value - * - * @return Preference - * @throws FireflyException - */ - public function setForUser(User $user, string $name, $value): Preference - { - $fullName = sprintf('preference%s%s', $user->id, $name); - Cache::forget($fullName); - /** @var Preference|null $pref */ - $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); - - if (null !== $pref && null === $value) { - $pref->delete(); - - return new Preference(); - } - if (null === $value) { - return new Preference(); - } - if (null === $pref) { - $pref = new Preference(); - $pref->user_id = $user->id; - $pref->name = $name; - } - $pref->data = $value; - try { - $pref->save(); - } catch (PDOException $e) { - throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e); - } - Cache::forever($fullName, $pref); - - return $pref; - } - - /** - * @param User $user - * @param string $search - * - * @return Collection - */ - public function beginsWith(User $user, string $search): Collection - { - return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get(); - } - - /** - * @param string $name - * - * @return Collection - */ - public function findByName(string $name): Collection - { - return Preference::where('name', $name)->get(); - } - - /** - * @param User $user - * @param array $list - * - * @return array - */ - public function getArrayForUser(User $user, array $list): array - { - $result = []; - $preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']); - /** @var Preference $preference */ - foreach ($preferences as $preference) { - $result[$preference->name] = $preference->data; - } - foreach ($list as $name) { - if (!array_key_exists($name, $result)) { - $result[$name] = null; - } - } - - return $result; - } - /** * @param string $name * @param mixed $default @@ -295,4 +256,43 @@ class Preferences return $this->setForUser(auth()->user(), $name, $value); } + + /** + * @param User $user + * @param string $name + * @param mixed $value + * + * @return Preference + * @throws FireflyException + */ + public function setForUser(User $user, string $name, $value): Preference + { + $fullName = sprintf('preference%s%s', $user->id, $name); + Cache::forget($fullName); + /** @var Preference|null $pref */ + $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); + + if (null !== $pref && null === $value) { + $pref->delete(); + + return new Preference(); + } + if (null === $value) { + return new Preference(); + } + if (null === $pref) { + $pref = new Preference(); + $pref->user_id = $user->id; + $pref->name = $name; + } + $pref->data = $value; + try { + $pref->save(); + } catch (PDOException $e) { + throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e); + } + Cache::forever($fullName, $pref); + + return $pref; + } } diff --git a/app/Support/Report/Budget/BudgetReportGenerator.php b/app/Support/Report/Budget/BudgetReportGenerator.php index 8c79b5faa8..94078cd593 100644 --- a/app/Support/Report/Budget/BudgetReportGenerator.php +++ b/app/Support/Report/Budget/BudgetReportGenerator.php @@ -91,48 +91,6 @@ class BudgetReportGenerator } } - /** - * Process each row of expenses collected for the "Account per budget" partial - * - * @param array $expenses - */ - private function processExpenses(array $expenses): void - { - foreach ($expenses['budgets'] as $budget) { - $this->processBudgetExpenses($expenses, $budget); - } - } - - /** - * Process each set of transactions for each row of expenses. - * - * @param array $expenses - * @param array $budget - */ - private function processBudgetExpenses(array $expenses, array $budget): void - { - $budgetId = (int)$budget['id']; - $currencyId = (int)$expenses['currency_id']; - foreach ($budget['transaction_journals'] as $journal) { - $sourceAccountId = $journal['source_account_id']; - - $this->report[$sourceAccountId]['currencies'][$currencyId] - = $this->report[$sourceAccountId]['currencies'][$currencyId] ?? [ - 'currency_id' => $expenses['currency_id'], - 'currency_symbol' => $expenses['currency_symbol'], - 'currency_name' => $expenses['currency_name'], - 'currency_decimal_places' => $expenses['currency_decimal_places'], - 'budgets' => [], - ]; - - $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] - = $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] ?? '0'; - - $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] - = bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], $journal['amount']); - } - } - /** * Generates the data necessary to create the card that displays * the budget overview in the general report. @@ -149,6 +107,60 @@ class BudgetReportGenerator $this->percentageReport(); } + /** + * @return array + */ + public function getReport(): array + { + return $this->report; + } + + /** + * @param Collection $accounts + */ + public function setAccounts(Collection $accounts): void + { + $this->accounts = $accounts; + } + + /** + * @param Collection $budgets + */ + public function setBudgets(Collection $budgets): void + { + $this->budgets = $budgets; + } + + /** + * @param Carbon $end + */ + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + /** + * @param Carbon $start + */ + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * @param User $user + * @throws FireflyException + * @throws JsonException + */ + public function setUser(User $user): void + { + $this->repository->setUser($user); + $this->blRepository->setUser($user); + $this->opsRepository->setUser($user); + $this->nbRepository->setUser($user); + $this->currency = app('amount')->getDefaultCurrencyByUser($user); + } + /** * Start the budgets block on the default report by processing every budget. */ @@ -161,82 +173,6 @@ class BudgetReportGenerator } } - /** - * Process expenses etc. for a single budget for the budgets block on the default report. - * - * @param Budget $budget - */ - private function processBudget(Budget $budget): void - { - $budgetId = (int)$budget->id; - $this->report['budgets'][$budgetId] = $this->report['budgets'][$budgetId] ?? [ - 'budget_id' => $budgetId, - 'budget_name' => $budget->name, - 'no_budget' => false, - 'budget_limits' => [], - ]; - - // get all budget limits for budget in period: - $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); - /** @var BudgetLimit $limit */ - foreach ($limits as $limit) { - $this->processLimit($budget, $limit); - } - } - - /** - * Process a single budget limit for the budgets block on the default report. - * - * @param Budget $budget - * @param BudgetLimit $limit - */ - private function processLimit(Budget $budget, BudgetLimit $limit): void - { - $budgetId = (int)$budget->id; - $limitId = (int)$limit->id; - $limitCurrency = $limit->transactionCurrency ?? $this->currency; - $currencyId = (int)$limitCurrency->id; - $expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection([$budget])); - $spent = $expenses[$currencyId]['sum'] ?? '0'; - $left = -1 === bccomp(bcadd($limit->amount, $spent), '0') ? '0' : bcadd($limit->amount, $spent); - $overspent = 1 === bccomp(bcmul($spent, '-1'), $limit->amount) ? bcadd($spent, $limit->amount) : '0'; - - $this->report['budgets'][$budgetId]['budget_limits'][$limitId] = $this->report['budgets'][$budgetId]['budget_limits'][$limitId] ?? [ - 'budget_limit_id' => $limitId, - 'start_date' => $limit->start_date, - 'end_date' => $limit->end_date, - 'budgeted' => $limit->amount, - 'budgeted_pct' => '0', - 'spent' => $spent, - 'spent_pct' => '0', - 'left' => $left, - 'overspent' => $overspent, - 'currency_id' => $currencyId, - 'currency_code' => $limitCurrency->code, - 'currency_name' => $limitCurrency->name, - 'currency_symbol' => $limitCurrency->symbol, - 'currency_decimal_places' => $limitCurrency->decimal_places, - ]; - - // make sum information: - $this->report['sums'][$currencyId] - = $this->report['sums'][$currencyId] ?? [ - 'budgeted' => '0', - 'spent' => '0', - 'left' => '0', - 'overspent' => '0', - 'currency_id' => $currencyId, - 'currency_code' => $limitCurrency->code, - 'currency_name' => $limitCurrency->name, - 'currency_symbol' => $limitCurrency->symbol, - 'currency_decimal_places' => $limitCurrency->decimal_places, - ]; - $this->report['sums'][$currencyId]['budgeted'] = bcadd($this->report['sums'][$currencyId]['budgeted'], $limit->amount); - $this->report['sums'][$currencyId]['spent'] = bcadd($this->report['sums'][$currencyId]['spent'], $spent); - $this->report['sums'][$currencyId]['left'] = bcadd($this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent)); - $this->report['sums'][$currencyId]['overspent'] = bcadd($this->report['sums'][$currencyId]['overspent'], $overspent); - } - /** * Calculate the expenses for transactions without a budget. Part of the "budgets" block of the default report. */ @@ -322,56 +258,120 @@ class BudgetReportGenerator } /** - * @return array + * Process expenses etc. for a single budget for the budgets block on the default report. + * + * @param Budget $budget */ - public function getReport(): array + private function processBudget(Budget $budget): void { - return $this->report; + $budgetId = (int)$budget->id; + $this->report['budgets'][$budgetId] = $this->report['budgets'][$budgetId] ?? [ + 'budget_id' => $budgetId, + 'budget_name' => $budget->name, + 'no_budget' => false, + 'budget_limits' => [], + ]; + + // get all budget limits for budget in period: + $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); + /** @var BudgetLimit $limit */ + foreach ($limits as $limit) { + $this->processLimit($budget, $limit); + } } /** - * @param Collection $accounts + * Process each set of transactions for each row of expenses. + * + * @param array $expenses + * @param array $budget */ - public function setAccounts(Collection $accounts): void + private function processBudgetExpenses(array $expenses, array $budget): void { - $this->accounts = $accounts; + $budgetId = (int)$budget['id']; + $currencyId = (int)$expenses['currency_id']; + foreach ($budget['transaction_journals'] as $journal) { + $sourceAccountId = $journal['source_account_id']; + + $this->report[$sourceAccountId]['currencies'][$currencyId] + = $this->report[$sourceAccountId]['currencies'][$currencyId] ?? [ + 'currency_id' => $expenses['currency_id'], + 'currency_symbol' => $expenses['currency_symbol'], + 'currency_name' => $expenses['currency_name'], + 'currency_decimal_places' => $expenses['currency_decimal_places'], + 'budgets' => [], + ]; + + $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] + = $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] ?? '0'; + + $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] + = bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], $journal['amount']); + } } /** - * @param Collection $budgets + * Process each row of expenses collected for the "Account per budget" partial + * + * @param array $expenses */ - public function setBudgets(Collection $budgets): void + private function processExpenses(array $expenses): void { - $this->budgets = $budgets; + foreach ($expenses['budgets'] as $budget) { + $this->processBudgetExpenses($expenses, $budget); + } } /** - * @param Carbon $end + * Process a single budget limit for the budgets block on the default report. + * + * @param Budget $budget + * @param BudgetLimit $limit */ - public function setEnd(Carbon $end): void + private function processLimit(Budget $budget, BudgetLimit $limit): void { - $this->end = $end; - } + $budgetId = (int)$budget->id; + $limitId = (int)$limit->id; + $limitCurrency = $limit->transactionCurrency ?? $this->currency; + $currencyId = (int)$limitCurrency->id; + $expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection([$budget])); + $spent = $expenses[$currencyId]['sum'] ?? '0'; + $left = -1 === bccomp(bcadd($limit->amount, $spent), '0') ? '0' : bcadd($limit->amount, $spent); + $overspent = 1 === bccomp(bcmul($spent, '-1'), $limit->amount) ? bcadd($spent, $limit->amount) : '0'; - /** - * @param Carbon $start - */ - public function setStart(Carbon $start): void - { - $this->start = $start; - } + $this->report['budgets'][$budgetId]['budget_limits'][$limitId] = $this->report['budgets'][$budgetId]['budget_limits'][$limitId] ?? [ + 'budget_limit_id' => $limitId, + 'start_date' => $limit->start_date, + 'end_date' => $limit->end_date, + 'budgeted' => $limit->amount, + 'budgeted_pct' => '0', + 'spent' => $spent, + 'spent_pct' => '0', + 'left' => $left, + 'overspent' => $overspent, + 'currency_id' => $currencyId, + 'currency_code' => $limitCurrency->code, + 'currency_name' => $limitCurrency->name, + 'currency_symbol' => $limitCurrency->symbol, + 'currency_decimal_places' => $limitCurrency->decimal_places, + ]; - /** - * @param User $user - * @throws FireflyException - * @throws JsonException - */ - public function setUser(User $user): void - { - $this->repository->setUser($user); - $this->blRepository->setUser($user); - $this->opsRepository->setUser($user); - $this->nbRepository->setUser($user); - $this->currency = app('amount')->getDefaultCurrencyByUser($user); + // make sum information: + $this->report['sums'][$currencyId] + = $this->report['sums'][$currencyId] ?? [ + 'budgeted' => '0', + 'spent' => '0', + 'left' => '0', + 'overspent' => '0', + 'currency_id' => $currencyId, + 'currency_code' => $limitCurrency->code, + 'currency_name' => $limitCurrency->name, + 'currency_symbol' => $limitCurrency->symbol, + 'currency_decimal_places' => $limitCurrency->decimal_places, + ]; + $this->report['sums'][$currencyId]['budgeted'] = bcadd($this->report['sums'][$currencyId]['budgeted'], $limit->amount); + $this->report['sums'][$currencyId]['spent'] = bcadd($this->report['sums'][$currencyId]['spent'], $spent); + $this->report['sums'][$currencyId]['left'] = bcadd($this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent)); + $this->report['sums'][$currencyId]['overspent'] = bcadd($this->report['sums'][$currencyId]['overspent'], $overspent); } } diff --git a/app/Support/Report/Category/CategoryReportGenerator.php b/app/Support/Report/Category/CategoryReportGenerator.php index 93097dd029..abbd6df6fa 100644 --- a/app/Support/Report/Category/CategoryReportGenerator.php +++ b/app/Support/Report/Category/CategoryReportGenerator.php @@ -86,45 +86,36 @@ class CategoryReportGenerator } /** - * Process one of the spent arrays from the operations method. - * - * @param array $data + * @param Collection $accounts */ - private function processOpsArray(array $data): void + public function setAccounts(Collection $accounts): void { - /** - * @var int $currencyId - * @var array $currencyRow - */ - foreach ($data as $currencyId => $currencyRow) { - $this->processCurrencyArray($currencyId, $currencyRow); - } + $this->accounts = $accounts; } /** - * @param int $currencyId - * @param array $currencyRow + * @param Carbon $end */ - private function processCurrencyArray(int $currencyId, array $currencyRow): void + public function setEnd(Carbon $end): void { - $this->report['sums'][$currencyId] = $this->report['sums'][$currencyId] ?? [ - 'spent' => '0', - 'earned' => '0', - 'sum' => '0', - 'currency_id' => $currencyRow['currency_id'], - 'currency_symbol' => $currencyRow['currency_symbol'], - 'currency_name' => $currencyRow['currency_name'], - 'currency_code' => $currencyRow['currency_code'], - 'currency_decimal_places' => $currencyRow['currency_decimal_places'], - ]; + $this->end = $end; + } - /** - * @var int $categoryId - * @var array $categoryRow - */ - foreach ($currencyRow['categories'] as $categoryId => $categoryRow) { - $this->processCategoryRow($currencyId, $currencyRow, $categoryId, $categoryRow); - } + /** + * @param Carbon $start + */ + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->noCatRepository->setUser($user); + $this->opsRepository->setUser($user); } /** @@ -179,35 +170,44 @@ class CategoryReportGenerator } /** - * @param Collection $accounts + * @param int $currencyId + * @param array $currencyRow */ - public function setAccounts(Collection $accounts): void + private function processCurrencyArray(int $currencyId, array $currencyRow): void { - $this->accounts = $accounts; + $this->report['sums'][$currencyId] = $this->report['sums'][$currencyId] ?? [ + 'spent' => '0', + 'earned' => '0', + 'sum' => '0', + 'currency_id' => $currencyRow['currency_id'], + 'currency_symbol' => $currencyRow['currency_symbol'], + 'currency_name' => $currencyRow['currency_name'], + 'currency_code' => $currencyRow['currency_code'], + 'currency_decimal_places' => $currencyRow['currency_decimal_places'], + ]; + + /** + * @var int $categoryId + * @var array $categoryRow + */ + foreach ($currencyRow['categories'] as $categoryId => $categoryRow) { + $this->processCategoryRow($currencyId, $currencyRow, $categoryId, $categoryRow); + } } /** - * @param Carbon $end + * Process one of the spent arrays from the operations method. + * + * @param array $data */ - public function setEnd(Carbon $end): void + private function processOpsArray(array $data): void { - $this->end = $end; - } - - /** - * @param Carbon $start - */ - public function setStart(Carbon $start): void - { - $this->start = $start; - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->noCatRepository->setUser($user); - $this->opsRepository->setUser($user); + /** + * @var int $currencyId + * @var array $currencyRow + */ + foreach ($data as $currencyId => $currencyRow) { + $this->processCurrencyArray($currencyId, $currencyRow); + } } } diff --git a/app/Support/Repositories/Administration/AdministrationTrait.php b/app/Support/Repositories/Administration/AdministrationTrait.php index 046a30e2ae..16060af73d 100644 --- a/app/Support/Repositories/Administration/AdministrationTrait.php +++ b/app/Support/Repositories/Administration/AdministrationTrait.php @@ -34,8 +34,8 @@ use Illuminate\Contracts\Auth\Authenticatable; */ trait AdministrationTrait { - protected User $user; protected ?int $administrationId = null; + protected User $user; protected ?UserGroup $userGroup = null; /** @@ -56,6 +56,13 @@ trait AdministrationTrait $this->refreshAdministration(); } + public function setUser(Authenticatable|User|null $user): void + { + if (null !== $user) { + $this->user = $user; + } + } + /** * @return void */ @@ -63,8 +70,8 @@ trait AdministrationTrait { if (null !== $this->administrationId) { $memberships = GroupMembership::where('user_id', $this->user->id) - ->where('user_group_id', $this->administrationId) - ->count(); + ->where('user_group_id', $this->administrationId) + ->count(); if (0 === $memberships) { throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $this->administrationId)); } @@ -73,12 +80,4 @@ trait AdministrationTrait } throw new FireflyException(sprintf('Cannot validate administration for user #%d', $this->user->id)); } - - - public function setUser(Authenticatable|User|null $user): void - { - if (null !== $user) { - $this->user = $user; - } - } } diff --git a/app/Support/Request/AppendsLocationData.php b/app/Support/Request/AppendsLocationData.php index d37584b759..373be35b11 100644 --- a/app/Support/Request/AppendsLocationData.php +++ b/app/Support/Request/AppendsLocationData.php @@ -30,6 +30,16 @@ use Illuminate\Support\Facades\Log; */ trait AppendsLocationData { + /** + * Abstract method stolen from "InteractsWithInput". + * + * @param null $key + * @param bool $default + * + * @return mixed + */ + abstract public function boolean($key = null, $default = false); + /** * Abstract method. * @@ -39,6 +49,22 @@ trait AppendsLocationData */ abstract public function has($key); + /** + * Abstract method. + * + * @return string + */ + abstract public function method(); + + /** + * Abstract method. + * + * @param mixed ...$patterns + * + * @return mixed + */ + abstract public function routeIs(...$patterns); + /** * Read the submitted Request data and add new or updated Location data to the array. * @@ -114,6 +140,27 @@ trait AppendsLocationData return sprintf('%s_%s', $prefix, $key); } + /** + * @param string|null $prefix + * + * @return bool + */ + private function isValidEmptyPUT(?string $prefix): bool + { + $longitudeKey = $this->getLocationKey($prefix, 'longitude'); + $latitudeKey = $this->getLocationKey($prefix, 'latitude'); + $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level'); + + return ( + null === $this->get($longitudeKey) + && null === $this->get($latitudeKey) + && null === $this->get($zoomLevelKey)) + && ( + 'PUT' === $this->method() + || ('POST' === $this->method() && $this->routeIs('*.update')) + ); + } + /** * @param string|null $prefix * @@ -157,32 +204,6 @@ trait AppendsLocationData return false; } - /** - * Abstract method. - * - * @return string - */ - abstract public function method(); - - /** - * Abstract method. - * - * @param mixed ...$patterns - * - * @return mixed - */ - abstract public function routeIs(...$patterns); - - /** - * Abstract method stolen from "InteractsWithInput". - * - * @param null $key - * @param bool $default - * - * @return mixed - */ - abstract public function boolean($key = null, $default = false); - /** * @param string|null $prefix * @@ -227,25 +248,4 @@ trait AppendsLocationData return false; } - - /** - * @param string|null $prefix - * - * @return bool - */ - private function isValidEmptyPUT(?string $prefix): bool - { - $longitudeKey = $this->getLocationKey($prefix, 'longitude'); - $latitudeKey = $this->getLocationKey($prefix, 'latitude'); - $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level'); - - return ( - null === $this->get($longitudeKey) - && null === $this->get($latitudeKey) - && null === $this->get($zoomLevelKey)) - && ( - 'PUT' === $this->method() - || ('POST' === $this->method() && $this->routeIs('*.update')) - ); - } } diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index cb207d6377..3f261c8f89 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -33,44 +33,6 @@ use Illuminate\Support\Facades\Log; */ trait ConvertsDataTypes { - /** - * Return integer value. - * - * @param string $field - * - * @return int - */ - public function convertInteger(string $field): int - { - return (int)$this->get($field); - } - - /** - * Abstract method that always exists in the Request classes that use this - * trait, OR a stub needs to be added by any other class that uses this train. - * - * @param string $key - * @param mixed|null $default - * @return mixed - */ - abstract public function get(string $key, mixed $default = null): mixed; - - /** - * Return string value. - * - * @param string $field - * - * @return string - */ - public function convertString(string $field): string - { - $entry = $this->get($field); - if (!is_scalar($entry)) { - return ''; - } - return $this->clearString((string)$entry, false); - } - /** * @param string|null $string * @param bool $keepNewlines @@ -152,6 +114,53 @@ trait ConvertsDataTypes return trim($string); } + /** + * Return integer value. + * + * @param string $field + * + * @return int + */ + public function convertInteger(string $field): int + { + return (int)$this->get($field); + } + + /** + * Return string value. + * + * @param string $field + * + * @return string + */ + public function convertString(string $field): string + { + $entry = $this->get($field); + if (!is_scalar($entry)) { + return ''; + } + return $this->clearString((string)$entry, false); + } + + /** + * Abstract method that always exists in the Request classes that use this + * trait, OR a stub needs to be added by any other class that uses this train. + * + * @param string $key + * @param mixed|null $default + * @return mixed + */ + abstract public function get(string $key, mixed $default = null): mixed; + + /** + * Abstract method that always exists in the Request classes that use this + * trait, OR a stub needs to be added by any other class that uses this train. + * + * @param mixed $key + * @return mixed + */ + abstract public function has($key); + /** * Return string value with newlines. * @@ -210,6 +219,44 @@ trait ConvertsDataTypes return false; } + protected function convertDateTime(?string $string): ?Carbon + { + $value = $this->get($string); + if (null === $value) { + return null; + } + if ('' === $value) { + return null; + } + if (10 === strlen($value)) { + // probably a date format. + try { + $carbon = Carbon::createFromFormat('Y-m-d', $value); + } catch (InvalidDateException $e) { + Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage())); + return null; + } catch (InvalidFormatException $e) { + Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage())); + + return null; + } + return $carbon; + } + // is an atom string, I hope? + try { + $carbon = Carbon::parse($value); + } catch (InvalidDateException $e) { + Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage())); + + return null; + } catch (InvalidFormatException $e) { + Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage())); + + return null; + } + return $carbon; + } + /** * Return floating value. * @@ -256,44 +303,6 @@ trait ConvertsDataTypes return $carbon; } - protected function convertDateTime(?string $string): ?Carbon - { - $value = $this->get($string); - if (null === $value) { - return null; - } - if ('' === $value) { - return null; - } - if (10 === strlen($value)) { - // probably a date format. - try { - $carbon = Carbon::createFromFormat('Y-m-d', $value); - } catch (InvalidDateException $e) { - Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage())); - return null; - } catch (InvalidFormatException $e) { - Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage())); - - return null; - } - return $carbon; - } - // is an atom string, I hope? - try { - $carbon = Carbon::parse($value); - } catch (InvalidDateException $e) { - Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage())); - - return null; - } catch (InvalidFormatException $e) { - Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage())); - - return null; - } - return $carbon; - } - /** * Returns all data in the request, or omits the field if not set, * according to the config from the request. This is the way. @@ -315,15 +324,6 @@ trait ConvertsDataTypes return $return; } - /** - * Abstract method that always exists in the Request classes that use this - * trait, OR a stub needs to be added by any other class that uses this train. - * - * @param mixed $key - * @return mixed - */ - abstract public function has($key); - /** * Return date or NULL. * diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 659550186e..76c9c7d8b1 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -74,11 +74,11 @@ class OperatorQuerySearch implements SearchInterface private int $limit; private Collection $operators; private int $page; - private array $prohibitedWords; - private float $startTime; - private TagRepositoryInterface $tagRepository; - private array $validOperators; - private array $words; + private array $prohibitedWords; + private float $startTime; + private TagRepositoryInterface $tagRepository; + private array $validOperators; + private array $words; /** * OperatorQuerySearch constructor. @@ -104,6 +104,39 @@ class OperatorQuerySearch implements SearchInterface $this->currencyRepository = app(CurrencyRepositoryInterface::class); } + /** + * @param string $operator + * + * @return string + * @throws FireflyException + */ + public static function getRootOperator(string $operator): string + { + $original = $operator; + // if the string starts with "-" (not), we can remove it and recycle + // the configuration from the original operator. + if (str_starts_with($operator, '-')) { + $operator = substr($operator, 1); + } + + $config = config(sprintf('search.operators.%s', $operator)); + if (null === $config) { + throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator)); + } + if (true === $config['alias']) { + $return = $config['alias_for']; + if (str_starts_with($original, '-')) { + $return = sprintf('-%s', $config['alias_for']); + } + Log::debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return)); + + return $return; + } + Log::debug(sprintf('"%s" is not an alias.', $operator)); + + return $original; + } + /** * @return array */ @@ -128,6 +161,14 @@ class OperatorQuerySearch implements SearchInterface return $this->operators; } + /** + * @return array + */ + public function getWords(): array + { + return $this->words; + } + /** * @inheritDoc */ @@ -169,6 +210,97 @@ class OperatorQuerySearch implements SearchInterface $this->collector->excludeSearchWords($this->prohibitedWords); } + /** + * @inheritDoc + */ + public function searchTime(): float + { + return microtime(true) - $this->startTime; + } + + /** + * @inheritDoc + */ + public function searchTransactions(): LengthAwarePaginator + { + if (0 === count($this->getWords()) && 0 === count($this->getOperators())) { + return new LengthAwarePaginator([], 0, 5, 1); + } + + return $this->collector->getPaginatedGroups(); + } + + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $this->date = $date; + } + + /** + * @param int $limit + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + $this->collector->setLimit($this->limit); + } + + /** + * @inheritDoc + */ + public function setPage(int $page): void + { + $this->page = $page; + $this->collector->setPage($this->page); + } + + /** + * @inheritDoc + */ + public function setUser(User $user): void + { + $this->accountRepository->setUser($user); + $this->billRepository->setUser($user); + $this->categoryRepository->setUser($user); + $this->budgetRepository->setUser($user); + $this->tagRepository->setUser($user); + $this->collector = app(GroupCollectorInterface::class); + $this->collector->setUser($user); + $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); + + $this->setLimit((int)app('preferences')->getForUser($user, 'listPageSize', 50)->data); + } + + /** + * @param string $value + * + * @return TransactionCurrency|null + */ + private function findCurrency(string $value): ?TransactionCurrency + { + if (str_contains($value, '(') && str_contains($value, ')')) { + // bad method to split and get the currency code: + $parts = explode(' ', $value); + $value = trim($parts[count($parts) - 1], "() \t\n\r\0\x0B"); + } + $result = $this->currencyRepository->findByCodeNull($value); + if (null === $result) { + $result = $this->currencyRepository->findByNameNull($value); + } + + return $result; + } + + /** + * @return Account + */ + private function getCashAccount(): Account + { + return $this->accountRepository->getCashAccount(); + } + /** * @param Node $searchNode * @@ -238,6 +370,592 @@ class OperatorQuerySearch implements SearchInterface } } + /** + * @param string $value + * + * @return array + * @throws FireflyException + */ + private function parseDateRange(string $value): array + { + $parser = new ParseDateString(); + if ($parser->isDateRange($value)) { + return $parser->parseRange($value); + } + $parsedDate = $parser->parseDate($value); + + return [ + 'exact' => $parsedDate, + ]; + } + + /** + * searchDirection: 1 = source (default), 2 = destination, 3 = both + * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is + * + * @param string $value + * @param int $searchDirection + * @param int $stringPosition + * @param bool $prohibited + */ + private function searchAccount(string $value, int $searchDirection, int $stringPosition, bool $prohibited = false): void + { + Log::debug(sprintf('searchAccount("%s", %d, %d)', $value, $stringPosition, $searchDirection)); + + // search direction (default): for source accounts + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; + $collectorMethod = 'setSourceAccounts'; + if ($prohibited) { + $collectorMethod = 'excludeSourceAccounts'; + } + + // search direction: for destination accounts + if (2 === $searchDirection) { + // destination can be + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; + $collectorMethod = 'setDestinationAccounts'; + if ($prohibited) { + $collectorMethod = 'excludeDestinationAccounts'; + } + } + // either account could be: + if (3 === $searchDirection) { + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; + $collectorMethod = 'setAccounts'; + if ($prohibited) { + $collectorMethod = 'excludeAccounts'; + } + } + // string position (default): starts with: + $stringMethod = 'str_starts_with'; + + // string position: ends with: + if (2 === $stringPosition) { + $stringMethod = 'str_ends_with'; + } + if (3 === $stringPosition) { + $stringMethod = 'str_contains'; + } + if (4 === $stringPosition) { + $stringMethod = 'str_is_equal'; + } + + // get accounts: + $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 1337); + if (0 === $accounts->count()) { + Log::debug('Found zero accounts, search for non existing account, NO results will be returned.'); + $this->collector->findNothing(); + + return; + } + Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count())); + $filtered = $accounts->filter( + function (Account $account) use ($value, $stringMethod) { + return $stringMethod(strtolower($account->name), strtolower($value)); + } + ); + + if (0 === $filtered->count()) { + Log::debug('Left with zero accounts, so cannot find anything, NO results will be returned.'); + $this->collector->findNothing(); + + return; + } + Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod)); + $this->collector->$collectorMethod($filtered); + } + + /** + * searchDirection: 1 = source (default), 2 = destination, 3 = both + * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is + * + * @param string $value + * @param int $searchDirection + * @param int $stringPosition + * @param bool $prohibited + */ + private function searchAccountNr(string $value, int $searchDirection, int $stringPosition, bool $prohibited = false): void + { + Log::debug(sprintf('searchAccountNr(%s, %d, %d)', $value, $searchDirection, $stringPosition)); + + // search direction (default): for source accounts + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; + $collectorMethod = 'setSourceAccounts'; + if (true === $prohibited) { + $collectorMethod = 'excludeSourceAccounts'; + } + + // search direction: for destination accounts + if (2 === $searchDirection) { + // destination can be + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; + $collectorMethod = 'setDestinationAccounts'; + if (true === $prohibited) { + $collectorMethod = 'excludeDestinationAccounts'; + } + } + + // either account could be: + if (3 === $searchDirection) { + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; + $collectorMethod = 'setAccounts'; + if (true === $prohibited) { + $collectorMethod = 'excludeAccounts'; + } + } + + // string position (default): starts with: + $stringMethod = 'str_starts_with'; + + // string position: ends with: + if (2 === $stringPosition) { + $stringMethod = 'str_ends_with'; + } + if (3 === $stringPosition) { + $stringMethod = 'str_contains'; + } + if (4 === $stringPosition) { + $stringMethod = 'str_is_equal'; + } + + // search for accounts: + $accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 1337); + if (0 === $accounts->count()) { + Log::debug('Found zero accounts, search for invalid account.'); + $this->collector->findNothing(); + + return; + } + + // if found, do filter + Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count())); + $filtered = $accounts->filter( + function (Account $account) use ($value, $stringMethod) { + // either IBAN or account number + $ibanMatch = $stringMethod(strtolower((string)$account->iban), strtolower((string)$value)); + $accountNrMatch = false; + /** @var AccountMeta $meta */ + foreach ($account->accountMeta as $meta) { + if ('account_number' === $meta->name && $stringMethod(strtolower($meta->data), strtolower($value))) { + $accountNrMatch = true; + } + } + + return $ibanMatch || $accountNrMatch; + } + ); + + if (0 === $filtered->count()) { + Log::debug('Left with zero, search for invalid account'); + $this->collector->findNothing(); + + return; + } + Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod)); + $this->collector->$collectorMethod($filtered); + } + + /** + * + * @throws FireflyException + */ + private function setDateAfterParams(array $range, bool $prohibited = false) + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key)); + case 'exact': + $this->collector->setAfter($value); + $this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value)); + $this->collector->yearAfter($value); + $this->operators->push(['type' => 'date_after_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value)); + $this->collector->monthAfter($value); + $this->operators->push(['type' => 'date_after_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_after DAY value "%s"', $value)); + $this->collector->dayAfter($value); + $this->operators->push(['type' => 'date_after_day', 'value' => $value,]); + break; + } + } + } + + /** + * + * @throws FireflyException + */ + private function setDateBeforeParams(array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key)); + case 'exact': + $this->collector->setBefore($value); + $this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value)); + $this->collector->yearBefore($value); + $this->operators->push(['type' => 'date_before_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value)); + $this->collector->monthBefore($value); + $this->operators->push(['type' => 'date_before_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_before DAY value "%s"', $value)); + $this->collector->dayBefore($value); + $this->operators->push(['type' => 'date_before_day', 'value' => $value,]); + break; + } + } + } + + /** + * + * @throws FireflyException + */ + private function setExactDateParams(array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key)); + case 'exact': + Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d'))); + $this->collector->setRange($value, $value); + $this->operators->push(['type' => 'date_on', 'value' => $value->format('Y-m-d'),]); + break; + case 'exact_not': + $this->collector->excludeRange($value, $value); + $this->operators->push(['type' => 'not_date_on', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value)); + $this->collector->yearIs($value); + $this->operators->push(['type' => 'date_on_year', 'value' => $value,]); + break; + case 'year_not': + Log::debug(sprintf('Set date_is_exact_not YEAR value "%s"', $value)); + $this->collector->yearIsNot($value); + $this->operators->push(['type' => 'not_date_on_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value)); + $this->collector->monthIs($value); + $this->operators->push(['type' => 'date_on_month', 'value' => $value,]); + break; + case 'month_not': + Log::debug(sprintf('Set date_is_exact not MONTH value "%s"', $value)); + $this->collector->monthIsNot($value); + $this->operators->push(['type' => 'not_date_on_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value)); + $this->collector->dayIs($value); + $this->operators->push(['type' => 'date_on_day', 'value' => $value,]); + break; + case 'day_not': + Log::debug(sprintf('Set not date_is_exact DAY value "%s"', $value)); + $this->collector->dayIsNot($value); + $this->operators->push(['type' => 'not_date_on_day', 'value' => $value,]); + break; + } + } + } + + /** + * @throws FireflyException + */ + private function setExactMetaDateParams(string $field, array $range, bool $prohibited = false): void + { + Log::debug('Now in setExactMetaDateParams()'); + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setExactMetaDateParams()', $key)); + case 'exact': + Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); + $this->collector->setMetaDateRange($value, $value, $field); + $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'exact_not': + Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); + $this->collector->excludeMetaDateRange($value, $value, $field); + $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value)); + $this->collector->metaYearIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value,]); + break; + case 'year_not': + Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value)); + $this->collector->metaYearIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value)); + $this->collector->metaMonthIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value,]); + break; + case 'month_not': + Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value)); + $this->collector->metaMonthIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value)); + $this->collector->metaDayIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value,]); + break; + case 'day_not': + Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value)); + $this->collector->metaDayIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value,]); + break; + } + } + } + + /** + * @throws FireflyException + */ + private function setExactObjectDateParams(string $field, array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setExactObjectDateParams()', $key)); + case 'exact': + Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); + $this->collector->setObjectRange($value, clone $value, $field); + $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'exact_not': + Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); + $this->collector->excludeObjectRange($value, clone $value, $field); + $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value)); + $this->collector->objectYearIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value,]); + break; + case 'year_not': + Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value)); + $this->collector->objectYearIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value)); + $this->collector->objectMonthIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value,]); + break; + case 'month_not': + Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value)); + $this->collector->objectMonthIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value)); + $this->collector->objectDayIs($value, $field); + $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value,]); + break; + case 'day_not': + Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value)); + $this->collector->objectDayIsNot($value, $field); + $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value,]); + break; + } + } + } + + /** + * @throws FireflyException + */ + private function setMetaDateAfterParams(string $field, array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateAfterParams()', $key)); + case 'exact': + $this->collector->setMetaAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set %s_is_after YEAR value "%s"', $field, $value)); + $this->collector->metaYearAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set %s_is_after MONTH value "%s"', $field, $value)); + $this->collector->metaMonthAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set %s_is_after DAY value "%s"', $field, $value)); + $this->collector->metaDayAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value,]); + break; + } + } + } + + /** + * @throws FireflyException + */ + private function setMetaDateBeforeParams(string $field, array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateBeforeParams()', $key)); + case 'exact': + $this->collector->setMetaBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set %s_is_before YEAR value "%s"', $field, $value)); + $this->collector->metaYearBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set %s_is_before MONTH value "%s"', $field, $value)); + $this->collector->metaMonthBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set %s_is_before DAY value "%s"', $field, $value)); + $this->collector->metaDayBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value,]); + break; + } + } + } + + /** + * + * @throws FireflyException + */ + private function setObjectDateAfterParams(string $field, array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateAfterParams()', $key)); + case 'exact': + $this->collector->setObjectAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value)); + $this->collector->objectYearAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value)); + $this->collector->objectMonthAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_after DAY value "%s"', $value)); + $this->collector->objectDayAfter($value, $field); + $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value,]); + break; + } + } + } + + /** + * + * @throws FireflyException + */ + private function setObjectDateBeforeParams(string $field, array $range, bool $prohibited = false): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + $key = $prohibited ? sprintf('%s_not', $key) : $key; + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateBeforeParams()', $key)); + case 'exact': + $this->collector->setObjectBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value)); + $this->collector->objectYearBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value)); + $this->collector->objectMonthBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_before DAY value "%s"', $value)); + $this->collector->objectDayBefore($value, $field); + $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value,]); + break; + } + } + } + /** * * @throws FireflyException @@ -1315,722 +2033,4 @@ class OperatorQuerySearch implements SearchInterface } return true; } - - /** - * @param string $operator - * - * @return string - * @throws FireflyException - */ - public static function getRootOperator(string $operator): string - { - $original = $operator; - // if the string starts with "-" (not), we can remove it and recycle - // the configuration from the original operator. - if (str_starts_with($operator, '-')) { - $operator = substr($operator, 1); - } - - $config = config(sprintf('search.operators.%s', $operator)); - if (null === $config) { - throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator)); - } - if (true === $config['alias']) { - $return = $config['alias_for']; - if (str_starts_with($original, '-')) { - $return = sprintf('-%s', $config['alias_for']); - } - Log::debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return)); - - return $return; - } - Log::debug(sprintf('"%s" is not an alias.', $operator)); - - return $original; - } - - /** - * searchDirection: 1 = source (default), 2 = destination, 3 = both - * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is - * - * @param string $value - * @param int $searchDirection - * @param int $stringPosition - * @param bool $prohibited - */ - private function searchAccount(string $value, int $searchDirection, int $stringPosition, bool $prohibited = false): void - { - Log::debug(sprintf('searchAccount("%s", %d, %d)', $value, $stringPosition, $searchDirection)); - - // search direction (default): for source accounts - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; - $collectorMethod = 'setSourceAccounts'; - if ($prohibited) { - $collectorMethod = 'excludeSourceAccounts'; - } - - // search direction: for destination accounts - if (2 === $searchDirection) { - // destination can be - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; - $collectorMethod = 'setDestinationAccounts'; - if ($prohibited) { - $collectorMethod = 'excludeDestinationAccounts'; - } - } - // either account could be: - if (3 === $searchDirection) { - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; - $collectorMethod = 'setAccounts'; - if ($prohibited) { - $collectorMethod = 'excludeAccounts'; - } - } - // string position (default): starts with: - $stringMethod = 'str_starts_with'; - - // string position: ends with: - if (2 === $stringPosition) { - $stringMethod = 'str_ends_with'; - } - if (3 === $stringPosition) { - $stringMethod = 'str_contains'; - } - if (4 === $stringPosition) { - $stringMethod = 'str_is_equal'; - } - - // get accounts: - $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 1337); - if (0 === $accounts->count()) { - Log::debug('Found zero accounts, search for non existing account, NO results will be returned.'); - $this->collector->findNothing(); - - return; - } - Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count())); - $filtered = $accounts->filter( - function (Account $account) use ($value, $stringMethod) { - return $stringMethod(strtolower($account->name), strtolower($value)); - } - ); - - if (0 === $filtered->count()) { - Log::debug('Left with zero accounts, so cannot find anything, NO results will be returned.'); - $this->collector->findNothing(); - - return; - } - Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod)); - $this->collector->$collectorMethod($filtered); - } - - /** - * searchDirection: 1 = source (default), 2 = destination, 3 = both - * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is - * - * @param string $value - * @param int $searchDirection - * @param int $stringPosition - * @param bool $prohibited - */ - private function searchAccountNr(string $value, int $searchDirection, int $stringPosition, bool $prohibited = false): void - { - Log::debug(sprintf('searchAccountNr(%s, %d, %d)', $value, $searchDirection, $stringPosition)); - - // search direction (default): for source accounts - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; - $collectorMethod = 'setSourceAccounts'; - if (true === $prohibited) { - $collectorMethod = 'excludeSourceAccounts'; - } - - // search direction: for destination accounts - if (2 === $searchDirection) { - // destination can be - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; - $collectorMethod = 'setDestinationAccounts'; - if (true === $prohibited) { - $collectorMethod = 'excludeDestinationAccounts'; - } - } - - // either account could be: - if (3 === $searchDirection) { - $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; - $collectorMethod = 'setAccounts'; - if (true === $prohibited) { - $collectorMethod = 'excludeAccounts'; - } - } - - // string position (default): starts with: - $stringMethod = 'str_starts_with'; - - // string position: ends with: - if (2 === $stringPosition) { - $stringMethod = 'str_ends_with'; - } - if (3 === $stringPosition) { - $stringMethod = 'str_contains'; - } - if (4 === $stringPosition) { - $stringMethod = 'str_is_equal'; - } - - // search for accounts: - $accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 1337); - if (0 === $accounts->count()) { - Log::debug('Found zero accounts, search for invalid account.'); - $this->collector->findNothing(); - - return; - } - - // if found, do filter - Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count())); - $filtered = $accounts->filter( - function (Account $account) use ($value, $stringMethod) { - // either IBAN or account number - $ibanMatch = $stringMethod(strtolower((string)$account->iban), strtolower((string)$value)); - $accountNrMatch = false; - /** @var AccountMeta $meta */ - foreach ($account->accountMeta as $meta) { - if ('account_number' === $meta->name && $stringMethod(strtolower($meta->data), strtolower($value))) { - $accountNrMatch = true; - } - } - - return $ibanMatch || $accountNrMatch; - } - ); - - if (0 === $filtered->count()) { - Log::debug('Left with zero, search for invalid account'); - $this->collector->findNothing(); - - return; - } - Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod)); - $this->collector->$collectorMethod($filtered); - } - - /** - * @return Account - */ - private function getCashAccount(): Account - { - return $this->accountRepository->getCashAccount(); - } - - /** - * @param string $value - * - * @return TransactionCurrency|null - */ - private function findCurrency(string $value): ?TransactionCurrency - { - if (str_contains($value, '(') && str_contains($value, ')')) { - // bad method to split and get the currency code: - $parts = explode(' ', $value); - $value = trim($parts[count($parts) - 1], "() \t\n\r\0\x0B"); - } - $result = $this->currencyRepository->findByCodeNull($value); - if (null === $result) { - $result = $this->currencyRepository->findByNameNull($value); - } - - return $result; - } - - /** - * @param string $value - * - * @return array - * @throws FireflyException - */ - private function parseDateRange(string $value): array - { - $parser = new ParseDateString(); - if ($parser->isDateRange($value)) { - return $parser->parseRange($value); - } - $parsedDate = $parser->parseDate($value); - - return [ - 'exact' => $parsedDate, - ]; - } - - /** - * - * @throws FireflyException - */ - private function setExactDateParams(array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key)); - case 'exact': - Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d'))); - $this->collector->setRange($value, $value); - $this->operators->push(['type' => 'date_on', 'value' => $value->format('Y-m-d'),]); - break; - case 'exact_not': - $this->collector->excludeRange($value, $value); - $this->operators->push(['type' => 'not_date_on', 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value)); - $this->collector->yearIs($value); - $this->operators->push(['type' => 'date_on_year', 'value' => $value,]); - break; - case 'year_not': - Log::debug(sprintf('Set date_is_exact_not YEAR value "%s"', $value)); - $this->collector->yearIsNot($value); - $this->operators->push(['type' => 'not_date_on_year', 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value)); - $this->collector->monthIs($value); - $this->operators->push(['type' => 'date_on_month', 'value' => $value,]); - break; - case 'month_not': - Log::debug(sprintf('Set date_is_exact not MONTH value "%s"', $value)); - $this->collector->monthIsNot($value); - $this->operators->push(['type' => 'not_date_on_month', 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value)); - $this->collector->dayIs($value); - $this->operators->push(['type' => 'date_on_day', 'value' => $value,]); - break; - case 'day_not': - Log::debug(sprintf('Set not date_is_exact DAY value "%s"', $value)); - $this->collector->dayIsNot($value); - $this->operators->push(['type' => 'not_date_on_day', 'value' => $value,]); - break; - } - } - } - - /** - * - * @throws FireflyException - */ - private function setDateBeforeParams(array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key)); - case 'exact': - $this->collector->setBefore($value); - $this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value)); - $this->collector->yearBefore($value); - $this->operators->push(['type' => 'date_before_year', 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value)); - $this->collector->monthBefore($value); - $this->operators->push(['type' => 'date_before_month', 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set date_is_before DAY value "%s"', $value)); - $this->collector->dayBefore($value); - $this->operators->push(['type' => 'date_before_day', 'value' => $value,]); - break; - } - } - } - - /** - * - * @throws FireflyException - */ - private function setDateAfterParams(array $range, bool $prohibited = false) - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key)); - case 'exact': - $this->collector->setAfter($value); - $this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value)); - $this->collector->yearAfter($value); - $this->operators->push(['type' => 'date_after_year', 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value)); - $this->collector->monthAfter($value); - $this->operators->push(['type' => 'date_after_month', 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set date_is_after DAY value "%s"', $value)); - $this->collector->dayAfter($value); - $this->operators->push(['type' => 'date_after_day', 'value' => $value,]); - break; - } - } - } - - /** - * @throws FireflyException - */ - private function setExactMetaDateParams(string $field, array $range, bool $prohibited = false): void - { - Log::debug('Now in setExactMetaDateParams()'); - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setExactMetaDateParams()', $key)); - case 'exact': - Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); - $this->collector->setMetaDateRange($value, $value, $field); - $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'exact_not': - Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); - $this->collector->excludeMetaDateRange($value, $value, $field); - $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value)); - $this->collector->metaYearIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value,]); - break; - case 'year_not': - Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value)); - $this->collector->metaYearIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value)); - $this->collector->metaMonthIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value,]); - break; - case 'month_not': - Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value)); - $this->collector->metaMonthIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value)); - $this->collector->metaDayIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value,]); - break; - case 'day_not': - Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value)); - $this->collector->metaDayIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * @throws FireflyException - */ - private function setMetaDateBeforeParams(string $field, array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateBeforeParams()', $key)); - case 'exact': - $this->collector->setMetaBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set %s_is_before YEAR value "%s"', $field, $value)); - $this->collector->metaYearBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set %s_is_before MONTH value "%s"', $field, $value)); - $this->collector->metaMonthBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set %s_is_before DAY value "%s"', $field, $value)); - $this->collector->metaDayBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * @throws FireflyException - */ - private function setMetaDateAfterParams(string $field, array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateAfterParams()', $key)); - case 'exact': - $this->collector->setMetaAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set %s_is_after YEAR value "%s"', $field, $value)); - $this->collector->metaYearAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set %s_is_after MONTH value "%s"', $field, $value)); - $this->collector->metaMonthAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set %s_is_after DAY value "%s"', $field, $value)); - $this->collector->metaDayAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * @throws FireflyException - */ - private function setExactObjectDateParams(string $field, array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setExactObjectDateParams()', $key)); - case 'exact': - Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); - $this->collector->setObjectRange($value, clone $value, $field); - $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'exact_not': - Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d'))); - $this->collector->excludeObjectRange($value, clone $value, $field); - $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value)); - $this->collector->objectYearIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value,]); - break; - case 'year_not': - Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value)); - $this->collector->objectYearIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value)); - $this->collector->objectMonthIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value,]); - break; - case 'month_not': - Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value)); - $this->collector->objectMonthIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value)); - $this->collector->objectDayIs($value, $field); - $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value,]); - break; - case 'day_not': - Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value)); - $this->collector->objectDayIsNot($value, $field); - $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * - * @throws FireflyException - */ - private function setObjectDateBeforeParams(string $field, array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateBeforeParams()', $key)); - case 'exact': - $this->collector->setObjectBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value)); - $this->collector->objectYearBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value)); - $this->collector->objectMonthBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set date_is_before DAY value "%s"', $value)); - $this->collector->objectDayBefore($value, $field); - $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * - * @throws FireflyException - */ - private function setObjectDateAfterParams(string $field, array $range, bool $prohibited = false): void - { - /** - * @var string $key - * @var Carbon|string $value - */ - foreach ($range as $key => $value) { - $key = $prohibited ? sprintf('%s_not', $key) : $key; - switch ($key) { - default: - throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateAfterParams()', $key)); - case 'exact': - $this->collector->setObjectAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d'),]); - break; - case 'year': - Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value)); - $this->collector->objectYearAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value,]); - break; - case 'month': - Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value)); - $this->collector->objectMonthAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value,]); - break; - case 'day': - Log::debug(sprintf('Set date_is_after DAY value "%s"', $value)); - $this->collector->objectDayAfter($value, $field); - $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value,]); - break; - } - } - } - - /** - * @inheritDoc - */ - public function searchTime(): float - { - return microtime(true) - $this->startTime; - } - - /** - * @inheritDoc - */ - public function searchTransactions(): LengthAwarePaginator - { - if (0 === count($this->getWords()) && 0 === count($this->getOperators())) { - return new LengthAwarePaginator([], 0, 5, 1); - } - - return $this->collector->getPaginatedGroups(); - } - - /** - * @return array - */ - public function getWords(): array - { - return $this->words; - } - - /** - * @param Carbon $date - */ - public function setDate(Carbon $date): void - { - $this->date = $date; - } - - /** - * @inheritDoc - */ - public function setPage(int $page): void - { - $this->page = $page; - $this->collector->setPage($this->page); - } - - /** - * @inheritDoc - */ - public function setUser(User $user): void - { - $this->accountRepository->setUser($user); - $this->billRepository->setUser($user); - $this->categoryRepository->setUser($user); - $this->budgetRepository->setUser($user); - $this->tagRepository->setUser($user); - $this->collector = app(GroupCollectorInterface::class); - $this->collector->setUser($user); - $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); - - $this->setLimit((int)app('preferences')->getForUser($user, 'listPageSize', 50)->data); - } - - /** - * @param int $limit - */ - public function setLimit(int $limit): void - { - $this->limit = $limit; - $this->collector->setLimit($this->limit); - } } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 52e052af11..552b80bcc6 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -32,8 +32,8 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Support\Collection; -use JsonException; use Illuminate\Support\Facades\Log; +use JsonException; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use stdClass; @@ -47,6 +47,56 @@ use ValueError; */ class Steam { + /** + * Gets balance at the end of current month by default + * + * @param Account $account + * @param Carbon $date + * @param TransactionCurrency|null $currency + * + * @return string + * @throws FireflyException + */ + public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string + { + // abuse chart properties: + $cache = new CacheProperties(); + $cache->addProperty($account->id); + $cache->addProperty('balance'); + $cache->addProperty($date); + $cache->addProperty($currency ? $currency->id : 0); + if ($cache->has()) { + return $cache->get(); + } + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + if (null === $currency) { + $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUser($account->user); + } + // first part: get all balances in own currency: + $transactions = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $currency->id) + ->get(['transactions.amount'])->toArray(); + $nativeBalance = $this->sumTransactions($transactions, 'amount'); + // get all balances in foreign currency: + $transactions = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.foreign_currency_id', $currency->id) + ->where('transactions.transaction_currency_id', '!=', $currency->id) + ->get(['transactions.foreign_amount'])->toArray(); + $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); + $balance = bcadd($nativeBalance, $foreignBalance); + $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; + $balance = bcadd($balance, $virtual); + + $cache->store($balance); + + return $balance; + } + /** * @param Account $account * @param Carbon $date @@ -79,25 +129,6 @@ class Steam return bcadd($nativeBalance, $foreignBalance); } - /** - * @param array $transactions - * @param string $key - * - * @return string - */ - public function sumTransactions(array $transactions, string $key): string - { - $sum = '0'; - /** @var array $transaction */ - foreach ($transactions as $transaction) { - $value = (string)($transaction[$key] ?? '0'); - $value = '' === $value ? '0' : $value; - $sum = bcadd($sum, $value); - } - - return $sum; - } - /** * Gets the balance for the given account during the whole range, using this format:. * @@ -189,53 +220,34 @@ class Steam } /** - * Gets balance at the end of current month by default - * * @param Account $account * @param Carbon $date - * @param TransactionCurrency|null $currency * - * @return string - * @throws FireflyException + * @return array */ - public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string + public function balancePerCurrency(Account $account, Carbon $date): array { // abuse chart properties: $cache = new CacheProperties(); $cache->addProperty($account->id); - $cache->addProperty('balance'); + $cache->addProperty('balance-per-currency'); $cache->addProperty($date); - $cache->addProperty($currency ? $currency->id : 0); if ($cache->has()) { return $cache->get(); } - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - if (null === $currency) { - $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUser($account->user); + $query = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->groupBy('transactions.transaction_currency_id'); + $balances = $query->get(['transactions.transaction_currency_id', DB::raw('SUM(transactions.amount) as sum_for_currency')]); + $return = []; + /** @var stdClass $entry */ + foreach ($balances as $entry) { + $return[(int)$entry->transaction_currency_id] = (string)$entry->sum_for_currency; } - // first part: get all balances in own currency: - $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $currency->id) - ->get(['transactions.amount'])->toArray(); - $nativeBalance = $this->sumTransactions($transactions, 'amount'); - // get all balances in foreign currency: - $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.foreign_currency_id', $currency->id) - ->where('transactions.transaction_currency_id', '!=', $currency->id) - ->get(['transactions.foreign_amount'])->toArray(); - $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); - $balance = bcadd($nativeBalance, $foreignBalance); - $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; - $balance = bcadd($balance, $virtual); + $cache->store($return); - $cache->store($balance); - - return $balance; + return $return; } /** @@ -303,37 +315,6 @@ class Steam return $result; } - /** - * @param Account $account - * @param Carbon $date - * - * @return array - */ - public function balancePerCurrency(Account $account, Carbon $date): array - { - // abuse chart properties: - $cache = new CacheProperties(); - $cache->addProperty($account->id); - $cache->addProperty('balance-per-currency'); - $cache->addProperty($date); - if ($cache->has()) { - return $cache->get(); - } - $query = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->groupBy('transactions.transaction_currency_id'); - $balances = $query->get(['transactions.transaction_currency_id', DB::raw('SUM(transactions.amount) as sum_for_currency')]); - $return = []; - /** @var stdClass $entry */ - foreach ($balances as $entry) { - $return[(int)$entry->transaction_currency_id] = (string)$entry->sum_for_currency; - } - $cache->store($return); - - return $return; - } - /** * https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers * @@ -428,6 +409,36 @@ class Steam return str_replace($search, '', $string); } + /** + * https://framework.zend.com/downloads/archives + * + * Convert a scientific notation to float + * Additionally fixed a problem with PHP <= 5.2.x with big integers + * + * @param string $value + * @return string + */ + public function floatalize(string $value): string + { + $value = strtoupper($value); + if (!str_contains($value, 'E')) { + return $value; + } + + $number = substr($value, 0, strpos($value, 'E')); + if (str_contains($number, '.')) { + $post = strlen(substr($number, strpos($number, '.') + 1)); + $mantis = substr($value, strpos($value, 'E') + 1); + if ($mantis < 0) { + $post += abs((int)$mantis); + } + // TODO careless float could break financial math. + return number_format((float)$value, $post, '.', ''); + } + // TODO careless float could break financial math. + return number_format((float)$value, 0, '.', ''); + } + /** * @param string $ipAddress * @return string @@ -443,6 +454,23 @@ class Steam return $hostName; } + /** + * Get user's language. + * + * @return string + * @throws FireflyException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getLanguage(): string // get preference + { + $preference = app('preferences')->get('language', config('firefly.default_language', 'en_US'))->data; + if (!is_string($preference)) { + throw new FireflyException(sprintf('Preference "language" must be a string, but is unexpectedly a "%s".', gettype($preference))); + } + return $preference; + } + /** * @param array $accounts * @@ -489,23 +517,6 @@ class Steam return $locale; } - /** - * Get user's language. - * - * @return string - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function getLanguage(): string // get preference - { - $preference = app('preferences')->get('language', config('firefly.default_language', 'en_US'))->data; - if (!is_string($preference)) { - throw new FireflyException(sprintf('Preference "language" must be a string, but is unexpectedly a "%s".', gettype($preference))); - } - return $preference; - } - /** * @param string $locale * @@ -584,36 +595,6 @@ class Steam return $amount; } - /** - * https://framework.zend.com/downloads/archives - * - * Convert a scientific notation to float - * Additionally fixed a problem with PHP <= 5.2.x with big integers - * - * @param string $value - * @return string - */ - public function floatalize(string $value): string - { - $value = strtoupper($value); - if (!str_contains($value, 'E')) { - return $value; - } - - $number = substr($value, 0, strpos($value, 'E')); - if (str_contains($number, '.')) { - $post = strlen(substr($number, strpos($number, '.') + 1)); - $mantis = substr($value, strpos($value, 'E') + 1); - if ($mantis < 0) { - $post += abs((int)$mantis); - } - // TODO careless float could break financial math. - return number_format((float)$value, $post, '.', ''); - } - // TODO careless float could break financial math. - return number_format((float)$value, 0, '.', ''); - } - /** * @param string|null $amount * @@ -683,4 +664,23 @@ class Steam return $amount; } + + /** + * @param array $transactions + * @param string $key + * + * @return string + */ + public function sumTransactions(array $transactions, string $key): string + { + $sum = '0'; + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $value = (string)($transaction[$key] ?? '0'); + $value = '' === $value ? '0' : $value; + $sum = bcadd($sum, $value); + } + + return $sum; + } } diff --git a/app/Support/System/OAuthKeys.php b/app/Support/System/OAuthKeys.php index abb8cc036d..72c8006a6f 100644 --- a/app/Support/System/OAuthKeys.php +++ b/app/Support/System/OAuthKeys.php @@ -28,8 +28,8 @@ use Artisan; use Crypt; use FireflyIII\Exceptions\FireflyException; use Illuminate\Contracts\Encryption\DecryptException; -use Laravel\Passport\Console\KeysCommand; use Illuminate\Support\Facades\Log; +use Laravel\Passport\Console\KeysCommand; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -44,22 +44,21 @@ class OAuthKeys /** * */ - public static function verifyKeysRoutine(): void + public static function generateKeys(): void { - if (!self::keysInDatabase() && !self::hasKeyFiles()) { - self::generateKeys(); - self::storeKeysInDB(); + Artisan::registerCommand(new KeysCommand()); + Artisan::call('passport:keys'); + } - return; - } - if (self::keysInDatabase() && !self::hasKeyFiles()) { - self::restoreKeysFromDB(); + /** + * @return bool + */ + public static function hasKeyFiles(): bool + { + $private = storage_path('oauth-private.key'); + $public = storage_path('oauth-public.key'); - return; - } - if (!self::keysInDatabase() && self::hasKeyFiles()) { - self::storeKeysInDB(); - } + return file_exists($private) && file_exists($public); } /** @@ -86,37 +85,6 @@ class OAuthKeys return false; } - /** - * @return bool - */ - public static function hasKeyFiles(): bool - { - $private = storage_path('oauth-private.key'); - $public = storage_path('oauth-public.key'); - - return file_exists($private) && file_exists($public); - } - - /** - * - */ - public static function generateKeys(): void - { - Artisan::registerCommand(new KeysCommand()); - Artisan::call('passport:keys'); - } - - /** - * - */ - public static function storeKeysInDB(): void - { - $private = storage_path('oauth-private.key'); - $public = storage_path('oauth-public.key'); - app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); - app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); - } - /** * @return bool * @throws ContainerExceptionInterface @@ -146,4 +114,36 @@ class OAuthKeys file_put_contents($public, $publicContent); return true; } + + /** + * + */ + public static function storeKeysInDB(): void + { + $private = storage_path('oauth-private.key'); + $public = storage_path('oauth-public.key'); + app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); + app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); + } + + /** + * + */ + public static function verifyKeysRoutine(): void + { + if (!self::keysInDatabase() && !self::hasKeyFiles()) { + self::generateKeys(); + self::storeKeysInDB(); + + return; + } + if (self::keysInDatabase() && !self::hasKeyFiles()) { + self::restoreKeysFromDB(); + + return; + } + if (!self::keysInDatabase() && self::hasKeyFiles()) { + self::storeKeysInDB(); + } + } } diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index 5ae6db2121..247f07f756 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -46,6 +46,18 @@ class AmountFormat extends AbstractExtension ]; } + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + $this->formatAmountByAccount(), + $this->formatAmountBySymbol(), + $this->formatAmountByCurrency(), + ]; + } + /** * @return TwigFilter */ @@ -62,34 +74,6 @@ class AmountFormat extends AbstractExtension ); } - /** - * @return TwigFilter - */ - protected function formatAmountPlain(): TwigFilter - { - return new TwigFilter( - 'formatAmountPlain', - static function (string $string): string { - $currency = app('amount')->getDefaultCurrency(); - - return app('amount')->formatAnything($currency, $string, false); - }, - ['is_safe' => ['html']] - ); - } - - /** - * {@inheritdoc} - */ - public function getFunctions(): array - { - return [ - $this->formatAmountByAccount(), - $this->formatAmountBySymbol(), - $this->formatAmountByCurrency(), - ]; - } - /** * Will format the amount by the currency related to the given account. * @@ -112,6 +96,24 @@ class AmountFormat extends AbstractExtension ); } + /** + * Will format the amount by the currency related to the given account. + * + * @return TwigFunction + */ + protected function formatAmountByCurrency(): TwigFunction + { + return new TwigFunction( + 'formatAmountByCurrency', + static function (TransactionCurrency $currency, string $amount, bool $coloured = null): string { + $coloured = $coloured ?? true; + + return app('amount')->formatAnything($currency, $amount, $coloured); + }, + ['is_safe' => ['html']] + ); + } + /** * Will format the amount by the currency related to the given account. * @@ -135,18 +137,16 @@ class AmountFormat extends AbstractExtension } /** - * Will format the amount by the currency related to the given account. - * - * @return TwigFunction + * @return TwigFilter */ - protected function formatAmountByCurrency(): TwigFunction + protected function formatAmountPlain(): TwigFilter { - return new TwigFunction( - 'formatAmountByCurrency', - static function (TransactionCurrency $currency, string $amount, bool $coloured = null): string { - $coloured = $coloured ?? true; + return new TwigFilter( + 'formatAmountPlain', + static function (string $string): string { + $currency = app('amount')->getDefaultCurrency(); - return app('amount')->formatAnything($currency, $amount, $coloured); + return app('amount')->formatAnything($currency, $string, false); }, ['is_safe' => ['html']] ); diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 55ef5aa9ce..d01c1c5a8c 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -53,6 +53,95 @@ class General extends AbstractExtension ]; } + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + $this->phpdate(), + $this->activeRouteStrict(), + $this->activeRoutePartial(), + $this->activeRoutePartialObjectType(), + $this->menuOpenRoutePartial(), + $this->formatDate(), + $this->getMetaField(), + $this->hasRole(), + $this->getRootSearchOperator(), + $this->carbonize(), + ]; + } + + /** + * Will return "active" when a part of the route matches the argument. + * ie. "accounts" will match "accounts.index". + * + * @return TwigFunction + */ + protected function activeRoutePartial(): TwigFunction + { + return new TwigFunction( + 'activeRoutePartial', + static function (): string { + $args = func_get_args(); + $route = $args[0]; // name of the route. + $name = Route::getCurrentRoute()->getName() ?? ''; + if (str_contains($name, $route)) { + return 'active'; + } + + return ''; + } + ); + } + + /** + * This function will return "active" when the current route matches the first argument (even partly) + * but, the variable $objectType has been set and matches the second argument. + * + * @return TwigFunction + */ + protected function activeRoutePartialObjectType(): TwigFunction + { + return new TwigFunction( + 'activeRoutePartialObjectType', + static function ($context): string { + [, $route, $objectType] = func_get_args(); + $activeObjectType = $context['objectType'] ?? false; + + if ($objectType === $activeObjectType && false !== stripos(Route::getCurrentRoute()->getName(), $route)) { + return 'active'; + } + + return ''; + }, + ['needs_context' => true] + ); + } + + /** + * Will return "active" when the current route matches the given argument + * exactly. + * + * @return TwigFunction + */ + protected function activeRouteStrict(): TwigFunction + { + return new TwigFunction( + 'activeRouteStrict', + static function (): string { + $args = func_get_args(); + $route = $args[0]; // name of the route. + + if (Route::getCurrentRoute()->getName() === $route) { + return 'active'; + } + + return ''; + } + ); + } + /** * Show account balance. Only used on the front page of Firefly III. * @@ -74,6 +163,36 @@ class General extends AbstractExtension ); } + /** + * @return TwigFunction + */ + protected function carbonize(): TwigFunction + { + return new TwigFunction( + 'carbonize', + static function (string $date): Carbon { + return new Carbon($date); + } + ); + } + + /** + * Formats a string as a thing by converting it to a Carbon first. + * + * @return TwigFunction + */ + protected function formatDate(): TwigFunction + { + return new TwigFunction( + 'formatDate', + function (string $date, string $format): string { + $carbon = new Carbon($date); + + return $carbon->isoFormat($format); + } + ); + } + /** * Used to convert 1024 to 1kb etc. * @@ -99,6 +218,103 @@ class General extends AbstractExtension ); } + /** + * @return TwigFunction + * TODO remove me when layout v1 is deprecated. + */ + protected function getMetaField(): TwigFunction + { + return new TwigFunction( + 'accountGetMetaField', + static function (Account $account, string $field): string { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $result = $repository->getMetaValue($account, $field); + if (null === $result) { + return ''; + } + + return $result; + } + ); + } + + protected function getRootSearchOperator(): TwigFunction + { + return new TwigFunction( + 'getRootSearchOperator', + static function (string $operator): string { + $result = OperatorQuerySearch::getRootOperator($operator); + return str_replace('-', 'not_', $result); + } + ); + } + + /** + * Will return true if the user is of role X. + * + * @return TwigFunction + */ + protected function hasRole(): TwigFunction + { + return new TwigFunction( + 'hasRole', + static function (string $role): bool { + $repository = app(UserRepositoryInterface::class); + if ($repository->hasRole(auth()->user(), $role)) { + return true; + } + + return false; + } + ); + } + + /** + * @return TwigFilter + */ + protected function markdown(): TwigFilter + { + return new TwigFilter( + 'markdown', + static function (string $text): string { + $converter = new GithubFlavoredMarkdownConverter( + [ + 'allow_unsafe_links' => false, + 'max_nesting_level' => 3, + 'html_input' => 'escape', + ] + ); + + return (string)$converter->convert($text); + }, + ['is_safe' => ['html']] + ); + } + + /** + * Will return "menu-open" when a part of the route matches the argument. + * ie. "accounts" will match "accounts.index". + * + * @return TwigFunction + */ + protected function menuOpenRoutePartial(): TwigFunction + { + return new TwigFunction( + 'menuOpenRoutePartial', + static function (): string { + $args = func_get_args(); + $route = $args[0]; // name of the route. + $name = Route::getCurrentRoute()->getName() ?? ''; + if (str_contains($name, $route)) { + return 'menu-open'; + } + + return ''; + } + ); + } + /** * Show icon with attachment. * @@ -177,28 +393,6 @@ class General extends AbstractExtension ); } - /** - * @return TwigFilter - */ - protected function markdown(): TwigFilter - { - return new TwigFilter( - 'markdown', - static function (string $text): string { - $converter = new GithubFlavoredMarkdownConverter( - [ - 'allow_unsafe_links' => false, - 'max_nesting_level' => 3, - 'html_input' => 'escape', - ] - ); - - return (string)$converter->convert($text); - }, - ['is_safe' => ['html']] - ); - } - /** * Show URL host name * @@ -217,25 +411,6 @@ class General extends AbstractExtension ); } - /** - * {@inheritdoc} - */ - public function getFunctions(): array - { - return [ - $this->phpdate(), - $this->activeRouteStrict(), - $this->activeRoutePartial(), - $this->activeRoutePartialObjectType(), - $this->menuOpenRoutePartial(), - $this->formatDate(), - $this->getMetaField(), - $this->hasRole(), - $this->getRootSearchOperator(), - $this->carbonize(), - ]; - } - /** * Basic example thing for some views. * @@ -250,179 +425,4 @@ class General extends AbstractExtension } ); } - - /** - * Will return "active" when the current route matches the given argument - * exactly. - * - * @return TwigFunction - */ - protected function activeRouteStrict(): TwigFunction - { - return new TwigFunction( - 'activeRouteStrict', - static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - - if (Route::getCurrentRoute()->getName() === $route) { - return 'active'; - } - - return ''; - } - ); - } - - /** - * Will return "active" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - * - * @return TwigFunction - */ - protected function activeRoutePartial(): TwigFunction - { - return new TwigFunction( - 'activeRoutePartial', - static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - $name = Route::getCurrentRoute()->getName() ?? ''; - if (str_contains($name, $route)) { - return 'active'; - } - - return ''; - } - ); - } - - /** - * This function will return "active" when the current route matches the first argument (even partly) - * but, the variable $objectType has been set and matches the second argument. - * - * @return TwigFunction - */ - protected function activeRoutePartialObjectType(): TwigFunction - { - return new TwigFunction( - 'activeRoutePartialObjectType', - static function ($context): string { - [, $route, $objectType] = func_get_args(); - $activeObjectType = $context['objectType'] ?? false; - - if ($objectType === $activeObjectType && false !== stripos(Route::getCurrentRoute()->getName(), $route)) { - return 'active'; - } - - return ''; - }, - ['needs_context' => true] - ); - } - - /** - * Will return "menu-open" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - * - * @return TwigFunction - */ - protected function menuOpenRoutePartial(): TwigFunction - { - return new TwigFunction( - 'menuOpenRoutePartial', - static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - $name = Route::getCurrentRoute()->getName() ?? ''; - if (str_contains($name, $route)) { - return 'menu-open'; - } - - return ''; - } - ); - } - - /** - * Formats a string as a thing by converting it to a Carbon first. - * - * @return TwigFunction - */ - protected function formatDate(): TwigFunction - { - return new TwigFunction( - 'formatDate', - function (string $date, string $format): string { - $carbon = new Carbon($date); - - return $carbon->isoFormat($format); - } - ); - } - - /** - * @return TwigFunction - * TODO remove me when layout v1 is deprecated. - */ - protected function getMetaField(): TwigFunction - { - return new TwigFunction( - 'accountGetMetaField', - static function (Account $account, string $field): string { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $result = $repository->getMetaValue($account, $field); - if (null === $result) { - return ''; - } - - return $result; - } - ); - } - - /** - * Will return true if the user is of role X. - * - * @return TwigFunction - */ - protected function hasRole(): TwigFunction - { - return new TwigFunction( - 'hasRole', - static function (string $role): bool { - $repository = app(UserRepositoryInterface::class); - if ($repository->hasRole(auth()->user(), $role)) { - return true; - } - - return false; - } - ); - } - - protected function getRootSearchOperator(): TwigFunction - { - return new TwigFunction( - 'getRootSearchOperator', - static function (string $operator): string { - $result = OperatorQuerySearch::getRootOperator($operator); - return str_replace('-', 'not_', $result); - } - ); - } - - /** - * @return TwigFunction - */ - protected function carbonize(): TwigFunction - { - return new TwigFunction( - 'carbonize', - static function (string $date): Carbon { - return new Carbon($date); - } - ); - } } diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php index 924cd5157d..1182eea8e4 100644 --- a/app/Support/Twig/Rule.php +++ b/app/Support/Twig/Rule.php @@ -33,15 +33,25 @@ use Twig\TwigFunction; class Rule extends AbstractExtension { /** - * @return array + * @return TwigFunction */ - public function getFunctions(): array + public function allActionTriggers(): TwigFunction { - return [ - $this->allJournalTriggers(), - $this->allRuleTriggers(), - $this->allActionTriggers(), - ]; + return new TwigFunction( + 'allRuleActions', + static function () { + // array of valid values for actions + $ruleActions = array_keys(Config::get('firefly.rule-actions')); + $possibleActions = []; + foreach ($ruleActions as $key) { + $possibleActions[$key] = (string)trans('firefly.rule_action_'.$key.'_choice'); + } + unset($ruleActions); + asort($possibleActions); + + return $possibleActions; + } + ); } /** @@ -84,24 +94,14 @@ class Rule extends AbstractExtension } /** - * @return TwigFunction + * @return array */ - public function allActionTriggers(): TwigFunction + public function getFunctions(): array { - return new TwigFunction( - 'allRuleActions', - static function () { - // array of valid values for actions - $ruleActions = array_keys(Config::get('firefly.rule-actions')); - $possibleActions = []; - foreach ($ruleActions as $key) { - $possibleActions[$key] = (string)trans('firefly.rule_action_'.$key.'_choice'); - } - unset($ruleActions); - asort($possibleActions); - - return $possibleActions; - } - ); + return [ + $this->allJournalTriggers(), + $this->allRuleTriggers(), + $this->allActionTriggers(), + ]; } } diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php index 2eb9f226cf..f99762766c 100644 --- a/app/Support/Twig/TransactionGroupTwig.php +++ b/app/Support/Twig/TransactionGroupTwig.php @@ -76,201 +76,6 @@ class TransactionGroupTwig extends AbstractExtension ); } - /** - * Generate normal amount for transaction from a transaction group. - * - * @param array $array - * - * @return string - */ - private function normalJournalArrayAmount(array $array): string - { - $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; - $amount = $array['amount'] ?? '0'; - $colored = true; - $sourceType = $array['source_account_type'] ?? 'invalid'; - $amount = $this->signAmount($amount, $type, $sourceType); - - if ($type === TransactionType::TRANSFER) { - $colored = false; - } - - $result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored); - if ($type === TransactionType::TRANSFER) { - $result = sprintf('%s', $result); - } - - return $result; - } - - /** - * @param string $amount - * @param string $transactionType - * @param string $sourceType - * - * @return string - */ - private function signAmount(string $amount, string $transactionType, string $sourceType): string - { - // withdrawals stay negative - if ($transactionType !== TransactionType::WITHDRAWAL) { - $amount = bcmul($amount, '-1'); - } - - // opening balance and it comes from initial balance? its expense. - if ($transactionType === TransactionType::OPENING_BALANCE && AccountType::INITIAL_BALANCE !== $sourceType) { - $amount = bcmul($amount, '-1'); - } - - // reconciliation and it comes from reconciliation? - if ($transactionType === TransactionType::RECONCILIATION && AccountType::RECONCILIATION !== $sourceType) { - $amount = bcmul($amount, '-1'); - } - - return $amount; - } - - /** - * Generate foreign amount for transaction from a transaction group. - * - * @param array $array - * - * @return string - */ - private function foreignJournalArrayAmount(array $array): string - { - $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; - $amount = $array['foreign_amount'] ?? '0'; - $colored = true; - - $sourceType = $array['source_account_type'] ?? 'invalid'; - $amount = $this->signAmount($amount, $type, $sourceType); - - if ($type === TransactionType::TRANSFER) { - $colored = false; - } - $result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored); - if ($type === TransactionType::TRANSFER) { - $result = sprintf('%s', $result); - } - - return $result; - } - - /** - * Shows the amount for a single journal object. - * - * @return TwigFunction - */ - public function journalObjectAmount(): TwigFunction - { - return new TwigFunction( - 'journalObjectAmount', - function (TransactionJournal $journal): string { - $result = $this->normalJournalObjectAmount($journal); - // now append foreign amount, if any. - if ($this->journalObjectHasForeign($journal)) { - $foreign = $this->foreignJournalObjectAmount($journal); - $result = sprintf('%s (%s)', $result, $foreign); - } - - return $result; - }, - ['is_safe' => ['html']] - ); - } - - /** - * Generate normal amount for transaction from a transaction group. - * - * @param TransactionJournal $journal - * - * @return string - */ - private function normalJournalObjectAmount(TransactionJournal $journal): string - { - $type = $journal->transactionType->type; - $first = $journal->transactions()->where('amount', '<', 0)->first(); - $currency = $journal->transactionCurrency; - $amount = $first->amount ?? '0'; - $colored = true; - $sourceType = $first->account()->first()->accountType()->first()->type; - - $amount = $this->signAmount($amount, $type, $sourceType); - - if ($type === TransactionType::TRANSFER) { - $colored = false; - } - $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); - if ($type === TransactionType::TRANSFER) { - $result = sprintf('%s', $result); - } - - return $result; - } - - /** - * @param TransactionJournal $journal - * - * @return bool - */ - private function journalObjectHasForeign(TransactionJournal $journal): bool - { - /** @var Transaction $first */ - $first = $journal->transactions()->where('amount', '<', 0)->first(); - - return '' !== $first->foreign_amount; - } - - /** - * Generate foreign amount for journal from a transaction group. - * - * @param TransactionJournal $journal - * - * @return string - */ - private function foreignJournalObjectAmount(TransactionJournal $journal): string - { - $type = $journal->transactionType->type; - /** @var Transaction $first */ - $first = $journal->transactions()->where('amount', '<', 0)->first(); - $currency = $first->foreignCurrency; - $amount = '' === $first->foreign_amount ? '0' : $first->foreign_amount; - $colored = true; - $sourceType = $first->account()->first()->accountType()->first()->type; - - $amount = $this->signAmount($amount, $type, $sourceType); - - if ($type === TransactionType::TRANSFER) { - $colored = false; - } - $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); - if ($type === TransactionType::TRANSFER) { - $result = sprintf('%s', $result); - } - - return $result; - } - - /** - * @return TwigFunction - */ - public function journalHasMeta(): TwigFunction - { - return new TwigFunction( - 'journalHasMeta', - static function (int $journalId, string $metaField) { - $count = DB::table('journal_meta') - ->where('name', $metaField) - ->where('transaction_journal_id', $journalId) - ->whereNull('deleted_at') - ->count(); - - return 1 === $count; - } - ); - } - /** * @return TwigFunction */ @@ -314,4 +119,199 @@ class TransactionGroupTwig extends AbstractExtension } ); } + + /** + * @return TwigFunction + */ + public function journalHasMeta(): TwigFunction + { + return new TwigFunction( + 'journalHasMeta', + static function (int $journalId, string $metaField) { + $count = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->count(); + + return 1 === $count; + } + ); + } + + /** + * Shows the amount for a single journal object. + * + * @return TwigFunction + */ + public function journalObjectAmount(): TwigFunction + { + return new TwigFunction( + 'journalObjectAmount', + function (TransactionJournal $journal): string { + $result = $this->normalJournalObjectAmount($journal); + // now append foreign amount, if any. + if ($this->journalObjectHasForeign($journal)) { + $foreign = $this->foreignJournalObjectAmount($journal); + $result = sprintf('%s (%s)', $result, $foreign); + } + + return $result; + }, + ['is_safe' => ['html']] + ); + } + + /** + * Generate foreign amount for transaction from a transaction group. + * + * @param array $array + * + * @return string + */ + private function foreignJournalArrayAmount(array $array): string + { + $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $amount = $array['foreign_amount'] ?? '0'; + $colored = true; + + $sourceType = $array['source_account_type'] ?? 'invalid'; + $amount = $this->signAmount($amount, $type, $sourceType); + + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * Generate foreign amount for journal from a transaction group. + * + * @param TransactionJournal $journal + * + * @return string + */ + private function foreignJournalObjectAmount(TransactionJournal $journal): string + { + $type = $journal->transactionType->type; + /** @var Transaction $first */ + $first = $journal->transactions()->where('amount', '<', 0)->first(); + $currency = $first->foreignCurrency; + $amount = '' === $first->foreign_amount ? '0' : $first->foreign_amount; + $colored = true; + $sourceType = $first->account()->first()->accountType()->first()->type; + + $amount = $this->signAmount($amount, $type, $sourceType); + + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * @param TransactionJournal $journal + * + * @return bool + */ + private function journalObjectHasForeign(TransactionJournal $journal): bool + { + /** @var Transaction $first */ + $first = $journal->transactions()->where('amount', '<', 0)->first(); + + return '' !== $first->foreign_amount; + } + + /** + * Generate normal amount for transaction from a transaction group. + * + * @param array $array + * + * @return string + */ + private function normalJournalArrayAmount(array $array): string + { + $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $amount = $array['amount'] ?? '0'; + $colored = true; + $sourceType = $array['source_account_type'] ?? 'invalid'; + $amount = $this->signAmount($amount, $type, $sourceType); + + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + + $result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * Generate normal amount for transaction from a transaction group. + * + * @param TransactionJournal $journal + * + * @return string + */ + private function normalJournalObjectAmount(TransactionJournal $journal): string + { + $type = $journal->transactionType->type; + $first = $journal->transactions()->where('amount', '<', 0)->first(); + $currency = $journal->transactionCurrency; + $amount = $first->amount ?? '0'; + $colored = true; + $sourceType = $first->account()->first()->accountType()->first()->type; + + $amount = $this->signAmount($amount, $type, $sourceType); + + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * @param string $amount + * @param string $transactionType + * @param string $sourceType + * + * @return string + */ + private function signAmount(string $amount, string $transactionType, string $sourceType): string + { + // withdrawals stay negative + if ($transactionType !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + + // opening balance and it comes from initial balance? its expense. + if ($transactionType === TransactionType::OPENING_BALANCE && AccountType::INITIAL_BALANCE !== $sourceType) { + $amount = bcmul($amount, '-1'); + } + + // reconciliation and it comes from reconciliation? + if ($transactionType === TransactionType::RECONCILIATION && AccountType::RECONCILIATION !== $sourceType) { + $amount = bcmul($amount, '-1'); + } + + return $amount; + } } diff --git a/app/TransactionRules/Actions/AppendNotesToDescription.php b/app/TransactionRules/Actions/AppendNotesToDescription.php index 387c1e87b2..d9d5445a14 100644 --- a/app/TransactionRules/Actions/AppendNotesToDescription.php +++ b/app/TransactionRules/Actions/AppendNotesToDescription.php @@ -72,7 +72,7 @@ class AppendNotesToDescription implements ActionInterface // only append if there is something to append if ('' !== $note->text) { $before = $object->description; - $object->description = trim(sprintf("%s %s", $object->description, (string)$this->clearString($note->text, false))); + $object->description = trim(sprintf('%s %s', $object->description, (string)$this->clearString($note->text, false))); $object->save(); Log::debug(sprintf('Journal description is updated to "%s".', $object->description)); diff --git a/app/TransactionRules/Actions/ConvertToDeposit.php b/app/TransactionRules/Actions/ConvertToDeposit.php index c4f774d543..bfff2b088f 100644 --- a/app/TransactionRules/Actions/ConvertToDeposit.php +++ b/app/TransactionRules/Actions/ConvertToDeposit.php @@ -114,6 +114,60 @@ class ConvertToDeposit implements ActionInterface return false; } + /** + * Input is a transfer from A to B. + * Output is a deposit from C to B. + * The source account is replaced. + * + * @param TransactionJournal $journal + * + * @return bool + * @throws FireflyException + * @throws JsonException + */ + private function convertTransferArray(TransactionJournal $journal): bool + { + $user = $journal->user; + // find or create revenue account. + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($user); + + $repository = app(AccountRepositoryInterface::class); + $repository->setUser($user); + + $sourceAccount = $this->getSourceAccount($journal); + + // get the action value, or use the original source name in case the action value is empty: + // this becomes a new or existing (revenue) account, which is the source of the new deposit. + $opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value; + // we check all possible source account types if one exists: + $validTypes = config('firefly.expected_source_types.source.Deposit'); + $opposingAccount = $repository->findByName($opposingName, $validTypes); + if (null === $opposingAccount) { + $opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE); + } + + Log::debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $this->action->action_value, $opposingAccount->name)); + + // update source transaction(s) to be revenue account + DB::table('transactions') + ->where('transaction_journal_id', '=', $journal->id) + ->where('amount', '<', 0) + ->update(['account_id' => $opposingAccount->id]); + + // change transaction type of journal: + $newType = TransactionType::whereType(TransactionType::DEPOSIT)->first(); + + DB::table('transaction_journals') + ->where('id', '=', $journal->id) + ->update(['transaction_type_id' => $newType->id, 'bill_id' => null]); + + Log::debug('Converted transfer to deposit.'); + + return true; + } + /** * Input is a withdrawal from A to B * Is converted to a deposit from C to A. @@ -175,57 +229,18 @@ class ConvertToDeposit implements ActionInterface } /** - * Input is a transfer from A to B. - * Output is a deposit from C to B. - * The source account is replaced. - * * @param TransactionJournal $journal - * - * @return bool + * @return Account * @throws FireflyException - * @throws JsonException */ - private function convertTransferArray(TransactionJournal $journal): bool + private function getDestinationAccount(TransactionJournal $journal): Account { - $user = $journal->user; - // find or create revenue account. - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($user); - - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($user); - - $sourceAccount = $this->getSourceAccount($journal); - - // get the action value, or use the original source name in case the action value is empty: - // this becomes a new or existing (revenue) account, which is the source of the new deposit. - $opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value; - // we check all possible source account types if one exists: - $validTypes = config('firefly.expected_source_types.source.Deposit'); - $opposingAccount = $repository->findByName($opposingName, $validTypes); - if (null === $opposingAccount) { - $opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE); + /** @var Transaction|null $destAccount */ + $destAccount = $journal->transactions()->where('amount', '>', 0)->first(); + if (null === $destAccount) { + throw new FireflyException(sprintf('Cannot find destination transaction for journal #%d', $journal->id)); } - - Log::debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $this->action->action_value, $opposingAccount->name)); - - // update source transaction(s) to be revenue account - DB::table('transactions') - ->where('transaction_journal_id', '=', $journal->id) - ->where('amount', '<', 0) - ->update(['account_id' => $opposingAccount->id]); - - // change transaction type of journal: - $newType = TransactionType::whereType(TransactionType::DEPOSIT)->first(); - - DB::table('transaction_journals') - ->where('id', '=', $journal->id) - ->update(['transaction_type_id' => $newType->id, 'bill_id' => null]); - - Log::debug('Converted transfer to deposit.'); - - return true; + return $destAccount->account; } /** @@ -242,19 +257,4 @@ class ConvertToDeposit implements ActionInterface } return $sourceTransaction->account; } - - /** - * @param TransactionJournal $journal - * @return Account - * @throws FireflyException - */ - private function getDestinationAccount(TransactionJournal $journal): Account - { - /** @var Transaction|null $destAccount */ - $destAccount = $journal->transactions()->where('amount', '>', 0)->first(); - if (null === $destAccount) { - throw new FireflyException(sprintf('Cannot find destination transaction for journal #%d', $journal->id)); - } - return $destAccount->account; - } } diff --git a/app/TransactionRules/Actions/ConvertToTransfer.php b/app/TransactionRules/Actions/ConvertToTransfer.php index 28dddc06a2..bf78ae8025 100644 --- a/app/TransactionRules/Actions/ConvertToTransfer.php +++ b/app/TransactionRules/Actions/ConvertToTransfer.php @@ -139,6 +139,48 @@ class ConvertToTransfer implements ActionInterface return false; } + /** + * A deposit is from Revenue to Asset. + * We replace the Revenue with another asset. + * + * @param TransactionJournal $journal + * @param Account $opposing + * + * @return bool + * @throws FireflyException + */ + private function convertDepositArray(TransactionJournal $journal, Account $opposing): bool + { + $destAccount = $this->getDestinationAccount($journal); + if ((int)$destAccount->id === (int)$opposing->id) { + Log::error( + vsprintf( + 'Journal #%d has already has "%s" as a destination asset. ConvertToTransfer failed. (rule #%d).', + [$journal->id, $opposing->name, $this->action->rule_id] + ) + ); + + return false; + } + + // update source transaction: + DB::table('transactions') + ->where('transaction_journal_id', '=', $journal->id) + ->where('amount', '<', 0) + ->update(['account_id' => $opposing->id]); + + // change transaction type of journal: + $newType = TransactionType::whereType(TransactionType::TRANSFER)->first(); + + DB::table('transaction_journals') + ->where('id', '=', $journal->id) + ->update(['transaction_type_id' => $newType->id, 'bill_id' => null]); + + Log::debug('Converted deposit to transfer.'); + + return true; + } + /** * A withdrawal is from Asset to Expense. * We replace the Expense with another asset. @@ -182,63 +224,6 @@ class ConvertToTransfer implements ActionInterface return true; } - /** - * A deposit is from Revenue to Asset. - * We replace the Revenue with another asset. - * - * @param TransactionJournal $journal - * @param Account $opposing - * - * @return bool - * @throws FireflyException - */ - private function convertDepositArray(TransactionJournal $journal, Account $opposing): bool - { - $destAccount = $this->getDestinationAccount($journal); - if ((int)$destAccount->id === (int)$opposing->id) { - Log::error( - vsprintf( - 'Journal #%d has already has "%s" as a destination asset. ConvertToTransfer failed. (rule #%d).', - [$journal->id, $opposing->name, $this->action->rule_id] - ) - ); - - return false; - } - - // update source transaction: - DB::table('transactions') - ->where('transaction_journal_id', '=', $journal->id) - ->where('amount', '<', 0) - ->update(['account_id' => $opposing->id]); - - // change transaction type of journal: - $newType = TransactionType::whereType(TransactionType::TRANSFER)->first(); - - DB::table('transaction_journals') - ->where('id', '=', $journal->id) - ->update(['transaction_type_id' => $newType->id, 'bill_id' => null]); - - Log::debug('Converted deposit to transfer.'); - - return true; - } - - /** - * @param TransactionJournal $journal - * @return Account - * @throws FireflyException - */ - private function getSourceAccount(TransactionJournal $journal): Account - { - /** @var Transaction|null $sourceTransaction */ - $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); - if (null === $sourceTransaction) { - throw new FireflyException(sprintf('Cannot find source transaction for journal #%d', $journal->id)); - } - return $sourceTransaction->account; - } - /** * @param TransactionJournal $journal * @return Account @@ -254,21 +239,6 @@ class ConvertToTransfer implements ActionInterface return $destAccount->account; } - /** - * @param int $journalId - * @return string - */ - private function getSourceType(int $journalId): string - { - /** @var TransactionJournal $journal */ - $journal = TransactionJournal::find($journalId); - if (null === $journal) { - Log::error(sprintf('Journal #%d does not exist. Cannot convert to transfer.', $journalId)); - return ''; - } - return (string)$journal->transactions()->where('amount', '<', 0)->first()?->account?->accountType?->type; - } - /** * @param int $journalId * @return string @@ -283,4 +253,34 @@ class ConvertToTransfer implements ActionInterface } return (string)$journal->transactions()->where('amount', '>', 0)->first()?->account?->accountType?->type; } + + /** + * @param TransactionJournal $journal + * @return Account + * @throws FireflyException + */ + private function getSourceAccount(TransactionJournal $journal): Account + { + /** @var Transaction|null $sourceTransaction */ + $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); + if (null === $sourceTransaction) { + throw new FireflyException(sprintf('Cannot find source transaction for journal #%d', $journal->id)); + } + return $sourceTransaction->account; + } + + /** + * @param int $journalId + * @return string + */ + private function getSourceType(int $journalId): string + { + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::find($journalId); + if (null === $journal) { + Log::error(sprintf('Journal #%d does not exist. Cannot convert to transfer.', $journalId)); + return ''; + } + return (string)$journal->transactions()->where('amount', '<', 0)->first()?->account?->accountType?->type; + } } diff --git a/app/TransactionRules/Actions/ConvertToWithdrawal.php b/app/TransactionRules/Actions/ConvertToWithdrawal.php index c2a414bc7e..a1d4fb9be3 100644 --- a/app/TransactionRules/Actions/ConvertToWithdrawal.php +++ b/app/TransactionRules/Actions/ConvertToWithdrawal.php @@ -34,7 +34,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\User; use Illuminate\Support\Facades\Log; use JsonException; @@ -113,7 +112,7 @@ class ConvertToWithdrawal implements ActionInterface } /** - * @param TransactionJournal $journal + * @param TransactionJournal $journal * @return bool * @throws FireflyException * @throws JsonException @@ -170,7 +169,7 @@ class ConvertToWithdrawal implements ActionInterface * Input is a transfer from A to B. * Output is a withdrawal from A to C. * - * @param TransactionJournal $journal + * @param TransactionJournal $journal * * @return bool * @throws FireflyException @@ -187,7 +186,7 @@ class ConvertToWithdrawal implements ActionInterface $repository = app(AccountRepositoryInterface::class); $repository->setUser($user); - $destAccount = $this->getDestinationAccount($journal); + $destAccount = $this->getDestinationAccount($journal); // get the action value, or use the original source name in case the action value is empty: // this becomes a new or existing (expense) account, which is the destination of the new withdrawal. @@ -218,21 +217,6 @@ class ConvertToWithdrawal implements ActionInterface return true; } - /** - * @param TransactionJournal $journal - * @return Account - * @throws FireflyException - */ - private function getSourceAccount(TransactionJournal $journal): Account - { - /** @var Transaction|null $sourceTransaction */ - $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); - if (null === $sourceTransaction) { - throw new FireflyException(sprintf('Cannot find source transaction for journal #%d', $journal->id)); - } - return $sourceTransaction->account; - } - /** * @param TransactionJournal $journal * @return Account @@ -247,4 +231,19 @@ class ConvertToWithdrawal implements ActionInterface } return $destAccount->account; } + + /** + * @param TransactionJournal $journal + * @return Account + * @throws FireflyException + */ + private function getSourceAccount(TransactionJournal $journal): Account + { + /** @var Transaction|null $sourceTransaction */ + $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); + if (null === $sourceTransaction) { + throw new FireflyException(sprintf('Cannot find source transaction for journal #%d', $journal->id)); + } + return $sourceTransaction->account; + } } diff --git a/app/TransactionRules/Actions/UpdatePiggybank.php b/app/TransactionRules/Actions/UpdatePiggybank.php index 96a14032ce..f5b7b0b9e2 100644 --- a/app/TransactionRules/Actions/UpdatePiggybank.php +++ b/app/TransactionRules/Actions/UpdatePiggybank.php @@ -134,6 +134,49 @@ class UpdatePiggybank implements ActionInterface return false; } + /** + * @param PiggyBank $piggyBank + * @param TransactionJournal $journal + * @param string $amount + * @return void + */ + private function addAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void + { + $repository = app(PiggyBankRepositoryInterface::class); + $repository->setUser($journal->user); + + // how much can we add to the piggy bank? + if (0 !== bccomp($piggyBank->targetamount, '0')) { + $toAdd = bcsub($piggyBank->targetamount, $repository->getCurrentAmount($piggyBank)); + Log::debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount)); + + // update amount to fit: + $amount = -1 === bccomp($amount, $toAdd) ? $amount : $toAdd; + Log::debug(sprintf('Amount is now %s', $amount)); + } + if (0 === bccomp($piggyBank->targetamount, '0')) { + Log::debug('Target amount is zero, can add anything.'); + } + + + // if amount is zero, stop. + if (0 === bccomp('0', $amount)) { + app('log')->warning('Amount left is zero, stop.'); + + return; + } + + // make sure we can add amount: + if (false === $repository->canAddAmount($piggyBank, $amount)) { + app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount)); + + return; + } + Log::debug(sprintf('Will now add %s to piggy bank.', $amount)); + + $repository->addAmount($piggyBank, $amount, $journal); + } + /** * @param User $user * @@ -180,47 +223,4 @@ class UpdatePiggybank implements ActionInterface $repository->removeAmount($piggyBank, $amount, $journal); } - - /** - * @param PiggyBank $piggyBank - * @param TransactionJournal $journal - * @param string $amount - * @return void - */ - private function addAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void - { - $repository = app(PiggyBankRepositoryInterface::class); - $repository->setUser($journal->user); - - // how much can we add to the piggy bank? - if (0 !== bccomp($piggyBank->targetamount, '0')) { - $toAdd = bcsub($piggyBank->targetamount, $repository->getCurrentAmount($piggyBank)); - Log::debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount)); - - // update amount to fit: - $amount = -1 === bccomp($amount, $toAdd) ? $amount : $toAdd; - Log::debug(sprintf('Amount is now %s', $amount)); - } - if (0 === bccomp($piggyBank->targetamount, '0')) { - Log::debug('Target amount is zero, can add anything.'); - } - - - // if amount is zero, stop. - if (0 === bccomp('0', $amount)) { - app('log')->warning('Amount left is zero, stop.'); - - return; - } - - // make sure we can add amount: - if (false === $repository->canAddAmount($piggyBank, $amount)) { - app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount)); - - return; - } - Log::debug(sprintf('Will now add %s to piggy bank.', $amount)); - - $repository->addAmount($piggyBank, $amount, $journal); - } } diff --git a/app/TransactionRules/Engine/RuleEngineInterface.php b/app/TransactionRules/Engine/RuleEngineInterface.php index 5d58065fa1..cca1956ee1 100644 --- a/app/TransactionRules/Engine/RuleEngineInterface.php +++ b/app/TransactionRules/Engine/RuleEngineInterface.php @@ -55,6 +55,12 @@ interface RuleEngineInterface */ public function getResults(): int; + /** + * @param bool $refreshTriggers + * @return void + */ + public function setRefreshTriggers(bool $refreshTriggers): void; + /** * Add entire rule groups for the engine to execute. * @@ -73,10 +79,4 @@ interface RuleEngineInterface * @param User $user */ public function setUser(User $user): void; - - /** - * @param bool $refreshTriggers - * @return void - */ - public function setRefreshTriggers(bool $refreshTriggers): void; } diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php index 11bc21d75c..69248cee96 100644 --- a/app/TransactionRules/Engine/SearchRuleEngine.php +++ b/app/TransactionRules/Engine/SearchRuleEngine.php @@ -44,10 +44,10 @@ class SearchRuleEngine implements RuleEngineInterface { private Collection $groups; private array $operators; + private bool $refreshTriggers; private array $resultCount; private Collection $rules; private User $user; - private bool $refreshTriggers; public function __construct() { @@ -91,129 +91,79 @@ class SearchRuleEngine implements RuleEngineInterface } /** - * Finds the transactions a strict rule will execute on. - * - * @param Rule $rule - * - * @return Collection + * @inheritDoc + * @throws FireflyException */ - private function findStrictRule(Rule $rule): Collection + public function fire(): void { - Log::debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0)); - $searchArray = []; - $triggers = []; - if ($this->refreshTriggers) { - $triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); - } - if (!$this->refreshTriggers) { - $triggers = $rule->ruleTriggers; - } + $this->resultCount = []; + Log::debug('SearchRuleEngine::fire()!'); - /** @var RuleTrigger $ruleTrigger */ - foreach ($triggers as $ruleTrigger) { - if (false === $ruleTrigger->active) { - continue; + // if rules and no rule groups, file each rule separately. + if (0 !== $this->rules->count()) { + Log::debug(sprintf('SearchRuleEngine:: found %d rule(s) to fire.', $this->rules->count())); + foreach ($this->rules as $rule) { + $this->fireRule($rule); } + Log::debug('SearchRuleEngine:: done processing all rules!'); - // if needs no context, value is different: - $needsContext = config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; - if (false === $needsContext) { - Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:true', $ruleTrigger->trigger_type)); - $searchArray[$ruleTrigger->trigger_type][] = 'true'; - } - if (true === $needsContext) { - Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:"%s"', $ruleTrigger->trigger_type, $ruleTrigger->trigger_value)); - $searchArray[$ruleTrigger->trigger_type][] = sprintf('"%s"', $ruleTrigger->trigger_value); + return; + } + if (0 !== $this->groups->count()) { + Log::debug(sprintf('SearchRuleEngine:: found %d rule group(s) to fire.', $this->groups->count())); + // fire each group: + /** @var RuleGroup $group */ + foreach ($this->groups as $group) { + $this->fireGroup($group); } } - - - // add local operators: - foreach ($this->operators as $operator) { - Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); - $searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']); - } - $date = today(config('app.timezone')); - if ($this->hasSpecificJournalTrigger($searchArray)) { - $date = $this->setDateFromJournalTrigger($searchArray); - } - // build and run the search engine. - $searchEngine = app(SearchInterface::class); - $searchEngine->setUser($this->user); - $searchEngine->setPage(1); - $searchEngine->setLimit(31337); - $searchEngine->setDate($date); - - foreach ($searchArray as $type => $searches) { - foreach ($searches as $value) { - $searchEngine->parseQuery(sprintf('%s:%s', $type, $value)); - } - } - - $result = $searchEngine->searchTransactions(); - - return $result->getCollection(); + Log::debug('SearchRuleEngine:: done processing all rules!'); } /** - * Search in the triggers of this particular search and if it contains - * one search operator for "journal_id" it means the date ranges - * in the search may need to be updated. + * Return the number of changed transactions from the previous "fire" action. * - * @param array $array - * - * @return bool + * @return int */ - private function hasSpecificJournalTrigger(array $array): bool + public function getResults(): int { - Log::debug('Now in hasSpecificJournalTrigger.'); - $journalTrigger = false; - $dateTrigger = false; - foreach ($array as $triggerName => $values) { - if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) { - Log::debug('Found a journal_id trigger with 1 journal, true.'); - $journalTrigger = true; - } - if (in_array($triggerName, ['date_is', 'date', 'on', 'date_before', 'before', 'date_after', 'after'], true)) { - Log::debug('Found a date related trigger, set to true.'); - $dateTrigger = true; - } - } - $result = $journalTrigger && $dateTrigger; - Log::debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true))); - - return $result; + return count($this->resultCount); } /** - * @param array $array - * - * @return Carbon + * @param bool $refreshTriggers */ - private function setDateFromJournalTrigger(array $array): Carbon + public function setRefreshTriggers(bool $refreshTriggers): void { - Log::debug('Now in setDateFromJournalTrigger()'); - $journalId = 0; - foreach ($array as $triggerName => $values) { - if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) { - $journalId = (int)trim(($values[0] ?? '"0"'), '"'); // follows format "123". - Log::debug(sprintf('Found journal ID #%d', $journalId)); + $this->refreshTriggers = $refreshTriggers; + } + + /** + * @inheritDoc + */ + public function setRuleGroups(Collection $ruleGroups): void + { + Log::debug(__METHOD__); + foreach ($ruleGroups as $group) { + if ($group instanceof RuleGroup) { + Log::debug(sprintf('Adding a rule group to the SearchRuleEngine: #%d ("%s")', $group->id, $group->title)); + $this->groups->push($group); } } - if (0 !== $journalId) { - $repository = app(JournalRepositoryInterface::class); - $repository->setUser($this->user); - $journal = $repository->find($journalId); - if (null !== $journal) { - $date = $journal->date; - Log::debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d'))); + } - return $date; + /** + * @inheritDoc + */ + public function setRules(Collection $rules): void + { + Log::debug(__METHOD__); + foreach ($rules as $rule) { + if ($rule instanceof Rule) { + Log::debug(sprintf('Adding a rule to the SearchRuleEngine: #%d ("%s")', $rule->id, $rule->title)); + $this->rules->push($rule); } } - Log::debug('Found no journal, return default date.'); - - return today(config('app.timezone')); } /** @@ -309,33 +259,112 @@ class SearchRuleEngine implements RuleEngineInterface } /** - * @inheritDoc + * Finds the transactions a strict rule will execute on. + * + * @param Rule $rule + * + * @return Collection + */ + private function findStrictRule(Rule $rule): Collection + { + Log::debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0)); + $searchArray = []; + $triggers = []; + if ($this->refreshTriggers) { + $triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); + } + if (!$this->refreshTriggers) { + $triggers = $rule->ruleTriggers; + } + + /** @var RuleTrigger $ruleTrigger */ + foreach ($triggers as $ruleTrigger) { + if (false === $ruleTrigger->active) { + continue; + } + + // if needs no context, value is different: + $needsContext = config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; + if (false === $needsContext) { + Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:true', $ruleTrigger->trigger_type)); + $searchArray[$ruleTrigger->trigger_type][] = 'true'; + } + if (true === $needsContext) { + Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:"%s"', $ruleTrigger->trigger_type, $ruleTrigger->trigger_value)); + $searchArray[$ruleTrigger->trigger_type][] = sprintf('"%s"', $ruleTrigger->trigger_value); + } + } + + + // add local operators: + foreach ($this->operators as $operator) { + Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); + $searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']); + } + $date = today(config('app.timezone')); + if ($this->hasSpecificJournalTrigger($searchArray)) { + $date = $this->setDateFromJournalTrigger($searchArray); + } + // build and run the search engine. + $searchEngine = app(SearchInterface::class); + $searchEngine->setUser($this->user); + $searchEngine->setPage(1); + $searchEngine->setLimit(31337); + $searchEngine->setDate($date); + + foreach ($searchArray as $type => $searches) { + foreach ($searches as $value) { + $searchEngine->parseQuery(sprintf('%s:%s', $type, $value)); + } + } + + $result = $searchEngine->searchTransactions(); + + return $result->getCollection(); + } + + /** + * @param RuleGroup $group + * + * @return void * @throws FireflyException */ - public function fire(): void + private function fireGroup(RuleGroup $group): void { - $this->resultCount = []; - Log::debug('SearchRuleEngine::fire()!'); - - // if rules and no rule groups, file each rule separately. - if (0 !== $this->rules->count()) { - Log::debug(sprintf('SearchRuleEngine:: found %d rule(s) to fire.', $this->rules->count())); - foreach ($this->rules as $rule) { - $this->fireRule($rule); + $all = false; + Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count())); + /** @var Rule $rule */ + foreach ($group->rules as $rule) { + Log::debug(sprintf('Going to fire rule #%d from group #%d', $rule->id, $group->id)); + $result = $this->fireRule($rule); + if (true === $result) { + $all = true; } - Log::debug('SearchRuleEngine:: done processing all rules!'); + if (true === $result && true === $rule->stop_processing) { + Log::debug(sprintf('The rule was triggered and rule->stop_processing = true, so group #%d will stop processing further rules.', $group->id)); - return; - } - if (0 !== $this->groups->count()) { - Log::debug(sprintf('SearchRuleEngine:: found %d rule group(s) to fire.', $this->groups->count())); - // fire each group: - /** @var RuleGroup $group */ - foreach ($this->groups as $group) { - $this->fireGroup($group); + return; } } - Log::debug('SearchRuleEngine:: done processing all rules!'); + } + + /** + * Return true if the rule is fired (the collection is larger than zero). + * + * @param Rule $rule + * + * @return bool + * @throws FireflyException + */ + private function fireNonStrictRule(Rule $rule): bool + { + Log::debug(sprintf('SearchRuleEngine::fireNonStrictRule(%d)!', $rule->id)); + $collection = $this->findNonStrictRule($rule); + + $this->processResults($rule, $collection); + Log::debug(sprintf('SearchRuleEngine:: done processing non-strict rule #%d', $rule->id)); + + return $collection->count() > 0; } /** @@ -391,6 +420,36 @@ class SearchRuleEngine implements RuleEngineInterface return false; } + /** + * Search in the triggers of this particular search and if it contains + * one search operator for "journal_id" it means the date ranges + * in the search may need to be updated. + * + * @param array $array + * + * @return bool + */ + private function hasSpecificJournalTrigger(array $array): bool + { + Log::debug('Now in hasSpecificJournalTrigger.'); + $journalTrigger = false; + $dateTrigger = false; + foreach ($array as $triggerName => $values) { + if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) { + Log::debug('Found a journal_id trigger with 1 journal, true.'); + $journalTrigger = true; + } + if (in_array($triggerName, ['date_is', 'date', 'on', 'date_before', 'before', 'date_after', 'after'], true)) { + Log::debug('Found a date related trigger, set to true.'); + $dateTrigger = true; + } + } + $result = $journalTrigger && $dateTrigger; + Log::debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true))); + + return $result; + } + /** * @param Rule $rule * @param Collection $collection @@ -406,43 +465,6 @@ class SearchRuleEngine implements RuleEngineInterface } } - /** - * @param Rule $rule - * @param array $group - * - * @throws FireflyException - */ - private function processTransactionGroup(Rule $rule, array $group): void - { - Log::debug(sprintf('SearchRuleEngine:: Will now execute actions on transaction group #%d', $group['id'])); - /** @var array $transaction */ - foreach ($group['transactions'] as $transaction) { - $this->processTransactionJournal($rule, $transaction); - } - } - - /** - * @param Rule $rule - * @param array $transaction - * - * @throws FireflyException - */ - private function processTransactionJournal(Rule $rule, array $transaction): void - { - Log::debug(sprintf('SearchRuleEngine:: Will now execute actions on transaction journal #%d', $transaction['transaction_journal_id'])); - $actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); - /** @var RuleAction $ruleAction */ - foreach ($actions as $ruleAction) { - if (false === $ruleAction->active) { - continue; - } - $break = $this->processRuleAction($ruleAction, $transaction); - if (true === $break) { - break; - } - } - } - /** * @param RuleAction $ruleAction * @param array $transaction @@ -482,92 +504,70 @@ class SearchRuleEngine implements RuleEngineInterface } /** - * Return true if the rule is fired (the collection is larger than zero). - * * @param Rule $rule + * @param array $group * - * @return bool * @throws FireflyException */ - private function fireNonStrictRule(Rule $rule): bool + private function processTransactionGroup(Rule $rule, array $group): void { - Log::debug(sprintf('SearchRuleEngine::fireNonStrictRule(%d)!', $rule->id)); - $collection = $this->findNonStrictRule($rule); - - $this->processResults($rule, $collection); - Log::debug(sprintf('SearchRuleEngine:: done processing non-strict rule #%d', $rule->id)); - - return $collection->count() > 0; + Log::debug(sprintf('SearchRuleEngine:: Will now execute actions on transaction group #%d', $group['id'])); + /** @var array $transaction */ + foreach ($group['transactions'] as $transaction) { + $this->processTransactionJournal($rule, $transaction); + } } /** - * @param RuleGroup $group + * @param Rule $rule + * @param array $transaction * - * @return void * @throws FireflyException */ - private function fireGroup(RuleGroup $group): void + private function processTransactionJournal(Rule $rule, array $transaction): void { - $all = false; - Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count())); - /** @var Rule $rule */ - foreach ($group->rules as $rule) { - Log::debug(sprintf('Going to fire rule #%d from group #%d', $rule->id, $group->id)); - $result = $this->fireRule($rule); - if (true === $result) { - $all = true; + Log::debug(sprintf('SearchRuleEngine:: Will now execute actions on transaction journal #%d', $transaction['transaction_journal_id'])); + $actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); + /** @var RuleAction $ruleAction */ + foreach ($actions as $ruleAction) { + if (false === $ruleAction->active) { + continue; } - if (true === $result && true === $rule->stop_processing) { - Log::debug(sprintf('The rule was triggered and rule->stop_processing = true, so group #%d will stop processing further rules.', $group->id)); - - return; + $break = $this->processRuleAction($ruleAction, $transaction); + if (true === $break) { + break; } } } /** - * Return the number of changed transactions from the previous "fire" action. + * @param array $array * - * @return int + * @return Carbon */ - public function getResults(): int + private function setDateFromJournalTrigger(array $array): Carbon { - return count($this->resultCount); - } - - /** - * @inheritDoc - */ - public function setRuleGroups(Collection $ruleGroups): void - { - Log::debug(__METHOD__); - foreach ($ruleGroups as $group) { - if ($group instanceof RuleGroup) { - Log::debug(sprintf('Adding a rule group to the SearchRuleEngine: #%d ("%s")', $group->id, $group->title)); - $this->groups->push($group); + Log::debug('Now in setDateFromJournalTrigger()'); + $journalId = 0; + foreach ($array as $triggerName => $values) { + if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) { + $journalId = (int)trim(($values[0] ?? '"0"'), '"'); // follows format "123". + Log::debug(sprintf('Found journal ID #%d', $journalId)); } } - } + if (0 !== $journalId) { + $repository = app(JournalRepositoryInterface::class); + $repository->setUser($this->user); + $journal = $repository->find($journalId); + if (null !== $journal) { + $date = $journal->date; + Log::debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d'))); - /** - * @inheritDoc - */ - public function setRules(Collection $rules): void - { - Log::debug(__METHOD__); - foreach ($rules as $rule) { - if ($rule instanceof Rule) { - Log::debug(sprintf('Adding a rule to the SearchRuleEngine: #%d ("%s")', $rule->id, $rule->title)); - $this->rules->push($rule); + return $date; } } - } + Log::debug('Found no journal, return default date.'); - /** - * @param bool $refreshTriggers - */ - public function setRefreshTriggers(bool $refreshTriggers): void - { - $this->refreshTriggers = $refreshTriggers; + return today(config('app.timezone')); } } diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 63225907a6..95471f7307 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -157,17 +157,31 @@ class AccountTransformer extends AbstractTransformer } /** - * TODO duplicated in the V2 transformer. - * @return Carbon + * @param Account $account + * @param string|null $accountRole + * @param string $accountType + * + * @return array */ - private function getDate(): Carbon + private function getCCInfo(Account $account, ?string $accountRole, string $accountType): array { - $date = today(config('app.timezone')); - if (null !== $this->parameters->get('date')) { - $date = $this->parameters->get('date'); + $monthlyPaymentDate = null; + $creditCardType = null; + if ('ccAsset' === $accountRole && 'asset' === $accountType) { + $creditCardType = $this->repository->getMetaValue($account, 'cc_type'); + $monthlyPaymentDate = $this->repository->getMetaValue($account, 'cc_monthly_payment_date'); + } + if (null !== $monthlyPaymentDate) { + // try classic date: + if (10 === strlen($monthlyPaymentDate)) { + $monthlyPaymentDate = Carbon::createFromFormat('!Y-m-d', $monthlyPaymentDate, config('app.timezone'))->toAtomString(); + } + if (10 !== strlen($monthlyPaymentDate)) { + $monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString(); + } } - return $date; + return [$creditCardType, $monthlyPaymentDate]; } /** @@ -193,32 +207,36 @@ class AccountTransformer extends AbstractTransformer return [$currencyId, $currencyCode, $currencySymbol, $decimalPlaces]; } + /** + * TODO duplicated in the V2 transformer. + * @return Carbon + */ + private function getDate(): Carbon + { + $date = today(config('app.timezone')); + if (null !== $this->parameters->get('date')) { + $date = $this->parameters->get('date'); + } + + return $date; + } + /** * @param Account $account - * @param string|null $accountRole * @param string $accountType * * @return array */ - private function getCCInfo(Account $account, ?string $accountRole, string $accountType): array + private function getInterest(Account $account, string $accountType): array { - $monthlyPaymentDate = null; - $creditCardType = null; - if ('ccAsset' === $accountRole && 'asset' === $accountType) { - $creditCardType = $this->repository->getMetaValue($account, 'cc_type'); - $monthlyPaymentDate = $this->repository->getMetaValue($account, 'cc_monthly_payment_date'); - } - if (null !== $monthlyPaymentDate) { - // try classic date: - if(10 === strlen($monthlyPaymentDate)) { - $monthlyPaymentDate = Carbon::createFromFormat('!Y-m-d', $monthlyPaymentDate, config('app.timezone'))->toAtomString(); - } - if(10 !== strlen($monthlyPaymentDate)) { - $monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString(); - } + $interest = null; + $interestPeriod = null; + if ('liabilities' === $accountType) { + $interest = $this->repository->getMetaValue($account, 'interest'); + $interestPeriod = $this->repository->getMetaValue($account, 'interest_period'); } - return [$creditCardType, $monthlyPaymentDate]; + return [$interest, $interestPeriod]; } /** @@ -244,22 +262,4 @@ class AccountTransformer extends AbstractTransformer return [$openingBalance, $openingBalanceDate]; } - - /** - * @param Account $account - * @param string $accountType - * - * @return array - */ - private function getInterest(Account $account, string $accountType): array - { - $interest = null; - $interestPeriod = null; - if ('liabilities' === $accountType) { - $interest = $this->repository->getMetaValue($account, 'interest'); - $interestPeriod = $this->repository->getMetaValue($account, 'interest_period'); - } - - return [$interest, $interestPeriod]; - } } diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index 09129156db..2564ac2f16 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -143,6 +143,53 @@ class BillTransformer extends AbstractTransformer ]; } + /** + * Returns the latest date in the set, or start when set is empty. + * + * @param Collection $dates + * @param Carbon $default + * + * @return Carbon + */ + protected function lastPaidDate(Collection $dates, Carbon $default): Carbon + { + if (0 === $dates->count()) { + return $default; + } + $latest = $dates->first()->date; + /** @var TransactionJournal $journal */ + foreach ($dates as $journal) { + if ($journal->date->gte($latest)) { + $latest = $journal->date; + } + } + + return $latest; + } + + /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction. Whether or not it is there already, is not relevant. + * + * @param Bill $bill + * @param Carbon $date + * + * @return Carbon + */ + protected function nextDateMatch(Bill $bill, Carbon $date): Carbon + { + //Log::debug(sprintf('Now in nextDateMatch(%d, %s)', $bill->id, $date->format('Y-m-d'))); + $start = clone $bill->date; + //Log::debug(sprintf('Bill start date is %s', $start->format('Y-m-d'))); + while ($start < $date) { + $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); + } + + //Log::debug(sprintf('End of loop, bill start date is now %s', $start->format('Y-m-d'))); + + return $start; + } + /** * Get the data the bill was paid and predict the next expected match. * @@ -217,30 +264,6 @@ class BillTransformer extends AbstractTransformer ]; } - /** - * Returns the latest date in the set, or start when set is empty. - * - * @param Collection $dates - * @param Carbon $default - * - * @return Carbon - */ - protected function lastPaidDate(Collection $dates, Carbon $default): Carbon - { - if (0 === $dates->count()) { - return $default; - } - $latest = $dates->first()->date; - /** @var TransactionJournal $journal */ - foreach ($dates as $journal) { - if ($journal->date->gte($latest)) { - $latest = $journal->date; - } - } - - return $latest; - } - /** * @param Bill $bill * @@ -280,27 +303,4 @@ class BillTransformer extends AbstractTransformer return $simple->toArray(); } - - /** - * Given a bill and a date, this method will tell you at which moment this bill expects its next - * transaction. Whether or not it is there already, is not relevant. - * - * @param Bill $bill - * @param Carbon $date - * - * @return Carbon - */ - protected function nextDateMatch(Bill $bill, Carbon $date): Carbon - { - //Log::debug(sprintf('Now in nextDateMatch(%d, %s)', $bill->id, $date->format('Y-m-d'))); - $start = clone $bill->date; - //Log::debug(sprintf('Bill start date is %s', $start->format('Y-m-d'))); - while ($start < $date) { - $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); - } - - //Log::debug(sprintf('End of loop, bill start date is now %s', $start->format('Y-m-d'))); - - return $start; - } } diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index b1b98ac2ed..3bb6d87a87 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -149,6 +149,75 @@ class RecurrenceTransformer extends AbstractTransformer return $return; } + /** + * @param RecurrenceTransaction $transaction + * @param array $array + * + * @return array + * @throws FireflyException + */ + private function getTransactionMeta(RecurrenceTransaction $transaction, array $array): array + { + Log::debug(sprintf('Now in %s', __METHOD__)); + $array['tags'] = []; + $array['category_id'] = null; + $array['category_name'] = null; + $array['budget_id'] = null; + $array['budget_name'] = null; + $array['piggy_bank_id'] = null; + $array['piggy_bank_name'] = null; + $array['bill_id'] = null; + $array['bill_name'] = null; + + /** @var RecurrenceTransactionMeta $transactionMeta */ + foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) { + switch ($transactionMeta->name) { + default: + throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $transactionMeta->name)); + case 'bill_id': + $bill = $this->billRepos->find((int)$transactionMeta->value); + if (null !== $bill) { + $array['bill_id'] = (string)$bill->id; + $array['bill_name'] = $bill->name; + } + break; + case 'tags': + $array['tags'] = json_decode($transactionMeta->value); + break; + case 'piggy_bank_id': + $piggy = $this->piggyRepos->find((int)$transactionMeta->value); + if (null !== $piggy) { + $array['piggy_bank_id'] = (string)$piggy->id; + $array['piggy_bank_name'] = $piggy->name; + } + break; + case 'category_id': + $category = $this->factory->findOrCreate((int)$transactionMeta->value, null); + if (null !== $category) { + $array['category_id'] = (string)$category->id; + $array['category_name'] = $category->name; + } + break; + case 'category_name': + $category = $this->factory->findOrCreate(null, $transactionMeta->value); + if (null !== $category) { + $array['category_id'] = (string)$category->id; + $array['category_name'] = $category->name; + } + break; + case 'budget_id': + $budget = $this->budgetRepos->find((int)$transactionMeta->value); + if (null !== $budget) { + $array['budget_id'] = (string)$budget->id; + $array['budget_name'] = $budget->name; + } + break; + } + } + + return $array; + } + /** * @param Recurrence $recurrence * @@ -235,73 +304,4 @@ class RecurrenceTransformer extends AbstractTransformer return $return; } - - /** - * @param RecurrenceTransaction $transaction - * @param array $array - * - * @return array - * @throws FireflyException - */ - private function getTransactionMeta(RecurrenceTransaction $transaction, array $array): array - { - Log::debug(sprintf('Now in %s', __METHOD__)); - $array['tags'] = []; - $array['category_id'] = null; - $array['category_name'] = null; - $array['budget_id'] = null; - $array['budget_name'] = null; - $array['piggy_bank_id'] = null; - $array['piggy_bank_name'] = null; - $array['bill_id'] = null; - $array['bill_name'] = null; - - /** @var RecurrenceTransactionMeta $transactionMeta */ - foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) { - switch ($transactionMeta->name) { - default: - throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $transactionMeta->name)); - case 'bill_id': - $bill = $this->billRepos->find((int)$transactionMeta->value); - if (null !== $bill) { - $array['bill_id'] = (string)$bill->id; - $array['bill_name'] = $bill->name; - } - break; - case 'tags': - $array['tags'] = json_decode($transactionMeta->value); - break; - case 'piggy_bank_id': - $piggy = $this->piggyRepos->find((int)$transactionMeta->value); - if (null !== $piggy) { - $array['piggy_bank_id'] = (string)$piggy->id; - $array['piggy_bank_name'] = $piggy->name; - } - break; - case 'category_id': - $category = $this->factory->findOrCreate((int)$transactionMeta->value, null); - if (null !== $category) { - $array['category_id'] = (string)$category->id; - $array['category_name'] = $category->name; - } - break; - case 'category_name': - $category = $this->factory->findOrCreate(null, $transactionMeta->value); - if (null !== $category) { - $array['category_id'] = (string)$category->id; - $array['category_name'] = $category->name; - } - break; - case 'budget_id': - $budget = $this->budgetRepos->find((int)$transactionMeta->value); - if (null !== $budget) { - $array['budget_id'] = (string)$budget->id; - $array['budget_name'] = $budget->name; - } - break; - } - } - - return $array; - } } diff --git a/app/Transformers/RuleTransformer.php b/app/Transformers/RuleTransformer.php index d95c1ba4dc..58f2148ddf 100644 --- a/app/Transformers/RuleTransformer.php +++ b/app/Transformers/RuleTransformer.php @@ -83,6 +83,32 @@ class RuleTransformer extends AbstractTransformer ]; } + /** + * @param Rule $rule + * + * @return array + */ + private function actions(Rule $rule): array + { + $result = []; + $actions = $this->ruleRepository->getRuleActions($rule); + /** @var RuleAction $ruleAction */ + foreach ($actions as $ruleAction) { + $result[] = [ + 'id' => (string)$ruleAction->id, + 'created_at' => $ruleAction->created_at->toAtomString(), + 'updated_at' => $ruleAction->updated_at->toAtomString(), + 'type' => $ruleAction->action_type, + 'value' => $ruleAction->action_value, + 'order' => $ruleAction->order, + 'active' => $ruleAction->active, + 'stop_processing' => $ruleAction->stop_processing, + ]; + } + + return $result; + } + /** * @param Rule $rule * @@ -134,30 +160,4 @@ class RuleTransformer extends AbstractTransformer return $result; } - - /** - * @param Rule $rule - * - * @return array - */ - private function actions(Rule $rule): array - { - $result = []; - $actions = $this->ruleRepository->getRuleActions($rule); - /** @var RuleAction $ruleAction */ - foreach ($actions as $ruleAction) { - $result[] = [ - 'id' => (string)$ruleAction->id, - 'created_at' => $ruleAction->created_at->toAtomString(), - 'updated_at' => $ruleAction->updated_at->toAtomString(), - 'type' => $ruleAction->action_type, - 'value' => $ruleAction->action_value, - 'order' => $ruleAction->order, - 'active' => $ruleAction->active, - 'stop_processing' => $ruleAction->stop_processing, - ]; - } - - return $result; - } } diff --git a/app/Transformers/TransactionGroupTransformer.php b/app/Transformers/TransactionGroupTransformer.php index 49b5b535ab..8ebdc65cd6 100644 --- a/app/Transformers/TransactionGroupTransformer.php +++ b/app/Transformers/TransactionGroupTransformer.php @@ -104,16 +104,427 @@ class TransactionGroupTransformer extends AbstractTransformer } /** - * @param NullArrayObject $data + * @param TransactionGroup $group + * + * @return array + * @throws FireflyException + */ + public function transformObject(TransactionGroup $group): array + { + try { + $result = [ + 'id' => (int)$group->id, + 'created_at' => $group->created_at->toAtomString(), + 'updated_at' => $group->updated_at->toAtomString(), + 'user' => (int)$group->user_id, + 'group_title' => $group->title, + 'transactions' => $this->transformJournals($group->transactionJournals), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/transactions/'.$group->id, + ], + ], + ]; + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException(sprintf('Transaction group #%d is broken. Please check out your log files.', $group->id), 0, $e); + } + + // do something else. + + return $result; + } + + /** + * @param NullArrayObject $object + * @param string $key + * + * @return string|null + */ + private function dateFromArray(NullArrayObject $object, string $key): ?string + { + if (null === $object[$key]) { + return null; + } + + return $object[$key]->toAtomString(); + } + + /** + * @param string $type + * @param string $amount + * + * @return string + */ + private function getAmount(string $type, string $amount): string + { + return app('steam')->positive($amount); + } + + /** + * @param Bill|null $bill * * @return array */ - private function transformTransactions(NullArrayObject $data): array + private function getBill(?Bill $bill): array { - $result = []; - $transactions = $data['transactions'] ?? []; - foreach ($transactions as $transaction) { - $result[] = $this->transformTransaction($transaction); + $array = [ + 'id' => null, + 'name' => null, + ]; + if (null === $bill) { + return $array; + } + $array['id'] = (string)$bill->id; + $array['name'] = $bill->name; + + return $array; + } + + /** + * @param Budget|null $budget + * + * @return array + */ + private function getBudget(?Budget $budget): array + { + $array = [ + 'id' => null, + 'name' => null, + ]; + if (null === $budget) { + return $array; + } + $array['id'] = (int)$budget->id; + $array['name'] = $budget->name; + + return $array; + } + + /** + * @param Category|null $category + * + * @return array + */ + private function getCategory(?Category $category): array + { + $array = [ + 'id' => null, + 'name' => null, + ]; + if (null === $category) { + return $array; + } + $array['id'] = (int)$category->id; + $array['name'] = $category->name; + + return $array; + } + + /** + * @param NullArrayObject $dates + * + * @return array + */ + private function getDates(NullArrayObject $dates): array + { + $fields = [ + 'interest_date', + 'book_date', + 'process_date', + 'due_date', + 'payment_date', + 'invoice_date', + ]; + $return = []; + foreach ($fields as $field) { + $return[$field] = null; + if (null !== $dates[$field]) { + $return[$field] = $dates[$field]->toAtomString(); + } + } + + return $return; + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + * @throws FireflyException + */ + private function getDestinationTransaction(TransactionJournal $journal): Transaction + { + $result = $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount > 0; // lame but it works + } + ); + if (null === $result) { + throw new FireflyException(sprintf('Journal #%d unexpectedly has no destination transaction.', $journal->id)); + } + + return $result; + } + + /** + * @param string $type + * @param string|null $foreignAmount + * + * @return string|null + */ + private function getForeignAmount(string $type, ?string $foreignAmount): ?string + { + $result = null; + if (null !== $foreignAmount && '' !== $foreignAmount && bccomp('0', $foreignAmount) !== 0) { + $result = app('steam')->positive($foreignAmount); + } + + return $result; + } + + /** + * @param TransactionCurrency|null $currency + * + * @return array + */ + private function getForeignCurrency(?TransactionCurrency $currency): array + { + $array = [ + 'id' => null, + 'code' => null, + 'symbol' => null, + 'decimal_places' => null, + ]; + if (null === $currency) { + return $array; + } + $array['id'] = (int)$currency->id; + $array['code'] = $currency->code; + $array['symbol'] = $currency->symbol; + $array['decimal_places'] = (int)$currency->decimal_places; + + return $array; + } + + /** + * @param TransactionJournal $journal + * + * @return Location|null + */ + private function getLocation(TransactionJournal $journal): ?Location + { + return $journal->locations()->first(); + } + + /** + * @param int $journalId + * + * @return Location|null + */ + private function getLocationById(int $journalId): ?Location + { + return $this->groupRepos->getLocation($journalId); + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + * @throws FireflyException + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + $result = $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount < 0; // lame but it works. + } + ); + if (null === $result) { + throw new FireflyException(sprintf('Journal #%d unexpectedly has no source transaction.', $journal->id)); + } + + return $result; + } + + /** + * @param int $journalId + * + * @return bool + */ + private function hasAttachments(int $journalId): bool + { + return $this->groupRepos->countAttachments($journalId) > 0; + } + + /** + * @param array $array + * @param string $key + * + * @return int|null + */ + private function integerFromArray(array $array, string $key): ?int + { + if (array_key_exists($key, $array)) { + return (int)$array[$key]; + } + + return null; + } + + /** + * @param array $array + * @param string $key + * @param string|null $default + * + * @return string|null + */ + private function stringFromArray(array $array, string $key, ?string $default): ?string + { + if (array_key_exists($key, $array) && null === $array[$key]) { + return null; + } + if (array_key_exists($key, $array) && null !== $array[$key]) { + if (0 === $array[$key]) { + return $default; + } + if ('0' === $array[$key]) { + return $default; + } + return (string)$array[$key]; + } + + if (null !== $default) { + return (string)$default; + } + + return null; + } + + /** + * @param TransactionJournal $journal + * + * @return array + * @throws FireflyException + */ + private function transformJournal(TransactionJournal $journal): array + { + $source = $this->getSourceTransaction($journal); + $destination = $this->getDestinationTransaction($journal); + $type = $journal->transactionType->type; + $currency = $source->transactionCurrency; + $amount = app('steam')->bcround($this->getAmount($type, (string)$source->amount), $currency->decimal_places ?? 0); + $foreignAmount = $this->getForeignAmount($type, null === $source->foreign_amount ? null : (string)$source->foreign_amount); + $metaFieldData = $this->groupRepos->getMetaFields($journal->id, $this->metaFields); + $metaDates = $this->getDates($this->groupRepos->getMetaDateFields($journal->id, $this->metaDateFields)); + $foreignCurrency = $this->getForeignCurrency($source->foreignCurrency); + $budget = $this->getBudget($journal->budgets->first()); + $category = $this->getCategory($journal->categories->first()); + $bill = $this->getBill($journal->bill); + + if (null !== $foreignAmount && null !== $source->foreignCurrency) { + $foreignAmount = app('steam')->bcround($foreignAmount, $foreignCurrency['decimal_places'] ?? 0); + } + + $longitude = null; + $latitude = null; + $zoomLevel = null; + $location = $this->getLocation($journal); + if (null !== $location) { + $longitude = $location->longitude; + $latitude = $location->latitude; + $zoomLevel = $location->zoom_level; + } + + return [ + 'user' => (int)$journal->user_id, + 'transaction_journal_id' => (int)$journal->id, + 'type' => strtolower($type), + 'date' => $journal->date->toAtomString(), + 'order' => $journal->order, + + 'currency_id' => (int)$currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => (int)$currency->decimal_places, + + 'foreign_currency_id' => $foreignCurrency['id'], + 'foreign_currency_code' => $foreignCurrency['code'], + 'foreign_currency_symbol' => $foreignCurrency['symbol'], + 'foreign_currency_decimal_places' => $foreignCurrency['decimal_places'], + + 'amount' => app('steam')->bcround($amount, $currency->decimal_places), + 'foreign_amount' => $foreignAmount, + + 'description' => $journal->description, + + 'source_id' => (int)$source->account_id, + 'source_name' => $source->account->name, + 'source_iban' => $source->account->iban, + 'source_type' => $source->account->accountType->type, + + 'destination_id' => (int)$destination->account_id, + 'destination_name' => $destination->account->name, + 'destination_iban' => $destination->account->iban, + 'destination_type' => $destination->account->accountType->type, + + 'budget_id' => $budget['id'], + 'budget_name' => $budget['name'], + + 'category_id' => $category['id'], + 'category_name' => $category['name'], + + 'bill_id' => $bill['id'], + 'bill_name' => $bill['name'], + + 'reconciled' => $source->reconciled, + 'notes' => $this->groupRepos->getNoteText($journal->id), + 'tags' => $this->groupRepos->getTags($journal->id), + + 'internal_reference' => $metaFieldData['internal_reference'], + 'external_id' => $metaFieldData['external_id'], + 'original_source' => $metaFieldData['original_source'], + 'recurrence_id' => $metaFieldData['recurrence_id'], + 'bunq_payment_id' => $metaFieldData['bunq_payment_id'], + 'import_hash_v2' => $metaFieldData['import_hash_v2'], + + 'sepa_cc' => $metaFieldData['sepa_cc'], + 'sepa_ct_op' => $metaFieldData['sepa_ct_op'], + 'sepa_ct_id' => $metaFieldData['sepa_ct_id'], + 'sepa_db' => $metaFieldData['sepa_db'], + 'sepa_country' => $metaFieldData['sepa_country'], + 'sepa_ep' => $metaFieldData['sepa_ep'], + 'sepa_ci' => $metaFieldData['sepa_ci'], + 'sepa_batch_id' => $metaFieldData['sepa_batch_id'], + + 'interest_date' => $metaDates['interest_date'], + 'book_date' => $metaDates['book_date'], + 'process_date' => $metaDates['process_date'], + 'due_date' => $metaDates['due_date'], + 'payment_date' => $metaDates['payment_date'], + 'invoice_date' => $metaDates['invoice_date'], + + // location data + 'longitude' => $longitude, + 'latitude' => $latitude, + 'zoom_level' => $zoomLevel, + ]; + } + + /** + * @param Collection $transactionJournals + * + * @return array + * @throws FireflyException + */ + private function transformJournals(Collection $transactionJournals): array + { + $result = []; + /** @var TransactionJournal $journal */ + foreach ($transactionJournals as $journal) { + $result[] = $this->transformJournal($journal); } return $result; @@ -232,429 +643,18 @@ class TransactionGroupTransformer extends AbstractTransformer } /** - * @param array $array - * @param string $key - * @param string|null $default - * - * @return string|null - */ - private function stringFromArray(array $array, string $key, ?string $default): ?string - { - if (array_key_exists($key, $array) && null === $array[$key]) { - return null; - } - if (array_key_exists($key, $array) && null !== $array[$key]) { - if (0 === $array[$key]) { - return $default; - } - if ('0' === $array[$key]) { - return $default; - } - return (string)$array[$key]; - } - - if (null !== $default) { - return (string)$default; - } - - return null; - } - - /** - * @param int $journalId - * - * @return Location|null - */ - private function getLocationById(int $journalId): ?Location - { - return $this->groupRepos->getLocation($journalId); - } - - /** - * @param TransactionJournal $journal - * - * @return Location|null - */ - private function getLocation(TransactionJournal $journal): ?Location - { - return $journal->locations()->first(); - } - - /** - * @param array $array - * @param string $key - * - * @return int|null - */ - private function integerFromArray(array $array, string $key): ?int - { - if (array_key_exists($key, $array)) { - return (int)$array[$key]; - } - - return null; - } - - /** - * @param NullArrayObject $object - * @param string $key - * - * @return string|null - */ - private function dateFromArray(NullArrayObject $object, string $key): ?string - { - if (null === $object[$key]) { - return null; - } - - return $object[$key]->toAtomString(); - } - - /** - * @param int $journalId - * - * @return bool - */ - private function hasAttachments(int $journalId): bool - { - return $this->groupRepos->countAttachments($journalId) > 0; - } - - /** - * @param TransactionGroup $group + * @param NullArrayObject $data * * @return array - * @throws FireflyException */ - public function transformObject(TransactionGroup $group): array + private function transformTransactions(NullArrayObject $data): array { - try { - $result = [ - 'id' => (int)$group->id, - 'created_at' => $group->created_at->toAtomString(), - 'updated_at' => $group->updated_at->toAtomString(), - 'user' => (int)$group->user_id, - 'group_title' => $group->title, - 'transactions' => $this->transformJournals($group->transactionJournals), - 'links' => [ - [ - 'rel' => 'self', - 'uri' => '/transactions/'.$group->id, - ], - ], - ]; - } catch (FireflyException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - throw new FireflyException(sprintf('Transaction group #%d is broken. Please check out your log files.', $group->id), 0, $e); - } - - // do something else. - - return $result; - } - - /** - * @param Collection $transactionJournals - * - * @return array - * @throws FireflyException - */ - private function transformJournals(Collection $transactionJournals): array - { - $result = []; - /** @var TransactionJournal $journal */ - foreach ($transactionJournals as $journal) { - $result[] = $this->transformJournal($journal); + $result = []; + $transactions = $data['transactions'] ?? []; + foreach ($transactions as $transaction) { + $result[] = $this->transformTransaction($transaction); } return $result; } - - /** - * @param TransactionJournal $journal - * - * @return array - * @throws FireflyException - */ - private function transformJournal(TransactionJournal $journal): array - { - $source = $this->getSourceTransaction($journal); - $destination = $this->getDestinationTransaction($journal); - $type = $journal->transactionType->type; - $currency = $source->transactionCurrency; - $amount = app('steam')->bcround($this->getAmount($type, (string)$source->amount), $currency->decimal_places ?? 0); - $foreignAmount = $this->getForeignAmount($type, null === $source->foreign_amount ? null : (string)$source->foreign_amount); - $metaFieldData = $this->groupRepos->getMetaFields($journal->id, $this->metaFields); - $metaDates = $this->getDates($this->groupRepos->getMetaDateFields($journal->id, $this->metaDateFields)); - $foreignCurrency = $this->getForeignCurrency($source->foreignCurrency); - $budget = $this->getBudget($journal->budgets->first()); - $category = $this->getCategory($journal->categories->first()); - $bill = $this->getBill($journal->bill); - - if (null !== $foreignAmount && null !== $source->foreignCurrency) { - $foreignAmount = app('steam')->bcround($foreignAmount, $foreignCurrency['decimal_places'] ?? 0); - } - - $longitude = null; - $latitude = null; - $zoomLevel = null; - $location = $this->getLocation($journal); - if (null !== $location) { - $longitude = $location->longitude; - $latitude = $location->latitude; - $zoomLevel = $location->zoom_level; - } - - return [ - 'user' => (int)$journal->user_id, - 'transaction_journal_id' => (int)$journal->id, - 'type' => strtolower($type), - 'date' => $journal->date->toAtomString(), - 'order' => $journal->order, - - 'currency_id' => (int)$currency->id, - 'currency_code' => $currency->code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => (int)$currency->decimal_places, - - 'foreign_currency_id' => $foreignCurrency['id'], - 'foreign_currency_code' => $foreignCurrency['code'], - 'foreign_currency_symbol' => $foreignCurrency['symbol'], - 'foreign_currency_decimal_places' => $foreignCurrency['decimal_places'], - - 'amount' => app('steam')->bcround($amount, $currency->decimal_places), - 'foreign_amount' => $foreignAmount, - - 'description' => $journal->description, - - 'source_id' => (int)$source->account_id, - 'source_name' => $source->account->name, - 'source_iban' => $source->account->iban, - 'source_type' => $source->account->accountType->type, - - 'destination_id' => (int)$destination->account_id, - 'destination_name' => $destination->account->name, - 'destination_iban' => $destination->account->iban, - 'destination_type' => $destination->account->accountType->type, - - 'budget_id' => $budget['id'], - 'budget_name' => $budget['name'], - - 'category_id' => $category['id'], - 'category_name' => $category['name'], - - 'bill_id' => $bill['id'], - 'bill_name' => $bill['name'], - - 'reconciled' => $source->reconciled, - 'notes' => $this->groupRepos->getNoteText($journal->id), - 'tags' => $this->groupRepos->getTags($journal->id), - - 'internal_reference' => $metaFieldData['internal_reference'], - 'external_id' => $metaFieldData['external_id'], - 'original_source' => $metaFieldData['original_source'], - 'recurrence_id' => $metaFieldData['recurrence_id'], - 'bunq_payment_id' => $metaFieldData['bunq_payment_id'], - 'import_hash_v2' => $metaFieldData['import_hash_v2'], - - 'sepa_cc' => $metaFieldData['sepa_cc'], - 'sepa_ct_op' => $metaFieldData['sepa_ct_op'], - 'sepa_ct_id' => $metaFieldData['sepa_ct_id'], - 'sepa_db' => $metaFieldData['sepa_db'], - 'sepa_country' => $metaFieldData['sepa_country'], - 'sepa_ep' => $metaFieldData['sepa_ep'], - 'sepa_ci' => $metaFieldData['sepa_ci'], - 'sepa_batch_id' => $metaFieldData['sepa_batch_id'], - - 'interest_date' => $metaDates['interest_date'], - 'book_date' => $metaDates['book_date'], - 'process_date' => $metaDates['process_date'], - 'due_date' => $metaDates['due_date'], - 'payment_date' => $metaDates['payment_date'], - 'invoice_date' => $metaDates['invoice_date'], - - // location data - 'longitude' => $longitude, - 'latitude' => $latitude, - 'zoom_level' => $zoomLevel, - ]; - } - - /** - * @param TransactionJournal $journal - * - * @return Transaction - * @throws FireflyException - */ - private function getSourceTransaction(TransactionJournal $journal): Transaction - { - $result = $journal->transactions->first( - static function (Transaction $transaction) { - return (float)$transaction->amount < 0; // lame but it works. - } - ); - if (null === $result) { - throw new FireflyException(sprintf('Journal #%d unexpectedly has no source transaction.', $journal->id)); - } - - return $result; - } - - /** - * @param TransactionJournal $journal - * - * @return Transaction - * @throws FireflyException - */ - private function getDestinationTransaction(TransactionJournal $journal): Transaction - { - $result = $journal->transactions->first( - static function (Transaction $transaction) { - return (float)$transaction->amount > 0; // lame but it works - } - ); - if (null === $result) { - throw new FireflyException(sprintf('Journal #%d unexpectedly has no destination transaction.', $journal->id)); - } - - return $result; - } - - /** - * @param string $type - * @param string $amount - * - * @return string - */ - private function getAmount(string $type, string $amount): string - { - return app('steam')->positive($amount); - } - - /** - * @param string $type - * @param string|null $foreignAmount - * - * @return string|null - */ - private function getForeignAmount(string $type, ?string $foreignAmount): ?string - { - $result = null; - if (null !== $foreignAmount && '' !== $foreignAmount && bccomp('0', $foreignAmount) !== 0) { - $result = app('steam')->positive($foreignAmount); - } - - return $result; - } - - /** - * @param NullArrayObject $dates - * - * @return array - */ - private function getDates(NullArrayObject $dates): array - { - $fields = [ - 'interest_date', - 'book_date', - 'process_date', - 'due_date', - 'payment_date', - 'invoice_date', - ]; - $return = []; - foreach ($fields as $field) { - $return[$field] = null; - if (null !== $dates[$field]) { - $return[$field] = $dates[$field]->toAtomString(); - } - } - - return $return; - } - - /** - * @param TransactionCurrency|null $currency - * - * @return array - */ - private function getForeignCurrency(?TransactionCurrency $currency): array - { - $array = [ - 'id' => null, - 'code' => null, - 'symbol' => null, - 'decimal_places' => null, - ]; - if (null === $currency) { - return $array; - } - $array['id'] = (int)$currency->id; - $array['code'] = $currency->code; - $array['symbol'] = $currency->symbol; - $array['decimal_places'] = (int)$currency->decimal_places; - - return $array; - } - - /** - * @param Budget|null $budget - * - * @return array - */ - private function getBudget(?Budget $budget): array - { - $array = [ - 'id' => null, - 'name' => null, - ]; - if (null === $budget) { - return $array; - } - $array['id'] = (int)$budget->id; - $array['name'] = $budget->name; - - return $array; - } - - /** - * @param Category|null $category - * - * @return array - */ - private function getCategory(?Category $category): array - { - $array = [ - 'id' => null, - 'name' => null, - ]; - if (null === $category) { - return $array; - } - $array['id'] = (int)$category->id; - $array['name'] = $category->name; - - return $array; - } - - /** - * @param Bill|null $bill - * - * @return array - */ - private function getBill(?Bill $bill): array - { - $array = [ - 'id' => null, - 'name' => null, - ]; - if (null === $bill) { - return $array; - } - $array['id'] = (string)$bill->id; - $array['name'] = $bill->name; - - return $array; - } } diff --git a/app/Transformers/V2/AccountTransformer.php b/app/Transformers/V2/AccountTransformer.php index ed0169fed8..4c534a5dd0 100644 --- a/app/Transformers/V2/AccountTransformer.php +++ b/app/Transformers/V2/AccountTransformer.php @@ -73,19 +73,6 @@ class AccountTransformer extends AbstractTransformer } } - /** - * @return Carbon - */ - private function getDate(): Carbon - { - $date = today(config('app.timezone')); - if (null !== $this->parameters->get('date')) { - $date = $this->parameters->get('date'); - } - - return $date; - } - /** * Transform the account. * @@ -146,4 +133,17 @@ class AccountTransformer extends AbstractTransformer ], ]; } + + /** + * @return Carbon + */ + private function getDate(): Carbon + { + $date = today(config('app.timezone')); + if (null !== $this->parameters->get('date')) { + $date = $this->parameters->get('date'); + } + + return $date; + } } diff --git a/app/Transformers/V2/TransactionGroupTransformer.php b/app/Transformers/V2/TransactionGroupTransformer.php index 962e7d5a71..48477b608c 100644 --- a/app/Transformers/V2/TransactionGroupTransformer.php +++ b/app/Transformers/V2/TransactionGroupTransformer.php @@ -104,17 +104,52 @@ class TransactionGroupTransformer extends AbstractTransformer } /** - * @param array $transactions - * @return array + * @param string|null $string + * @return Carbon|null */ - private function transformTransactions(array $transactions): array + private function date(?string $string): ?Carbon { - $return = []; - /** @var array $transaction */ - foreach ($transactions as $transaction) { - $return[] = $this->transformTransaction($transaction); + if (null === $string) { + return null; } - return $return; + Log::debug(sprintf('Now in date("%s")', $string)); + if (10 === strlen($string)) { + return Carbon::createFromFormat('Y-m-d', $string, config('app.timezone')); + } + // 2022-01-01 01:01:01 + return Carbon::createFromFormat('Y-m-d H:i:s', substr($string, 0, 19), config('app.timezone')); + } + + /** + * TODO also in the old transformer. + * + * @param NullArrayObject $array + * @param string $key + * @param string|null $default + * + * @return string|null + */ + private function stringFromArray(NullArrayObject $array, string $key, ?string $default): ?string + { + //Log::debug(sprintf('%s: %s', $key, var_export($array[$key], true))); + if (null === $array[$key] && null === $default) { + return null; + } + if (0 === $array[$key]) { + return $default; + } + if ('0' === $array[$key]) { + return $default; + } + if (null !== $array[$key]) { + return (string)$array[$key]; + } + + if (null !== $default) { + return $default; + } + + return null; } private function transformTransaction(array $transaction): array @@ -229,51 +264,16 @@ class TransactionGroupTransformer extends AbstractTransformer } /** - * TODO also in the old transformer. - * - * @param NullArrayObject $array - * @param string $key - * @param string|null $default - * - * @return string|null + * @param array $transactions + * @return array */ - private function stringFromArray(NullArrayObject $array, string $key, ?string $default): ?string + private function transformTransactions(array $transactions): array { - //Log::debug(sprintf('%s: %s', $key, var_export($array[$key], true))); - if (null === $array[$key] && null === $default) { - return null; + $return = []; + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = $this->transformTransaction($transaction); } - if (0 === $array[$key]) { - return $default; - } - if ('0' === $array[$key]) { - return $default; - } - if (null !== $array[$key]) { - return (string)$array[$key]; - } - - if (null !== $default) { - return $default; - } - - return null; - } - - /** - * @param string|null $string - * @return Carbon|null - */ - private function date(?string $string): ?Carbon - { - if (null === $string) { - return null; - } - Log::debug(sprintf('Now in date("%s")', $string)); - if (10 === strlen($string)) { - return Carbon::createFromFormat('Y-m-d', $string, config('app.timezone')); - } - // 2022-01-01 01:01:01 - return Carbon::createFromFormat('Y-m-d H:i:s', substr($string, 0, 19), config('app.timezone')); + return $return; } } diff --git a/app/Transformers/WebhookMessageTransformer.php b/app/Transformers/WebhookMessageTransformer.php index f62761964a..398c6cda68 100644 --- a/app/Transformers/WebhookMessageTransformer.php +++ b/app/Transformers/WebhookMessageTransformer.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Transformers; use FireflyIII\Models\WebhookMessage; -use Jsonexception; use Illuminate\Support\Facades\Log; +use Jsonexception; /** * Class WebhookMessageTransformer diff --git a/app/Validation/Account/DepositValidation.php b/app/Validation/Account/DepositValidation.php index 2c009e5ab6..508708503b 100644 --- a/app/Validation/Account/DepositValidation.php +++ b/app/Validation/Account/DepositValidation.php @@ -32,6 +32,21 @@ use Illuminate\Support\Facades\Log; */ trait DepositValidation { + /** + * @param array $accountTypes + * + * @return bool + */ + abstract protected function canCreateTypes(array $accountTypes): bool; + + /** + * @param array $validTypes + * @param array $data + * + * @return Account|null + */ + abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; + /** * @param array $array * @@ -71,7 +86,7 @@ trait DepositValidation if (null !== $search) { Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name)); $this->setDestination($search); - $result = true; + $result = true; } } Log::debug(sprintf('validateDepositDestination will return %s', var_export($result, true))); @@ -79,21 +94,6 @@ trait DepositValidation return $result; } - /** - * @param array $accountTypes - * - * @return bool - */ - abstract protected function canCreateTypes(array $accountTypes): bool; - - /** - * @param array $validTypes - * @param array $data - * - * @return Account|null - */ - abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; - /** * @param array $array * @@ -132,7 +132,7 @@ trait DepositValidation if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { Log::debug('ID result is not null and seems valid, save as source account.'); $this->setSource($search); - $result = true; + $result = true; } } @@ -146,7 +146,7 @@ trait DepositValidation if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { Log::debug('IBAN result is not null and seems valid, save as source account.'); $this->setSource($search); - $result = true; + $result = true; } } @@ -162,7 +162,7 @@ trait DepositValidation if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { Log::debug('Number result is not null and seems valid, save as source account.'); $this->setSource($search); - $result = true; + $result = true; } } diff --git a/app/Validation/Account/OBValidation.php b/app/Validation/Account/OBValidation.php index 655259a321..976443f081 100644 --- a/app/Validation/Account/OBValidation.php +++ b/app/Validation/Account/OBValidation.php @@ -33,6 +33,13 @@ use Illuminate\Support\Facades\Log; */ trait OBValidation { + /** + * @param array $accountTypes + * + * @return bool + */ + abstract protected function canCreateTypes(array $accountTypes): bool; + /** * @param array $array * @@ -71,7 +78,7 @@ trait OBValidation if (null !== $search) { Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name)); $this->setDestination($search); - $result = true; + $result = true; } } Log::debug(sprintf('validateOBDestination(%d, "%s") will return %s', $accountId, $accountName, var_export($result, true))); @@ -79,13 +86,6 @@ trait OBValidation return $result; } - /** - * @param array $accountTypes - * - * @return bool - */ - abstract protected function canCreateTypes(array $accountTypes): bool; - /** * Source of an opening balance can either be an asset account * or an "initial balance account". The latter can be created. @@ -128,7 +128,7 @@ trait OBValidation if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { Log::debug(sprintf('Found account of correct type: #%d, "%s"', $search->id, $search->name)); $this->setSource($search); - $result = true; + $result = true; } } diff --git a/app/Validation/Account/TransferValidation.php b/app/Validation/Account/TransferValidation.php index 7cee3f8731..85a46715d9 100644 --- a/app/Validation/Account/TransferValidation.php +++ b/app/Validation/Account/TransferValidation.php @@ -31,6 +31,21 @@ use Illuminate\Support\Facades\Log; */ trait TransferValidation { + /** + * @param array $accountTypes + * + * @return bool + */ + abstract protected function canCreateTypes(array $accountTypes): bool; + + /** + * @param array $validTypes + * @param array $data + * + * @return Account|null + */ + abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; + /** * @param array $array * @@ -72,21 +87,6 @@ trait TransferValidation return true; } - /** - * @param array $accountTypes - * - * @return bool - */ - abstract protected function canCreateTypes(array $accountTypes): bool; - - /** - * @param array $validTypes - * @param array $data - * - * @return Account|null - */ - abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; - /** * @param array $array * diff --git a/app/Validation/Account/WithdrawalValidation.php b/app/Validation/Account/WithdrawalValidation.php index f8ce6eb55e..65566830fe 100644 --- a/app/Validation/Account/WithdrawalValidation.php +++ b/app/Validation/Account/WithdrawalValidation.php @@ -32,6 +32,21 @@ use Illuminate\Support\Facades\Log; */ trait WithdrawalValidation { + /** + * @param array $accountTypes + * + * @return bool + */ + abstract protected function canCreateTypes(array $accountTypes): bool; + + /** + * @param array $validTypes + * @param array $data + * + * @return Account|null + */ + abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; + /** * @param array $array * @@ -67,21 +82,6 @@ trait WithdrawalValidation return true; } - /** - * @param array $accountTypes - * - * @return bool - */ - abstract protected function canCreateTypes(array $accountTypes): bool; - - /** - * @param array $validTypes - * @param array $data - * - * @return Account|null - */ - abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; - /** * @param array $array * @@ -128,10 +128,10 @@ trait WithdrawalValidation */ protected function validateWithdrawalSource(array $array): bool { - $accountId = array_key_exists('id', $array) ? $array['id'] : null; - $accountName = array_key_exists('name', $array) ? $array['name'] : null; - $accountIban = array_key_exists('iban', $array) ? $array['iban'] : null; - $accountNumber =array_key_exists('number', $array) ? $array['number'] : null; + $accountId = array_key_exists('id', $array) ? $array['id'] : null; + $accountName = array_key_exists('name', $array) ? $array['name'] : null; + $accountIban = array_key_exists('iban', $array) ? $array['iban'] : null; + $accountNumber = array_key_exists('number', $array) ? $array['number'] : null; Log::debug('Now in validateWithdrawalSource', $array); // source can be any of the following types. diff --git a/app/Validation/AccountValidator.php b/app/Validation/AccountValidator.php index de4133b41c..15eeacdee8 100644 --- a/app/Validation/AccountValidator.php +++ b/app/Validation/AccountValidator.php @@ -82,6 +82,34 @@ class AccountValidator return $this->source; } + /** + * @param Account|null $account + */ + public function setSource(?Account $account): void + { + if (null === $account) { + Log::debug('AccountValidator source is set to NULL'); + } + if (null !== $account) { + Log::debug(sprintf('AccountValidator source is set to #%d: "%s" (%s)', $account->id, $account->name, $account?->accountType->type)); + } + $this->source = $account; + } + + /** + * @param Account|null $account + */ + public function setDestination(?Account $account): void + { + if (null === $account) { + Log::debug('AccountValidator destination is set to NULL'); + } + if (null !== $account) { + Log::debug(sprintf('AccountValidator destination is set to #%d: "%s" (%s)', $account->id, $account->name, $account?->accountType->type)); + } + $this->destination = $account; + } + /** * @param string $transactionType */ @@ -183,6 +211,21 @@ class AccountValidator return $result; } + /** + * @param string $accountType + * + * @return bool + */ + protected function canCreateType(string $accountType): bool + { + $canCreate = [AccountType::EXPENSE, AccountType::REVENUE, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT]; + if (in_array($accountType, $canCreate, true)) { + return true; + } + + return false; + } + /** * @param array $accountTypes * @@ -204,21 +247,6 @@ class AccountValidator return false; } - /** - * @param string $accountType - * - * @return bool - */ - protected function canCreateType(string $accountType): bool - { - $canCreate = [AccountType::EXPENSE, AccountType::REVENUE, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT]; - if (in_array($accountType, $canCreate, true)) { - return true; - } - - return false; - } - /** * @param array $validTypes * @param array $data @@ -264,32 +292,4 @@ class AccountValidator return null; } - - /** - * @param Account|null $account - */ - public function setDestination(?Account $account): void - { - if (null === $account) { - Log::debug('AccountValidator destination is set to NULL'); - } - if (null !== $account) { - Log::debug(sprintf('AccountValidator destination is set to #%d: "%s" (%s)', $account->id, $account->name, $account?->accountType->type)); - } - $this->destination = $account; - } - - /** - * @param Account|null $account - */ - public function setSource(?Account $account): void - { - if (null === $account) { - Log::debug('AccountValidator source is set to NULL'); - } - if (null !== $account) { - Log::debug(sprintf('AccountValidator source is set to #%d: "%s" (%s)', $account->id, $account->name, $account?->accountType->type)); - } - $this->source = $account; - } } diff --git a/app/Validation/CurrencyValidation.php b/app/Validation/CurrencyValidation.php index f454c3bd15..c7f0d7e7c9 100644 --- a/app/Validation/CurrencyValidation.php +++ b/app/Validation/CurrencyValidation.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Validation; -use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Trait CurrencyValidation @@ -36,6 +36,13 @@ trait CurrencyValidation { public const TEST = 'Test'; + /** + * @param Validator $validator + * + * @return array + */ + abstract protected function getTransactionsArray(Validator $validator): array; + /** * If the transactions contain foreign amounts, there must also be foreign currency information. * @@ -80,11 +87,4 @@ trait CurrencyValidation } } } - - /** - * @param Validator $validator - * - * @return array - */ - abstract protected function getTransactionsArray(Validator $validator): array; } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 6e8ebdd106..e517b886bd 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -41,7 +41,6 @@ use FireflyIII\Support\ParseDateString; use FireflyIII\TransactionRules\Triggers\TriggerInterface; use FireflyIII\User; use Google2FA; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Illuminate\Validation\Validator; use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException; @@ -505,121 +504,6 @@ class FireflyValidator extends Validator return $this->validateByAccountName($value); } - /** - * @return bool - */ - private function validateAccountAnonymously(): bool - { - if (!array_key_exists('user_id', $this->data)) { - return false; - } - - $user = User::find($this->data['user_id']); - $type = AccountType::find($this->data['account_type_id'])->first(); - $value = $this->data['name']; - - /** @var Account|null $result */ - $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); - - return null === $result; - } - - /** - * @param string $value - * @param array $parameters - * @param string $type - * - * @return bool - */ - private function validateByAccountTypeString(string $value, array $parameters, string $type): bool - { - /** @var array|null $search */ - $search = Config::get('firefly.accountTypeByIdentifier.'.$type); - - if (null === $search) { - return false; - } - - $accountTypes = AccountType::whereIn('type', $search)->get(); - $ignore = (int)($parameters[0] ?? 0.0); - $accountTypeIds = $accountTypes->pluck('id')->toArray(); - /** @var Account|null $result */ - $result = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore) - ->where('name', $value) - ->first(); - return null === $result; - } - - /** - * @param mixed $value - * @param mixed $parameters - * - * @return bool - */ - private function validateByAccountTypeId($value, $parameters): bool - { - $type = AccountType::find($this->data['account_type_id'])->first(); - $ignore = (int)($parameters[0] ?? 0.0); - - /** @var Account|null $result */ - $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) - ->where('name', $value) - ->first(); - - return null === $result; - } - - /** - * @param int $accountId - * @param mixed $value - * - * @return bool - */ - private function validateByParameterId(int $accountId, $value): bool - { - /** @var Account $existingAccount */ - $existingAccount = Account::find($accountId); - - $type = $existingAccount->accountType; - $ignore = $existingAccount->id; - - $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) - ->where('name', $value) - ->first(); - - return null === $entry; - } - - /** - * @param mixed $value - * - * @return bool - */ - private function validateByAccountId($value): bool - { - /** @var Account $existingAccount */ - $existingAccount = Account::find($this->data['id']); - - $type = $existingAccount->accountType; - $ignore = $existingAccount->id; - - $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) - ->where('name', $value) - ->first(); - - return null === $entry; - } - - /** - * @param string $value - * - * @return bool - */ - private function validateByAccountName(string $value): bool - { - return auth()->user()->accounts()->where('name', $value)->count() === 0; - } - /** * @param mixed $attribute * @param mixed $value @@ -673,16 +557,6 @@ class FireflyValidator extends Validator return false; } - /** - * @param $attribute - * @param $value - * @return bool - */ - public function validateUniqueCurrencyCode($attribute, $value): bool - { - return $this->validateUniqueCurrency('code', (string)$attribute, (string)$value); - } - /** * @param string $field * @param string $attribute @@ -694,6 +568,16 @@ class FireflyValidator extends Validator return 0 === DB::table('transaction_currencies')->where($field, $value)->whereNull('deleted_at')->count(); } + /** + * @param $attribute + * @param $value + * @return bool + */ + public function validateUniqueCurrencyCode($attribute, $value): bool + { + return $this->validateUniqueCurrency('code', (string)$attribute, (string)$value); + } + /** * @param $attribute * @param $value @@ -791,9 +675,9 @@ class FireflyValidator extends Validator } // get entries from table $result = DB::table($table)->where('user_id', auth()->user()->id)->whereNull('deleted_at') - ->where('id', '!=', $exclude) - ->where($field, $value) - ->first([$field]); + ->where('id', '!=', $exclude) + ->where($field, $value) + ->first([$field]); if (null === $result) { return true; // not found, so true. } @@ -871,4 +755,119 @@ class FireflyValidator extends Validator return false; } + + /** + * @return bool + */ + private function validateAccountAnonymously(): bool + { + if (!array_key_exists('user_id', $this->data)) { + return false; + } + + $user = User::find($this->data['user_id']); + $type = AccountType::find($this->data['account_type_id'])->first(); + $value = $this->data['name']; + + /** @var Account|null $result */ + $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); + + return null === $result; + } + + /** + * @param mixed $value + * + * @return bool + */ + private function validateByAccountId($value): bool + { + /** @var Account $existingAccount */ + $existingAccount = Account::find($this->data['id']); + + $type = $existingAccount->accountType; + $ignore = $existingAccount->id; + + $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $entry; + } + + /** + * @param string $value + * + * @return bool + */ + private function validateByAccountName(string $value): bool + { + return auth()->user()->accounts()->where('name', $value)->count() === 0; + } + + /** + * @param mixed $value + * @param mixed $parameters + * + * @return bool + */ + private function validateByAccountTypeId($value, $parameters): bool + { + $type = AccountType::find($this->data['account_type_id'])->first(); + $ignore = (int)($parameters[0] ?? 0.0); + + /** @var Account|null $result */ + $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $result; + } + + /** + * @param string $value + * @param array $parameters + * @param string $type + * + * @return bool + */ + private function validateByAccountTypeString(string $value, array $parameters, string $type): bool + { + /** @var array|null $search */ + $search = Config::get('firefly.accountTypeByIdentifier.'.$type); + + if (null === $search) { + return false; + } + + $accountTypes = AccountType::whereIn('type', $search)->get(); + $ignore = (int)($parameters[0] ?? 0.0); + $accountTypeIds = $accountTypes->pluck('id')->toArray(); + /** @var Account|null $result */ + $result = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + return null === $result; + } + + /** + * @param int $accountId + * @param mixed $value + * + * @return bool + */ + private function validateByParameterId(int $accountId, $value): bool + { + /** @var Account $existingAccount */ + $existingAccount = Account::find($accountId); + + $type = $existingAccount->accountType; + $ignore = $existingAccount->id; + + $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first(); + + return null === $entry; + } } diff --git a/app/Validation/GroupValidation.php b/app/Validation/GroupValidation.php index 78d18a55aa..7f1f7d32aa 100644 --- a/app/Validation/GroupValidation.php +++ b/app/Validation/GroupValidation.php @@ -26,8 +26,8 @@ namespace FireflyIII\Validation; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; -use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Trait GroupValidation. @@ -36,6 +36,13 @@ use Illuminate\Support\Facades\Log; */ trait GroupValidation { + /** + * @param Validator $validator + * + * @return array + */ + abstract protected function getTransactionsArray(Validator $validator): array; + /** * @param Validator $validator */ @@ -54,7 +61,7 @@ trait GroupValidation ]; /** @var array $transaction */ foreach ($transactions as $index => $transaction) { - if(!is_array($transaction)) { + if (!is_array($transaction)) { throw new FireflyException('Invalid data submitted: transaction is not array.'); } $hasAccountInfo = false; @@ -79,13 +86,6 @@ trait GroupValidation // only an issue if there is no transaction_journal_id } - /** - * @param Validator $validator - * - * @return array - */ - abstract protected function getTransactionsArray(Validator $validator): array; - /** * Adds an error to the "description" field when the user has submitted no descriptions and no * journal description. diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php index fb7669603d..0d5c8893fc 100644 --- a/app/Validation/RecurrenceValidation.php +++ b/app/Validation/RecurrenceValidation.php @@ -26,9 +26,9 @@ namespace FireflyIII\Validation; use Carbon\Carbon; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceTransaction; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\Validator; use InvalidArgumentException; -use Illuminate\Support\Facades\Log; /** * Trait RecurrenceValidation diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 2c80315ad5..1fa1080e50 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -68,281 +68,6 @@ trait TransactionValidation } } - /** - * @param Validator $validator - * - * @return array - */ - protected function getTransactionsArray(Validator $validator): array - { - Log::debug('Now in getTransactionsArray'); - $data = $validator->getData(); - $transactions = []; - if (is_array($data) && array_key_exists('transactions', $data) && is_array($data['transactions'])) { - Log::debug('Transactions key exists and is array.'); - $transactions = $data['transactions']; - } - if (is_array($data) && array_key_exists('transactions', $data) && !is_array($data['transactions'])) { - Log::debug(sprintf('Transactions key exists but is NOT array, its a %s', gettype($data['transactions']))); - } - // should be impossible to hit this: - if (!is_countable($transactions)) { - Log::error(sprintf('Transactions array is not countable, because its a %s', gettype($transactions))); - return []; - } - //Log::debug('Returning transactions.', $transactions); - - return $transactions; - } - - /** - * @param Validator $validator - * @param int $index - * @param string $transactionType - * @param array $transaction - */ - protected function validateSingleAccount(Validator $validator, int $index, string $transactionType, array $transaction): void - { - /** @var AccountValidator $accountValidator */ - $accountValidator = app(AccountValidator::class); - - $transactionType = $transaction['type'] ?? $transactionType; - $accountValidator->setTransactionType($transactionType); - - // validate source account. - $sourceId = array_key_exists('source_id', $transaction) ? (int)$transaction['source_id'] : null; - $sourceName = array_key_exists('source_name', $transaction) ? (string)$transaction['source_name'] : null; - $sourceIban = array_key_exists('source_iban', $transaction) ? (string)$transaction['source_iban'] : null; - $sourceNumber = array_key_exists('source_number', $transaction) ? (string)$transaction['source_number'] : null; - $source = [ - 'id' => $sourceId, - 'name' => $sourceName, - 'iban' => $sourceIban, - 'number' => $sourceNumber, - ]; - $validSource = $accountValidator->validateSource($source); - - // do something with result: - if (false === $validSource) { - $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); - $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); - - return; - } - // validate destination account - $destinationId = array_key_exists('destination_id', $transaction) ? (int)$transaction['destination_id'] : null; - $destinationName = array_key_exists('destination_name', $transaction) ? (string)$transaction['destination_name'] : null; - $destinationIban = array_key_exists('destination_iban', $transaction) ? (string)$transaction['destination_iban'] : null; - $destinationNumber = array_key_exists('destination_number', $transaction) ? (string)$transaction['destination_number'] : null; - $destination = [ - 'id' => $destinationId, - 'name' => $destinationName, - 'iban' => $destinationIban, - 'number' => $destinationNumber, - ]; - $validDestination = $accountValidator->validateDestination($destination); - // do something with result: - if (false === $validDestination) { - $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); - $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); - } - // sanity check for reconciliation accounts. They can't both be null. - $this->sanityCheckReconciliation($validator, $transactionType, $index, $source, $destination); - - // sanity check for currency information. - $this->sanityCheckForeignCurrency($validator, $accountValidator, $transaction, $transactionType, $index); - } - - /** - * TODO describe this method. - * @param Validator $validator - * @param AccountValidator $accountValidator - * @param array $transaction - * @param string $transactionType - * @param int $index - * @return void - */ - private function sanityCheckForeignCurrency( - Validator $validator, - AccountValidator $accountValidator, - array $transaction, - string $transactionType, - int $index - ): void { - Log::debug('Now in sanityCheckForeignCurrency()'); - if (0 !== $validator->errors()->count()) { - Log::debug('Already have errors, return'); - return; - } - if (null === $accountValidator->source) { - Log::debug('No source, return'); - return; - } - if (null === $accountValidator->destination) { - Log::debug('No destination, return'); - return; - } - $source = $accountValidator->source; - $destination = $accountValidator->destination; - - Log::debug(sprintf('Source: #%d "%s (%s)"', $source->id, $source->name, $source->accountType->type)); - Log::debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $source->accountType->type)); - - if (!$this->isLiabilityOrAsset($source) || !$this->isLiabilityOrAsset($destination)) { - Log::debug('Any account must be liability or asset account to continue.'); - return; - } - - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $defaultCurrency = app('amount')->getDefaultCurrency(); - $sourceCurrency = $accountRepository->getAccountCurrency($source) ?? $defaultCurrency; - $destinationCurrency = $accountRepository->getAccountCurrency($destination) ?? $defaultCurrency; - // if both accounts have the same currency, continue. - if ($sourceCurrency->code === $destinationCurrency->code) { - Log::debug('Both accounts have the same currency, continue.'); - return; - } - Log::debug(sprintf('Source account expects %s', $sourceCurrency->code)); - Log::debug(sprintf('Destination account expects %s', $destinationCurrency->code)); - - Log::debug(sprintf('Amount is %s', $transaction['amount'])); - - if (TransactionType::DEPOSIT === ucfirst($transactionType)) { - Log::debug(sprintf('Processing as a "%s"', $transactionType)); - // use case: deposit from liability account to an asset account - // the foreign amount must be in the currency of the source - // the amount must be in the currency of the destination - - // no foreign currency information is present: - if (!$this->hasForeignCurrencyInfo($transaction)) { - $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); - return; - } - - // wrong currency information is present - $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; - $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); - Log::debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); - if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== (int)$sourceCurrency->id) { - $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string)trans('validation.require_foreign_src')); - return; - } - } - if (TransactionType::TRANSFER === ucfirst($transactionType) || TransactionType::WITHDRAWAL === ucfirst($transactionType)) { - Log::debug(sprintf('Processing as a "%s"', $transactionType)); - // use case: withdrawal from asset account to a liability account. - // the foreign amount must be in the currency of the destination - // the amount must be in the currency of the source - - // use case: transfer between accounts with different currencies. - // the foreign amount must be in the currency of the destination - // the amount must be in the currency of the source - - // no foreign currency information is present: - if (!$this->hasForeignCurrencyInfo($transaction)) { - $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); - return; - } - - // wrong currency information is present - $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; - $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); - Log::debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); - if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== (int)$destinationCurrency->id) { - Log::debug(sprintf('No match on code, "%s" vs "%s"', $foreignCurrencyCode, $destinationCurrency->code)); - Log::debug(sprintf('No match on ID, #%d vs #%d', $foreignCurrencyId, $destinationCurrency->id)); - $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_dest')); - } - } - } - - /** - * @param Account $account - * @return bool - */ - private function isLiabilityOrAsset(Account $account): bool - { - return $this->isLiability($account) || $this->isAsset($account); - } - - /** - * @param Account $account - * @return bool - */ - private function isLiability(Account $account): bool - { - $type = $account->accountType?->type; - if (in_array($type, config('firefly.valid_liabilities'), true)) { - return true; - } - return false; - } - - /** - * @param Account $account - * @return bool - */ - private function isAsset(Account $account): bool - { - $type = $account->accountType?->type; - return $type === AccountType::ASSET; - } - - /** - * @param array $transaction - * @return bool - */ - private function hasForeignCurrencyInfo(array $transaction): bool - { - if (!array_key_exists('foreign_currency_code', $transaction) && !array_key_exists('foreign_currency_id', $transaction)) { - return false; - } - if (!array_key_exists('foreign_amount', $transaction)) { - return false; - } - if ('' === $transaction['foreign_amount']) { - return false; - } - if (bccomp('0', $transaction['foreign_amount']) === 0) { - return false; - } - return true; - } - - /** - * @param Validator $validator - * @param string $transactionType - * @param int $index - * @param array $source - * @param array $destination - * @return void - */ - protected function sanityCheckReconciliation(Validator $validator, string $transactionType, int $index, array $source, array $destination): void - { - Log::debug('Now in sanityCheckReconciliation'); - if (TransactionType::RECONCILIATION === ucfirst($transactionType) && - null === $source['id'] && null === $source['name'] && null === $destination['id'] && null === $destination['name'] - ) { - Log::debug('Both are NULL, error!'); - $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.source_name', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), trans('validation.reconciliation_either_account')); - } - - if (TransactionType::RECONCILIATION === $transactionType && - (null !== $source['id'] || null !== $source['name']) && - (null !== $destination['id'] || null !== $destination['name'])) { - Log::debug('Both are not NULL, error!'); - $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.source_name', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), trans('validation.reconciliation_either_account')); - $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), trans('validation.reconciliation_either_account')); - } - } - /** * Validates the given account information. Switches on given transaction type. * @@ -359,120 +84,13 @@ trait TransactionValidation * @var array $transaction */ foreach ($transactions as $index => $transaction) { - if(!is_int($index)) { + if (!is_int($index)) { throw new FireflyException('Invalid data submitted: transaction is not array.'); } $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); } } - /** - * @param Validator $validator - * @param int $index - * @param array $transaction - * @param TransactionGroup $transactionGroup - */ - protected function validateSingleUpdate(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void - { - Log::debug('Now validating single account update in validateSingleUpdate()'); - - // if no account types are given, just skip the check. - if ( - !array_key_exists('source_id', $transaction) - && !array_key_exists('source_name', $transaction) - && !array_key_exists('destination_id', $transaction) - && !array_key_exists('destination_name', $transaction)) { - Log::debug('No account data has been submitted so will not validating account info.'); - - return; - } - // create validator: - /** @var AccountValidator $accountValidator */ - $accountValidator = app(AccountValidator::class); - - // get the transaction type using the original transaction group: - $accountValidator->setTransactionType($this->getTransactionType($transactionGroup, [])); - - // validate if the submitted source and / or name are valid - if (array_key_exists('source_id', $transaction) || array_key_exists('source_name', $transaction)) { - Log::debug('Will try to validate source account information.'); - $sourceId = (int)($transaction['source_id'] ?? 0); - $sourceName = $transaction['source_name'] ?? null; - $validSource = $accountValidator->validateSource(['id' => $sourceId, 'name' => $sourceName]); - - // do something with result: - if (false === $validSource) { - app('log')->warning('Looks like the source account is not valid so complain to the user about it.'); - $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); - $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); - - return; - } - Log::debug('Source account info is valid.'); - } - - if (array_key_exists('destination_id', $transaction) || array_key_exists('destination_name', $transaction)) { - Log::debug('Will try to validate destination account information.'); - // at this point the validator may not have a source account, because it was never submitted for validation. - // must add it ourselves or the validator can never check if the destination is correct. - // the $transaction array must have a journal id or it's just one, this was validated before. - if (null === $accountValidator->source) { - Log::debug('Account validator has no source account, must find it.'); - $source = $this->getOriginalSource($transaction, $transactionGroup); - if (null !== $source) { - Log::debug('Found a source!'); - $accountValidator->source = $source; - } - } - $destinationId = (int)($transaction['destination_id'] ?? 0); - $destinationName = $transaction['destination_name'] ?? null; - $array = ['id' => $destinationId, 'name' => $destinationName,]; - $validDestination = $accountValidator->validateDestination($array); - // do something with result: - if (false === $validDestination) { - app('log')->warning('Looks like the destination account is not valid so complain to the user about it.'); - $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); - $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); - } - Log::debug('Destination account info is valid.'); - } - Log::debug('Done with validateSingleUpdate().'); - } - - /** - * @param TransactionGroup $group - * @param array $transactions - * - * @return string - */ - private function getTransactionType(TransactionGroup $group, array $transactions): string - { - return $transactions[0]['type'] ?? strtolower($group->transactionJournals()->first()->transactionType->type); - } - - /** - * @param array $transaction - * @param TransactionGroup $transactionGroup - * - * @return Account|null - */ - private function getOriginalSource(array $transaction, TransactionGroup $transactionGroup): ?Account - { - if (1 === $transactionGroup->transactionJournals->count()) { - $journal = $transactionGroup->transactionJournals->first(); - - return $journal->transactions()->where('amount', '<', 0)->first()->account; - } - /** @var TransactionJournal $journal */ - foreach ($transactionGroup->transactionJournals as $journal) { - if ((int)$journal->id === (int)$transaction['transaction_journal_id']) { - return $journal->transactions()->where('amount', '<', 0)->first()->account; - } - } - - return null; - } - /** * Adds an error to the validator when there are no transactions in the array of data. * @@ -585,6 +203,365 @@ trait TransactionValidation Log::debug('No errors in validateTransactionTypesForUpdate()'); } + /** + * @param Validator $validator + * + * @return array + */ + protected function getTransactionsArray(Validator $validator): array + { + Log::debug('Now in getTransactionsArray'); + $data = $validator->getData(); + $transactions = []; + if (is_array($data) && array_key_exists('transactions', $data) && is_array($data['transactions'])) { + Log::debug('Transactions key exists and is array.'); + $transactions = $data['transactions']; + } + if (is_array($data) && array_key_exists('transactions', $data) && !is_array($data['transactions'])) { + Log::debug(sprintf('Transactions key exists but is NOT array, its a %s', gettype($data['transactions']))); + } + // should be impossible to hit this: + if (!is_countable($transactions)) { + Log::error(sprintf('Transactions array is not countable, because its a %s', gettype($transactions))); + return []; + } + //Log::debug('Returning transactions.', $transactions); + + return $transactions; + } + + /** + * @param Validator $validator + * @param string $transactionType + * @param int $index + * @param array $source + * @param array $destination + * @return void + */ + protected function sanityCheckReconciliation(Validator $validator, string $transactionType, int $index, array $source, array $destination): void + { + Log::debug('Now in sanityCheckReconciliation'); + if (TransactionType::RECONCILIATION === ucfirst($transactionType) && + null === $source['id'] && null === $source['name'] && null === $destination['id'] && null === $destination['name'] + ) { + Log::debug('Both are NULL, error!'); + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), trans('validation.reconciliation_either_account')); + } + + if (TransactionType::RECONCILIATION === $transactionType && + (null !== $source['id'] || null !== $source['name']) && + (null !== $destination['id'] || null !== $destination['name'])) { + Log::debug('Both are not NULL, error!'); + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), trans('validation.reconciliation_either_account')); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), trans('validation.reconciliation_either_account')); + } + } + + /** + * @param Validator $validator + * @param int $index + * @param string $transactionType + * @param array $transaction + */ + protected function validateSingleAccount(Validator $validator, int $index, string $transactionType, array $transaction): void + { + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + + $transactionType = $transaction['type'] ?? $transactionType; + $accountValidator->setTransactionType($transactionType); + + // validate source account. + $sourceId = array_key_exists('source_id', $transaction) ? (int)$transaction['source_id'] : null; + $sourceName = array_key_exists('source_name', $transaction) ? (string)$transaction['source_name'] : null; + $sourceIban = array_key_exists('source_iban', $transaction) ? (string)$transaction['source_iban'] : null; + $sourceNumber = array_key_exists('source_number', $transaction) ? (string)$transaction['source_number'] : null; + $source = [ + 'id' => $sourceId, + 'name' => $sourceName, + 'iban' => $sourceIban, + 'number' => $sourceNumber, + ]; + $validSource = $accountValidator->validateSource($source); + + // do something with result: + if (false === $validSource) { + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); + + return; + } + // validate destination account + $destinationId = array_key_exists('destination_id', $transaction) ? (int)$transaction['destination_id'] : null; + $destinationName = array_key_exists('destination_name', $transaction) ? (string)$transaction['destination_name'] : null; + $destinationIban = array_key_exists('destination_iban', $transaction) ? (string)$transaction['destination_iban'] : null; + $destinationNumber = array_key_exists('destination_number', $transaction) ? (string)$transaction['destination_number'] : null; + $destination = [ + 'id' => $destinationId, + 'name' => $destinationName, + 'iban' => $destinationIban, + 'number' => $destinationNumber, + ]; + $validDestination = $accountValidator->validateDestination($destination); + // do something with result: + if (false === $validDestination) { + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); + } + // sanity check for reconciliation accounts. They can't both be null. + $this->sanityCheckReconciliation($validator, $transactionType, $index, $source, $destination); + + // sanity check for currency information. + $this->sanityCheckForeignCurrency($validator, $accountValidator, $transaction, $transactionType, $index); + } + + /** + * @param Validator $validator + * @param int $index + * @param array $transaction + * @param TransactionGroup $transactionGroup + */ + protected function validateSingleUpdate(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void + { + Log::debug('Now validating single account update in validateSingleUpdate()'); + + // if no account types are given, just skip the check. + if ( + !array_key_exists('source_id', $transaction) + && !array_key_exists('source_name', $transaction) + && !array_key_exists('destination_id', $transaction) + && !array_key_exists('destination_name', $transaction)) { + Log::debug('No account data has been submitted so will not validating account info.'); + + return; + } + // create validator: + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + + // get the transaction type using the original transaction group: + $accountValidator->setTransactionType($this->getTransactionType($transactionGroup, [])); + + // validate if the submitted source and / or name are valid + if (array_key_exists('source_id', $transaction) || array_key_exists('source_name', $transaction)) { + Log::debug('Will try to validate source account information.'); + $sourceId = (int)($transaction['source_id'] ?? 0); + $sourceName = $transaction['source_name'] ?? null; + $validSource = $accountValidator->validateSource(['id' => $sourceId, 'name' => $sourceName]); + + // do something with result: + if (false === $validSource) { + app('log')->warning('Looks like the source account is not valid so complain to the user about it.'); + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); + + return; + } + Log::debug('Source account info is valid.'); + } + + if (array_key_exists('destination_id', $transaction) || array_key_exists('destination_name', $transaction)) { + Log::debug('Will try to validate destination account information.'); + // at this point the validator may not have a source account, because it was never submitted for validation. + // must add it ourselves or the validator can never check if the destination is correct. + // the $transaction array must have a journal id or it's just one, this was validated before. + if (null === $accountValidator->source) { + Log::debug('Account validator has no source account, must find it.'); + $source = $this->getOriginalSource($transaction, $transactionGroup); + if (null !== $source) { + Log::debug('Found a source!'); + $accountValidator->source = $source; + } + } + $destinationId = (int)($transaction['destination_id'] ?? 0); + $destinationName = $transaction['destination_name'] ?? null; + $array = ['id' => $destinationId, 'name' => $destinationName,]; + $validDestination = $accountValidator->validateDestination($array); + // do something with result: + if (false === $validDestination) { + app('log')->warning('Looks like the destination account is not valid so complain to the user about it.'); + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); + } + Log::debug('Destination account info is valid.'); + } + Log::debug('Done with validateSingleUpdate().'); + } + + /** + * @param array $array + * + * @return bool + */ + private function arrayEqual(array $array): bool + { + return 1 === count(array_unique($array)); + } + + /** + * @param array $transactions + * + * @return array + */ + private function collectComparisonData(array $transactions): array + { + $fields = ['source_id', 'destination_id', 'source_name', 'destination_name']; + $comparison = []; + foreach ($fields as $field) { + $comparison[$field] = []; + /** @var array $transaction */ + foreach ($transactions as $transaction) { + // source or destination may be omitted. If this is the case, use the original source / destination name + ID. + $originalData = $this->getOriginalData((int)($transaction['transaction_journal_id'] ?? 0)); + + // get field. + $comparison[$field][] = $transaction[$field] ?? $originalData[$field]; + } + } + + return $comparison; + } + + /** + * @param string $type + * @param array $comparison + * + * @return bool + */ + private function compareAccountData(string $type, array $comparison): bool + { + return match ($type) { + default => $this->compareAccountDataWithdrawal($comparison), + 'deposit' => $this->compareAccountDataDeposit($comparison), + 'transfer' => $this->compareAccountDataTransfer($comparison), + }; + } + + /** + * @param array $comparison + * + * @return bool + */ + private function compareAccountDataDeposit(array $comparison): bool + { + if ($this->arrayEqual($comparison['destination_id'])) { + // destination ID's are equal, return void. + return true; + } + if ($this->arrayEqual($comparison['destination_name'])) { + // destination names are equal, return void. + return true; + } + + return false; + } + + /** + * @param array $comparison + * + * @return bool + */ + private function compareAccountDataTransfer(array $comparison): bool + { + if ($this->arrayEqual($comparison['source_id'])) { + // source ID's are equal, return void. + return true; + } + if ($this->arrayEqual($comparison['source_name'])) { + // source names are equal, return void. + return true; + } + if ($this->arrayEqual($comparison['destination_id'])) { + // destination ID's are equal, return void. + return true; + } + if ($this->arrayEqual($comparison['destination_name'])) { + // destination names are equal, return void. + return true; + } + + return false; + } + + /** + * @param array $comparison + * + * @return bool + */ + private function compareAccountDataWithdrawal(array $comparison): bool + { + if ($this->arrayEqual($comparison['source_id'])) { + // source ID's are equal, return void. + return true; + } + if ($this->arrayEqual($comparison['source_name'])) { + // source names are equal, return void. + return true; + } + + return false; + } + + /** + * @param int $journalId + * + * @return array + */ + private function getOriginalData(int $journalId): array + { + $return = [ + 'source_id' => 0, + 'source_name' => '', + 'destination_id' => 0, + 'destination_name' => '', + ]; + if (0 === $journalId) { + return $return; + } + /** @var Transaction $source */ + $source = Transaction::where('transaction_journal_id', $journalId)->where('amount', '<', 0)->with(['account'])->first(); + if (null !== $source) { + $return['source_id'] = $source->account_id; + $return['source_name'] = $source->account->name; + } + /** @var Transaction $destination */ + $destination = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->with(['account'])->first(); + if (null !== $source) { + $return['destination_id'] = $destination->account_id; + $return['destination_name'] = $destination->account->name; + } + + return $return; + } + + /** + * @param array $transaction + * @param TransactionGroup $transactionGroup + * + * @return Account|null + */ + private function getOriginalSource(array $transaction, TransactionGroup $transactionGroup): ?Account + { + if (1 === $transactionGroup->transactionJournals->count()) { + $journal = $transactionGroup->transactionJournals->first(); + + return $journal->transactions()->where('amount', '<', 0)->first()->account; + } + /** @var TransactionJournal $journal */ + foreach ($transactionGroup->transactionJournals as $journal) { + if ((int)$journal->id === (int)$transaction['transaction_journal_id']) { + return $journal->transactions()->where('amount', '<', 0)->first()->account; + } + } + + return null; + } + /** * @param int $journalId * @@ -604,6 +581,175 @@ trait TransactionValidation return 'invalid'; } + /** + * @param TransactionGroup $group + * @param array $transactions + * + * @return string + */ + private function getTransactionType(TransactionGroup $group, array $transactions): string + { + return $transactions[0]['type'] ?? strtolower($group->transactionJournals()->first()->transactionType->type); + } + + /** + * @param array $transaction + * @return bool + */ + private function hasForeignCurrencyInfo(array $transaction): bool + { + if (!array_key_exists('foreign_currency_code', $transaction) && !array_key_exists('foreign_currency_id', $transaction)) { + return false; + } + if (!array_key_exists('foreign_amount', $transaction)) { + return false; + } + if ('' === $transaction['foreign_amount']) { + return false; + } + if (bccomp('0', $transaction['foreign_amount']) === 0) { + return false; + } + return true; + } + + /** + * @param Account $account + * @return bool + */ + private function isAsset(Account $account): bool + { + $type = $account->accountType?->type; + return $type === AccountType::ASSET; + } + + /** + * @param Account $account + * @return bool + */ + private function isLiability(Account $account): bool + { + $type = $account->accountType?->type; + if (in_array($type, config('firefly.valid_liabilities'), true)) { + return true; + } + return false; + } + + /** + * @param Account $account + * @return bool + */ + private function isLiabilityOrAsset(Account $account): bool + { + return $this->isLiability($account) || $this->isAsset($account); + } + + /** + * TODO describe this method. + * @param Validator $validator + * @param AccountValidator $accountValidator + * @param array $transaction + * @param string $transactionType + * @param int $index + * @return void + */ + private function sanityCheckForeignCurrency( + Validator $validator, + AccountValidator $accountValidator, + array $transaction, + string $transactionType, + int $index + ): void { + Log::debug('Now in sanityCheckForeignCurrency()'); + if (0 !== $validator->errors()->count()) { + Log::debug('Already have errors, return'); + return; + } + if (null === $accountValidator->source) { + Log::debug('No source, return'); + return; + } + if (null === $accountValidator->destination) { + Log::debug('No destination, return'); + return; + } + $source = $accountValidator->source; + $destination = $accountValidator->destination; + + Log::debug(sprintf('Source: #%d "%s (%s)"', $source->id, $source->name, $source->accountType->type)); + Log::debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $source->accountType->type)); + + if (!$this->isLiabilityOrAsset($source) || !$this->isLiabilityOrAsset($destination)) { + Log::debug('Any account must be liability or asset account to continue.'); + return; + } + + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $sourceCurrency = $accountRepository->getAccountCurrency($source) ?? $defaultCurrency; + $destinationCurrency = $accountRepository->getAccountCurrency($destination) ?? $defaultCurrency; + // if both accounts have the same currency, continue. + if ($sourceCurrency->code === $destinationCurrency->code) { + Log::debug('Both accounts have the same currency, continue.'); + return; + } + Log::debug(sprintf('Source account expects %s', $sourceCurrency->code)); + Log::debug(sprintf('Destination account expects %s', $destinationCurrency->code)); + + Log::debug(sprintf('Amount is %s', $transaction['amount'])); + + if (TransactionType::DEPOSIT === ucfirst($transactionType)) { + Log::debug(sprintf('Processing as a "%s"', $transactionType)); + // use case: deposit from liability account to an asset account + // the foreign amount must be in the currency of the source + // the amount must be in the currency of the destination + + // no foreign currency information is present: + if (!$this->hasForeignCurrencyInfo($transaction)) { + $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); + return; + } + + // wrong currency information is present + $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; + $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); + Log::debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); + if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== (int)$sourceCurrency->id) { + $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string)trans('validation.require_foreign_src')); + return; + } + } + if (TransactionType::TRANSFER === ucfirst($transactionType) || TransactionType::WITHDRAWAL === ucfirst($transactionType)) { + Log::debug(sprintf('Processing as a "%s"', $transactionType)); + // use case: withdrawal from asset account to a liability account. + // the foreign amount must be in the currency of the destination + // the amount must be in the currency of the source + + // use case: transfer between accounts with different currencies. + // the foreign amount must be in the currency of the destination + // the amount must be in the currency of the source + + // no foreign currency information is present: + if (!$this->hasForeignCurrencyInfo($transaction)) { + $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); + return; + } + + // wrong currency information is present + $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; + $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); + Log::debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); + if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== (int)$destinationCurrency->id) { + Log::debug(sprintf('No match on code, "%s" vs "%s"', $foreignCurrencyCode, $destinationCurrency->code)); + Log::debug(sprintf('No match on ID, #%d vs #%d', $foreignCurrencyId, $destinationCurrency->id)); + $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_dest')); + } + } + } + /** * @param Validator $validator */ @@ -689,150 +835,4 @@ trait TransactionValidation } Log::debug('No errors found in validateEqualAccountsForUpdate'); } - - /** - * @param array $transactions - * - * @return array - */ - private function collectComparisonData(array $transactions): array - { - $fields = ['source_id', 'destination_id', 'source_name', 'destination_name']; - $comparison = []; - foreach ($fields as $field) { - $comparison[$field] = []; - /** @var array $transaction */ - foreach ($transactions as $transaction) { - // source or destination may be omitted. If this is the case, use the original source / destination name + ID. - $originalData = $this->getOriginalData((int)($transaction['transaction_journal_id'] ?? 0)); - - // get field. - $comparison[$field][] = $transaction[$field] ?? $originalData[$field]; - } - } - - return $comparison; - } - - /** - * @param int $journalId - * - * @return array - */ - private function getOriginalData(int $journalId): array - { - $return = [ - 'source_id' => 0, - 'source_name' => '', - 'destination_id' => 0, - 'destination_name' => '', - ]; - if (0 === $journalId) { - return $return; - } - /** @var Transaction $source */ - $source = Transaction::where('transaction_journal_id', $journalId)->where('amount', '<', 0)->with(['account'])->first(); - if (null !== $source) { - $return['source_id'] = $source->account_id; - $return['source_name'] = $source->account->name; - } - /** @var Transaction $destination */ - $destination = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->with(['account'])->first(); - if (null !== $source) { - $return['destination_id'] = $destination->account_id; - $return['destination_name'] = $destination->account->name; - } - - return $return; - } - - /** - * @param string $type - * @param array $comparison - * - * @return bool - */ - private function compareAccountData(string $type, array $comparison): bool - { - return match ($type) { - default => $this->compareAccountDataWithdrawal($comparison), - 'deposit' => $this->compareAccountDataDeposit($comparison), - 'transfer' => $this->compareAccountDataTransfer($comparison), - }; - } - - /** - * @param array $comparison - * - * @return bool - */ - private function compareAccountDataWithdrawal(array $comparison): bool - { - if ($this->arrayEqual($comparison['source_id'])) { - // source ID's are equal, return void. - return true; - } - if ($this->arrayEqual($comparison['source_name'])) { - // source names are equal, return void. - return true; - } - - return false; - } - - /** - * @param array $array - * - * @return bool - */ - private function arrayEqual(array $array): bool - { - return 1 === count(array_unique($array)); - } - - /** - * @param array $comparison - * - * @return bool - */ - private function compareAccountDataDeposit(array $comparison): bool - { - if ($this->arrayEqual($comparison['destination_id'])) { - // destination ID's are equal, return void. - return true; - } - if ($this->arrayEqual($comparison['destination_name'])) { - // destination names are equal, return void. - return true; - } - - return false; - } - - /** - * @param array $comparison - * - * @return bool - */ - private function compareAccountDataTransfer(array $comparison): bool - { - if ($this->arrayEqual($comparison['source_id'])) { - // source ID's are equal, return void. - return true; - } - if ($this->arrayEqual($comparison['source_name'])) { - // source names are equal, return void. - return true; - } - if ($this->arrayEqual($comparison['destination_id'])) { - // destination ID's are equal, return void. - return true; - } - if ($this->arrayEqual($comparison['destination_name'])) { - // destination names are equal, return void. - return true; - } - - return false; - } } diff --git a/composer.lock b/composer.lock index 79da5030e8..ca3c37ebd1 100644 --- a/composer.lock +++ b/composer.lock @@ -3694,16 +3694,16 @@ }, { "name": "nesbot/carbon", - "version": "2.66.0", + "version": "2.67.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "496712849902241f04902033b0441b269effe001" + "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/496712849902241f04902033b0441b269effe001", - "reference": "496712849902241f04902033b0441b269effe001", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/c1001b3bc75039b07f38a79db5237c4c529e04c8", + "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8", "shasum": "" }, "require": { @@ -3792,7 +3792,7 @@ "type": "tidelift" } ], - "time": "2023-01-29T18:53:47+00:00" + "time": "2023-05-25T22:09:47+00:00" }, { "name": "nette/schema", @@ -5868,16 +5868,16 @@ }, { "name": "symfony/console", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f" + "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f", + "url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77", + "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77", "shasum": "" }, "require": { @@ -5944,7 +5944,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.10" + "source": "https://github.com/symfony/console/tree/v6.2.11" }, "funding": [ { @@ -5960,7 +5960,7 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:37:43+00:00" + "time": "2023-05-26T08:16:21+00:00" }, { "name": "symfony/css-selector", @@ -6096,16 +6096,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb" + "reference": "e847ba47e7a8f9708082990cb40ab4ff0440a11e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b7e9f124640cb0611624a9383176c3e5f7d8cfb", - "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/e847ba47e7a8f9708082990cb40ab4ff0440a11e", + "reference": "e847ba47e7a8f9708082990cb40ab4ff0440a11e", "shasum": "" }, "require": { @@ -6147,7 +6147,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.10" + "source": "https://github.com/symfony/error-handler/tree/v6.2.11" }, "funding": [ { @@ -6163,7 +6163,7 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2023-05-05T11:55:01+00:00" }, { "name": "symfony/event-dispatcher", @@ -6563,16 +6563,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc" + "reference": "df27f4191a4292d01fd062296e09cbc8b657cb57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49adbb92bcb4e3c2943719d2756271e8b9602acc", - "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/df27f4191a4292d01fd062296e09cbc8b657cb57", + "reference": "df27f4191a4292d01fd062296e09cbc8b657cb57", "shasum": "" }, "require": { @@ -6621,7 +6621,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.2.10" + "source": "https://github.com/symfony/http-foundation/tree/v6.2.11" }, "funding": [ { @@ -6637,20 +6637,20 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2023-05-19T12:39:53+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "81064a65a5496f17d2b6984f6519406f98864215" + "reference": "954a1a3b178309b216261eedc735c079709e4ab3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/81064a65a5496f17d2b6984f6519406f98864215", - "reference": "81064a65a5496f17d2b6984f6519406f98864215", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/954a1a3b178309b216261eedc735c079709e4ab3", + "reference": "954a1a3b178309b216261eedc735c079709e4ab3", "shasum": "" }, "require": { @@ -6698,6 +6698,7 @@ "symfony/translation": "^5.4|^6.0", "symfony/translation-contracts": "^1.1|^2|^3", "symfony/uid": "^5.4|^6.0", + "symfony/var-exporter": "^6.2", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -6732,7 +6733,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.2.10" + "source": "https://github.com/symfony/http-kernel/tree/v6.2.11" }, "funding": [ { @@ -6748,7 +6749,7 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:50:28+00:00" + "time": "2023-05-27T21:12:52+00:00" }, { "name": "symfony/mailer", @@ -7637,16 +7638,16 @@ }, { "name": "symfony/process", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e" + "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", + "url": "https://api.github.com/repos/symfony/process/zipball/97ae9721bead9d1a39b5650e2f4b7834b93b539c", + "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c", "shasum": "" }, "require": { @@ -7678,7 +7679,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.10" + "source": "https://github.com/symfony/process/tree/v6.2.11" }, "funding": [ { @@ -7694,7 +7695,7 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:56:57+00:00" + "time": "2023-05-19T07:42:48+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -8045,16 +8046,16 @@ }, { "name": "symfony/translation", - "version": "v6.2.8", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5" + "reference": "64113df3e8b009f92fad63014f4ec647e65bc927" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/817535dbb1721df8b3a8f2489dc7e50bcd6209b5", - "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5", + "url": "https://api.github.com/repos/symfony/translation/zipball/64113df3e8b009f92fad63014f4ec647e65bc927", + "reference": "64113df3e8b009f92fad63014f4ec647e65bc927", "shasum": "" }, "require": { @@ -8123,7 +8124,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.8" + "source": "https://github.com/symfony/translation/tree/v6.2.11" }, "funding": [ { @@ -8139,7 +8140,7 @@ "type": "tidelift" } ], - "time": "2023-03-31T09:14:44+00:00" + "time": "2023-05-19T12:37:14+00:00" }, { "name": "symfony/translation-contracts", @@ -8298,16 +8299,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.2.10", + "version": "v6.2.11", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab" + "reference": "7d10f2a5a452bda385692fc7d38cd6eccfebe756" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41a750a23412ca76fdbbf5096943b4134272c1ab", - "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7d10f2a5a452bda385692fc7d38cd6eccfebe756", + "reference": "7d10f2a5a452bda385692fc7d38cd6eccfebe756", "shasum": "" }, "require": { @@ -8315,7 +8316,6 @@ "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", "symfony/console": "<5.4" }, "require-dev": { @@ -8366,7 +8366,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.10" + "source": "https://github.com/symfony/var-dumper/tree/v6.2.11" }, "funding": [ { @@ -8382,7 +8382,7 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2023-05-25T13:08:43+00:00" }, { "name": "therobfonz/laravel-mandrill-driver", diff --git a/config/app.php b/config/app.php index cd05e2bab3..b6f332b8c0 100644 --- a/config/app.php +++ b/config/app.php @@ -21,6 +21,17 @@ declare(strict_types=1); +use FireflyIII\Support\Facades\AccountForm; +use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Facades\CurrencyForm; +use FireflyIII\Support\Facades\ExpandedForm; +use FireflyIII\Support\Facades\FireflyConfig; +use FireflyIII\Support\Facades\Navigation; +use FireflyIII\Support\Facades\PiggyBankForm; +use FireflyIII\Support\Facades\Preferences; +use FireflyIII\Support\Facades\RuleForm; +use FireflyIII\Support\Facades\Steam; + return [ 'name' => envNonEmpty('APP_NAME', 'Firefly III'), 'env' => envNonEmpty('APP_ENV', 'local'), @@ -131,16 +142,16 @@ return [ 'View' => Illuminate\Support\Facades\View::class, 'Form' => Collective\Html\FormFacade::class, 'Html' => Collective\Html\HtmlFacade::class, - 'Preferences' => \FireflyIII\Support\Facades\Preferences::class, - 'FireflyConfig' => \FireflyIII\Support\Facades\FireflyConfig::class, - 'Navigation' => \FireflyIII\Support\Facades\Navigation::class, - 'Amount' => \FireflyIII\Support\Facades\Amount::class, - 'Steam' => \FireflyIII\Support\Facades\Steam::class, - 'ExpandedForm' => \FireflyIII\Support\Facades\ExpandedForm::class, - 'CurrencyForm' => \FireflyIII\Support\Facades\CurrencyForm::class, - 'AccountForm' => \FireflyIII\Support\Facades\AccountForm::class, - 'PiggyBankForm' => \FireflyIII\Support\Facades\PiggyBankForm::class, - 'RuleForm' => \FireflyIII\Support\Facades\RuleForm::class, + 'Preferences' => Preferences::class, + 'FireflyConfig' => FireflyConfig::class, + 'Navigation' => Navigation::class, + 'Amount' => Amount::class, + 'Steam' => Steam::class, + 'ExpandedForm' => ExpandedForm::class, + 'CurrencyForm' => CurrencyForm::class, + 'AccountForm' => AccountForm::class, + 'PiggyBankForm' => PiggyBankForm::class, + 'RuleForm' => RuleForm::class, 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, 'Twig' => TwigBridge\Facade\Twig::class, diff --git a/config/database.php b/config/database.php index 87371d7b43..01629997bd 100644 --- a/config/database.php +++ b/config/database.php @@ -75,12 +75,12 @@ if (false !== $useSSL && null !== $useSSL) { return [ 'default' => envNonEmpty('DB_CONNECTION', 'mysql'), 'connections' => [ - 'sqlite' => [ + 'sqlite' => [ 'driver' => 'sqlite', 'database' => envNonEmpty('DB_DATABASE', storage_path('database/database.sqlite')), 'prefix' => '', ], - 'mysql' => [ + 'mysql' => [ 'driver' => 'mysql', 'host' => envNonEmpty('DB_HOST', $host), 'port' => envNonEmpty('DB_PORT', $port), @@ -95,7 +95,7 @@ return [ 'engine' => 'InnoDB', 'options' => $mySqlSSLOptions, ], - 'pgsql' => [ + 'pgsql' => [ 'driver' => 'pgsql', 'host' => envNonEmpty('DB_HOST', $host), 'port' => envNonEmpty('DB_PORT', $port), @@ -110,7 +110,7 @@ return [ 'sslkey' => envNonEmpty('PGSQL_SSL_KEY'), 'sslrootcert' => envNonEmpty('PGSQL_SSL_ROOT_CERT'), ], - 'sqlsrv' => [ + 'sqlsrv' => [ 'driver' => 'sqlsrv', 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), diff --git a/config/mail.php b/config/mail.php index 22a0401edc..f82ae13e05 100644 --- a/config/mail.php +++ b/config/mail.php @@ -72,7 +72,7 @@ return [ 'channel' => env('MAIL_LOG_CHANNEL', 'stack'), 'level' => 'notice', ], - 'null' => [ + 'null' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL', 'stack'), 'level' => 'notice', diff --git a/database/migrations/2016_10_22_075804_changes_for_v410.php b/database/migrations/2016_10_22_075804_changes_for_v410.php index 9979235e54..5d3993d2e0 100644 --- a/database/migrations/2016_10_22_075804_changes_for_v410.php +++ b/database/migrations/2016_10_22_075804_changes_for_v410.php @@ -46,7 +46,7 @@ class ChangesForV410 extends Migration */ public function up(): void { - if(!Schema::hasTable('notes')) { + if (!Schema::hasTable('notes')) { try { Schema::create( 'notes', diff --git a/database/migrations/2017_04_13_163623_changes_for_v440.php b/database/migrations/2017_04_13_163623_changes_for_v440.php index d74d4d81ca..92c062e7c8 100644 --- a/database/migrations/2017_04_13_163623_changes_for_v440.php +++ b/database/migrations/2017_04_13_163623_changes_for_v440.php @@ -89,7 +89,7 @@ class ChangesForV440 extends Migration Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); } } - if(!Schema::hasColumn('transactions', 'transaction_currency_id')) { + if (!Schema::hasColumn('transactions', 'transaction_currency_id')) { try { Schema::table( 'transactions', diff --git a/database/migrations/2017_06_02_105232_changes_for_v450.php b/database/migrations/2017_06_02_105232_changes_for_v450.php index 572a154d13..f2e667d282 100644 --- a/database/migrations/2017_06_02_105232_changes_for_v450.php +++ b/database/migrations/2017_06_02_105232_changes_for_v450.php @@ -39,7 +39,7 @@ class ChangesForV450 extends Migration public function down(): void { // split up for sqlite compatibility - if(Schema::hasColumn('transactions', 'foreign_amount')) { + if (Schema::hasColumn('transactions', 'foreign_amount')) { try { Schema::table( 'transactions', @@ -67,7 +67,7 @@ class ChangesForV450 extends Migration Log::error(sprintf('Could not execute query: %s', $e->getMessage())); Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); } - if(Schema::hasColumn('transactions', 'foreign_currency_id')) { + if (Schema::hasColumn('transactions', 'foreign_currency_id')) { try { Schema::table( 'transactions', @@ -89,7 +89,7 @@ class ChangesForV450 extends Migration public function up(): void { // add "foreign_amount" to transactions - if(!Schema::hasColumn('transactions', 'foreign_amount')) { + if (!Schema::hasColumn('transactions', 'foreign_amount')) { try { Schema::table( 'transactions', @@ -104,7 +104,7 @@ class ChangesForV450 extends Migration } // add foreign transaction currency id to transactions (is nullable): - if(!Schema::hasColumn('transactions', 'foreign_currency_id')) { + if (!Schema::hasColumn('transactions', 'foreign_currency_id')) { try { Schema::table( 'transactions', diff --git a/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php b/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php index 1b11da602e..a498667e14 100644 --- a/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php +++ b/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php @@ -47,7 +47,7 @@ class CreateOauthAuthCodesTable extends Migration */ public function up(): void { - if(!Schema::hasTable('oauth_auth_codes')) { + if (!Schema::hasTable('oauth_auth_codes')) { try { Schema::create( 'oauth_auth_codes', diff --git a/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php b/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php index d23bf407dd..2d177fe642 100644 --- a/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php +++ b/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php @@ -47,7 +47,7 @@ class CreateOauthAccessTokensTable extends Migration */ public function up(): void { - if(!Schema::hasTable('oauth_access_tokens')) { + if (!Schema::hasTable('oauth_access_tokens')) { try { Schema::create( 'oauth_access_tokens', diff --git a/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php b/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php index c74233cec3..0b6b178976 100644 --- a/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php +++ b/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php @@ -47,7 +47,7 @@ class CreateOauthRefreshTokensTable extends Migration */ public function up(): void { - if(!Schema::hasTable('oauth_refresh_tokens')) { + if (!Schema::hasTable('oauth_refresh_tokens')) { try { Schema::create( 'oauth_refresh_tokens', diff --git a/database/migrations/2018_01_01_000004_create_oauth_clients_table.php b/database/migrations/2018_01_01_000004_create_oauth_clients_table.php index a0a260f367..c0087c7c25 100644 --- a/database/migrations/2018_01_01_000004_create_oauth_clients_table.php +++ b/database/migrations/2018_01_01_000004_create_oauth_clients_table.php @@ -47,7 +47,7 @@ class CreateOauthClientsTable extends Migration */ public function up(): void { - if(!Schema::hasTable('oauth_clients')) { + if (!Schema::hasTable('oauth_clients')) { try { Schema::create( 'oauth_clients', diff --git a/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php b/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php index 3b12be0c67..970363809f 100644 --- a/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php +++ b/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php @@ -47,7 +47,7 @@ class CreateOauthPersonalAccessClientsTable extends Migration */ public function up(): void { - if(!Schema::hasTable('oauth_personal_access_clients')) { + if (!Schema::hasTable('oauth_personal_access_clients')) { try { Schema::create( 'oauth_personal_access_clients', diff --git a/database/migrations/2018_03_19_141348_changes_for_v472.php b/database/migrations/2018_03_19_141348_changes_for_v472.php index cfa7717406..9a897f7169 100644 --- a/database/migrations/2018_03_19_141348_changes_for_v472.php +++ b/database/migrations/2018_03_19_141348_changes_for_v472.php @@ -41,7 +41,7 @@ class ChangesForV472 extends Migration */ public function down(): void { - if(!Schema::hasColumn('attachments', 'notes')) { + if (!Schema::hasColumn('attachments', 'notes')) { try { Schema::table( 'attachments', @@ -55,7 +55,7 @@ class ChangesForV472 extends Migration } } - if(Schema::hasColumn('transactions', 'order')) { + if (Schema::hasColumn('transactions', 'order')) { try { Schema::table( 'budgets', @@ -77,7 +77,7 @@ class ChangesForV472 extends Migration */ public function up(): void { - if(Schema::hasColumn('attachments', 'notes')) { + if (Schema::hasColumn('attachments', 'notes')) { try { Schema::table( 'attachments', @@ -91,7 +91,7 @@ class ChangesForV472 extends Migration } } - if(!Schema::hasColumn('budgets', 'order')) { + if (!Schema::hasColumn('budgets', 'order')) { try { Schema::table( 'budgets', diff --git a/database/migrations/2018_04_07_210913_changes_for_v473.php b/database/migrations/2018_04_07_210913_changes_for_v473.php index c471fbcf28..a45a466407 100644 --- a/database/migrations/2018_04_07_210913_changes_for_v473.php +++ b/database/migrations/2018_04_07_210913_changes_for_v473.php @@ -42,7 +42,7 @@ class ChangesForV473 extends Migration */ public function down(): void { - if(!Schema::hasColumn('bills', 'transaction_currency_id')) { + if (!Schema::hasColumn('bills', 'transaction_currency_id')) { try { Schema::table( 'bills', @@ -60,7 +60,7 @@ class ChangesForV473 extends Migration } } - if(!Schema::hasColumn('rules', 'strict')) { + if (!Schema::hasColumn('rules', 'strict')) { try { Schema::table( 'rules', @@ -82,7 +82,7 @@ class ChangesForV473 extends Migration */ public function up(): void { - if(!Schema::hasColumn('bills', 'transaction_currency_id')) { + if (!Schema::hasColumn('bills', 'transaction_currency_id')) { try { Schema::table( 'bills', @@ -96,7 +96,7 @@ class ChangesForV473 extends Migration Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); } } - if(!Schema::hasColumn('rules', 'strict')) { + if (!Schema::hasColumn('rules', 'strict')) { try { Schema::table( 'rules', diff --git a/database/migrations/2018_09_05_195147_changes_for_v477.php b/database/migrations/2018_09_05_195147_changes_for_v477.php index c234d10f31..72f056d65d 100644 --- a/database/migrations/2018_09_05_195147_changes_for_v477.php +++ b/database/migrations/2018_09_05_195147_changes_for_v477.php @@ -41,7 +41,7 @@ class ChangesForV477 extends Migration */ public function down(): void { - if(Schema::hasColumn('budget_limits', 'transaction_currency_id')) { + if (Schema::hasColumn('budget_limits', 'transaction_currency_id')) { try { Schema::table( 'budget_limits', @@ -68,7 +68,7 @@ class ChangesForV477 extends Migration */ public function up(): void { - if(!Schema::hasColumn('budget_limits', 'transaction_currency_id')) { + if (!Schema::hasColumn('budget_limits', 'transaction_currency_id')) { try { Schema::table( 'budget_limits', diff --git a/database/migrations/2018_11_06_172532_changes_for_v479.php b/database/migrations/2018_11_06_172532_changes_for_v479.php index 989d7aaf83..797d3fa2d5 100644 --- a/database/migrations/2018_11_06_172532_changes_for_v479.php +++ b/database/migrations/2018_11_06_172532_changes_for_v479.php @@ -41,7 +41,7 @@ class ChangesForV479 extends Migration */ public function down(): void { - if(Schema::hasColumn('transaction_currencies', 'enabled')) { + if (Schema::hasColumn('transaction_currencies', 'enabled')) { try { Schema::table( 'transaction_currencies', @@ -63,7 +63,7 @@ class ChangesForV479 extends Migration */ public function up(): void { - if(!Schema::hasColumn('transaction_currencies', 'enabled')) { + if (!Schema::hasColumn('transaction_currencies', 'enabled')) { try { Schema::table( 'transaction_currencies', diff --git a/database/migrations/2019_03_11_223700_fix_ldap_configuration.php b/database/migrations/2019_03_11_223700_fix_ldap_configuration.php index f098b8ba6e..e50e253f9e 100644 --- a/database/migrations/2019_03_11_223700_fix_ldap_configuration.php +++ b/database/migrations/2019_03_11_223700_fix_ldap_configuration.php @@ -41,7 +41,7 @@ class FixLdapConfiguration extends Migration */ public function down(): void { - if(Schema::hasColumn('users', 'objectguid')) { + if (Schema::hasColumn('users', 'objectguid')) { try { Schema::table( 'users', @@ -67,7 +67,7 @@ class FixLdapConfiguration extends Migration * ADLdap2 appears to require the ability to store an objectguid for LDAP users * now. To support this, we add the column. */ - if(!Schema::hasColumn('users', 'objectguid')) { + if (!Schema::hasColumn('users', 'objectguid')) { try { Schema::table( 'users', diff --git a/database/migrations/2019_03_22_183214_changes_for_v480.php b/database/migrations/2019_03_22_183214_changes_for_v480.php index bad696d61f..34e7cc217c 100644 --- a/database/migrations/2019_03_22_183214_changes_for_v480.php +++ b/database/migrations/2019_03_22_183214_changes_for_v480.php @@ -41,7 +41,7 @@ class ChangesForV480 extends Migration public function down(): void { // remove group ID - if(Schema::hasColumn('transaction_journals', 'transaction_group_id')) { + if (Schema::hasColumn('transaction_journals', 'transaction_group_id')) { try { Schema::table( 'transaction_journals', @@ -71,7 +71,7 @@ class ChangesForV480 extends Migration } // remove 'stop processing' column - if(Schema::hasColumn('rule_groups', 'stop_processing')) { + if (Schema::hasColumn('rule_groups', 'stop_processing')) { try { Schema::table( 'rule_groups', @@ -91,7 +91,7 @@ class ChangesForV480 extends Migration } // remove 'mfa_secret' column - if(Schema::hasColumn('users', 'mfa_secret')) { + if (Schema::hasColumn('users', 'mfa_secret')) { try { Schema::table( 'users', @@ -119,7 +119,7 @@ class ChangesForV480 extends Migration public function up(): void { // add currency_id - if(!Schema::hasColumn('transaction_journals', 'transaction_group_id')) { + if (!Schema::hasColumn('transaction_journals', 'transaction_group_id')) { try { Schema::table( 'transaction_journals', @@ -148,7 +148,7 @@ class ChangesForV480 extends Migration } // add 'stop processing' column - if(!Schema::hasColumn('rule_groups', 'stop_processing')) { + if (!Schema::hasColumn('rule_groups', 'stop_processing')) { try { Schema::table( 'rule_groups', @@ -163,7 +163,7 @@ class ChangesForV480 extends Migration } // add 'mfa_secret' column - if(!Schema::hasColumn('users', 'mfa_secret')) { + if (!Schema::hasColumn('users', 'mfa_secret')) { try { Schema::table( 'users', diff --git a/database/migrations/2020_06_30_202620_changes_for_v530a.php b/database/migrations/2020_06_30_202620_changes_for_v530a.php index 31e3a30ce0..659faf7480 100644 --- a/database/migrations/2020_06_30_202620_changes_for_v530a.php +++ b/database/migrations/2020_06_30_202620_changes_for_v530a.php @@ -42,7 +42,7 @@ class ChangesForV530a extends Migration */ public function down(): void { - if(Schema::hasColumn('bills', 'order')) { + if (Schema::hasColumn('bills', 'order')) { try { Schema::table( 'bills', @@ -64,7 +64,7 @@ class ChangesForV530a extends Migration */ public function up(): void { - if(!Schema::hasColumn('bills', 'order')) { + if (!Schema::hasColumn('bills', 'order')) { try { Schema::table( 'bills', diff --git a/database/migrations/2020_07_24_162820_changes_for_v540.php b/database/migrations/2020_07_24_162820_changes_for_v540.php index 51571ae1df..597463d7cc 100644 --- a/database/migrations/2020_07_24_162820_changes_for_v540.php +++ b/database/migrations/2020_07_24_162820_changes_for_v540.php @@ -42,7 +42,7 @@ class ChangesForV540 extends Migration */ public function down(): void { - if(Schema::hasColumn('oauth_clients', 'provider')) { + if (Schema::hasColumn('oauth_clients', 'provider')) { try { Schema::table( 'oauth_clients', @@ -56,7 +56,7 @@ class ChangesForV540 extends Migration } } - if(Schema::hasColumn('accounts', 'order')) { + if (Schema::hasColumn('accounts', 'order')) { try { Schema::table( 'accounts', @@ -70,7 +70,7 @@ class ChangesForV540 extends Migration } } // in two steps for sqlite - if(Schema::hasColumn('bills', 'end_date')) { + if (Schema::hasColumn('bills', 'end_date')) { try { Schema::table( 'bills', @@ -83,7 +83,7 @@ class ChangesForV540 extends Migration Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); } } - if(Schema::hasColumn('bills', 'extension_date')) { + if (Schema::hasColumn('bills', 'extension_date')) { try { Schema::table( 'bills', @@ -105,7 +105,7 @@ class ChangesForV540 extends Migration */ public function up(): void { - if(!Schema::hasColumn('accounts', 'order')) { + if (!Schema::hasColumn('accounts', 'order')) { try { Schema::table( 'accounts', @@ -119,7 +119,7 @@ class ChangesForV540 extends Migration } } - if(!Schema::hasColumn('oauth_clients', 'provider')) { + if (!Schema::hasColumn('oauth_clients', 'provider')) { try { Schema::table( 'oauth_clients', @@ -133,7 +133,7 @@ class ChangesForV540 extends Migration } } - if(!Schema::hasColumn('bills', 'end_date') && !Schema::hasColumn('bills', 'extension_date')) { + if (!Schema::hasColumn('bills', 'end_date') && !Schema::hasColumn('bills', 'extension_date')) { try { Schema::table( 'bills', diff --git a/database/migrations/2020_11_12_070604_changes_for_v550.php b/database/migrations/2020_11_12_070604_changes_for_v550.php index 20cc4a1073..39d4702e7f 100644 --- a/database/migrations/2020_11_12_070604_changes_for_v550.php +++ b/database/migrations/2020_11_12_070604_changes_for_v550.php @@ -67,7 +67,7 @@ class ChangesForV550 extends Migration } // expand budget / transaction journal table. - if(Schema::hasColumn('budget_transaction_journal', 'budget_limit_id')) { + if (Schema::hasColumn('budget_transaction_journal', 'budget_limit_id')) { try { Schema::table( 'budget_transaction_journal', @@ -89,7 +89,7 @@ class ChangesForV550 extends Migration // drop fields from budget limits // in two steps for sqlite - if(Schema::hasColumn('budget_limits', 'period')) { + if (Schema::hasColumn('budget_limits', 'period')) { try { Schema::table( 'budget_limits', @@ -102,7 +102,7 @@ class ChangesForV550 extends Migration Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); } } - if(Schema::hasColumn('budget_limits', 'generated')) { + if (Schema::hasColumn('budget_limits', 'generated')) { try { Schema::table( 'budget_limits', @@ -176,7 +176,7 @@ class ChangesForV550 extends Migration } // update budget / transaction journal table. - if(!Schema::hasColumn('budget_transaction_journal', 'budget_limit_id')) { + if (!Schema::hasColumn('budget_transaction_journal', 'budget_limit_id')) { try { Schema::table( 'budget_transaction_journal', diff --git a/database/migrations/2021_03_12_061213_changes_for_v550b2.php b/database/migrations/2021_03_12_061213_changes_for_v550b2.php index 900bde7d4d..1667f467ec 100644 --- a/database/migrations/2021_03_12_061213_changes_for_v550b2.php +++ b/database/migrations/2021_03_12_061213_changes_for_v550b2.php @@ -40,7 +40,7 @@ class ChangesForV550b2 extends Migration */ public function down(): void { - if(Schema::hasColumn('recurrences_transactions', 'transaction_type_id')) { + if (Schema::hasColumn('recurrences_transactions', 'transaction_type_id')) { try { Schema::table( 'recurrences_transactions', @@ -68,7 +68,7 @@ class ChangesForV550b2 extends Migration public function up(): void { // expand recurrence transaction table - if(!Schema::hasColumn('recurrences_transactions', 'transaction_type_id')) { + if (!Schema::hasColumn('recurrences_transactions', 'transaction_type_id')) { try { Schema::table( 'recurrences_transactions', diff --git a/database/migrations/2021_05_09_064644_add_ldap_columns_to_users_table.php b/database/migrations/2021_05_09_064644_add_ldap_columns_to_users_table.php index c8d29df8ac..c4a357f3fb 100644 --- a/database/migrations/2021_05_09_064644_add_ldap_columns_to_users_table.php +++ b/database/migrations/2021_05_09_064644_add_ldap_columns_to_users_table.php @@ -35,7 +35,7 @@ class AddLdapColumnsToUsersTable extends Migration */ public function down(): void { - if(Schema::hasColumn('users', 'domain')) { + if (Schema::hasColumn('users', 'domain')) { try { Schema::table( 'users', @@ -55,7 +55,7 @@ class AddLdapColumnsToUsersTable extends Migration */ public function up(): void { - if(!Schema::hasColumn('users', 'domain')) { + if (!Schema::hasColumn('users', 'domain')) { try { Schema::table( 'users', diff --git a/database/seeders/ExchangeRateSeeder.php b/database/seeders/ExchangeRateSeeder.php index 390b70d36a..8cd6b85001 100644 --- a/database/seeders/ExchangeRateSeeder.php +++ b/database/seeders/ExchangeRateSeeder.php @@ -71,6 +71,28 @@ class ExchangeRateSeeder extends Seeder } } + /** + * @param User $user + * @param TransactionCurrency $from + * @param TransactionCurrency $to + * @param string $date + * @param float $rate + * @return void + */ + private function addRate(User $user, TransactionCurrency $from, TransactionCurrency $to, string $date, float $rate): void + { + /** @var User $user */ + CurrencyExchangeRate::create( + [ + 'user_id' => $user->id, + 'from_currency_id' => $from->id, + 'to_currency_id' => $to->id, + 'date' => $date, + 'rate' => $rate, + ] + ); + } + /** * @param string $code * @return TransactionCurrency|null @@ -95,26 +117,4 @@ class ExchangeRateSeeder extends Seeder ->where('date', $date) ->count() > 0; } - - /** - * @param User $user - * @param TransactionCurrency $from - * @param TransactionCurrency $to - * @param string $date - * @param float $rate - * @return void - */ - private function addRate(User $user, TransactionCurrency $from, TransactionCurrency $to, string $date, float $rate): void - { - /** @var User $user */ - CurrencyExchangeRate::create( - [ - 'user_id' => $user->id, - 'from_currency_id' => $from->id, - 'to_currency_id' => $to->id, - 'date' => $date, - 'rate' => $rate, - ] - ); - } } From 6ed5892cf9b63c271bdcb16c1edde963f6b7261a Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 29 May 2023 14:17:10 +0200 Subject: [PATCH 02/30] chore: fix various small sonacloud issues to see if the actions run correctly. --- app/Http/Controllers/Transaction/ShowController.php | 6 +++--- app/Repositories/LinkType/LinkTypeRepository.php | 2 +- app/Services/Internal/Update/RecurrenceUpdateService.php | 5 ++--- app/Support/Search/OperatorQuerySearch.php | 4 ++-- bootstrap/app.php | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index 60d5ec957c..bb182c7d10 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -41,7 +41,7 @@ use Symfony\Component\HttpFoundation\ParameterBag; */ class ShowController extends Controller { - private ALERepositoryInterface $ALERepository; + private ALERepositoryInterface $aleRepository; private TransactionGroupRepositoryInterface $repository; /** @@ -55,7 +55,7 @@ class ShowController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(TransactionGroupRepositoryInterface::class); - $this->ALERepository = app(ALERepositoryInterface::class); + $this->aleRepository = app(ALERepositoryInterface::class); app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-exchange'); @@ -112,7 +112,7 @@ class ShowController extends Controller // get audit log entries: $logEntries = []; foreach ($transactionGroup->transactionJournals as $journal) { - $logEntries[$journal->id] = $this->ALERepository->getForObject($journal); + $logEntries[$journal->id] = $this->aleRepository->getForObject($journal); } $events = $this->repository->getPiggyEvents($transactionGroup); diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index 83cf35e81b..596990606a 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -212,7 +212,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $merged->filter( function (TransactionJournalLink $link) { - return (null !== $link->source && null !== $link->destination); + return null !== $link->source && null !== $link->destination; } ); } diff --git a/app/Services/Internal/Update/RecurrenceUpdateService.php b/app/Services/Internal/Update/RecurrenceUpdateService.php index 455de69768..9ee14e9156 100644 --- a/app/Services/Internal/Update/RecurrenceUpdateService.php +++ b/app/Services/Internal/Update/RecurrenceUpdateService.php @@ -125,9 +125,8 @@ class RecurrenceUpdateService $originalCount = $recurrence->recurrenceRepetitions()->count(); if (1 === $originalCount) { Log::debug('Return the first one'); - /** @var RecurrenceRepetition $result */ - $result = $recurrence->recurrenceRepetitions()->first(); - return $result; + /** @var RecurrenceRepetition|null */ + return $recurrence->recurrenceRepetitions()->first(); } // find it: $fields = [ diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 76c9c7d8b1..9081d6c219 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -437,7 +437,7 @@ class OperatorQuerySearch implements SearchInterface $stringMethod = 'str_contains'; } if (4 === $stringPosition) { - $stringMethod = 'str_is_equal'; + $stringMethod = 'stringIsEqual'; } // get accounts: @@ -515,7 +515,7 @@ class OperatorQuerySearch implements SearchInterface $stringMethod = 'str_contains'; } if (4 === $stringPosition) { - $stringMethod = 'str_is_equal'; + $stringMethod = 'stringIsEqual'; } // search for accounts: diff --git a/bootstrap/app.php b/bootstrap/app.php index 96ecd9288a..7180e36c15 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -52,14 +52,14 @@ if (!function_exists('envNonEmpty')) { } } -if (!function_exists('str_is_equal')) { +if (!function_exists('stringIsEqual')) { /** * @param string $left * @param string $right * * @return bool */ - function str_is_equal(string $left, string $right): bool + function stringIsEqual(string $left, string $right): bool { return $left === $right; } From 43f668dc6510b1982774240f6da6fbfaf3396ddc Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 29 May 2023 14:18:43 +0200 Subject: [PATCH 03/30] config: no more sonarcloud / qodana for each PR. --- .github/workflows/qodana.yml | 2 -- .github/workflows/sonarcloud.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml index cb933a2508..da78ebabe8 100644 --- a/.github/workflows/qodana.yml +++ b/.github/workflows/qodana.yml @@ -5,8 +5,6 @@ on: branches: - main - develop - pull_request: - types: [ opened, synchronize, reopened ] jobs: qodana: runs-on: ubuntu-latest diff --git a/.github/workflows/sonarcloud.yaml b/.github/workflows/sonarcloud.yaml index 5e6bc93605..a1a34fa24f 100644 --- a/.github/workflows/sonarcloud.yaml +++ b/.github/workflows/sonarcloud.yaml @@ -4,8 +4,6 @@ on: branches: - main - develop - pull_request: - types: [opened, synchronize, reopened] jobs: sonarcloud: name: SonarCloud From 4334e9bed76d0001b0ab02eae2911ddea30f2488 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 30 May 2023 20:15:07 +0200 Subject: [PATCH 04/30] chore: small fixes and prep for new language --- app/Api/V2/Response/Sum/AutoSum.php | 4 +- app/Support/Search/OperatorQuerySearch.php | 3 +- config/firefly.php | 3 +- ...016_06_16_000000_create_support_tables.php | 43 +- .../2016_06_16_000001_create_users_table.php | 7 +- .../2016_06_16_000002_create_main_tables.php | 146 +- .../2016_09_12_121359_fix_nullables.php | 11 +- resources/lang/.gitignore | 1 + resources/lang/pt_PT/breadcrumbs.php | 16 +- resources/lang/pt_PT/demo.php | 20 +- resources/lang/pt_PT/email.php | 104 +- resources/lang/pt_PT/errors.php | 20 +- resources/lang/pt_PT/firefly.php | 1284 ++++++++--------- resources/lang/pt_PT/form.php | 152 +- resources/lang/pt_PT/intro.php | 118 +- resources/lang/pt_PT/list.php | 80 +- resources/lang/pt_PT/validation.php | 224 +-- resources/lang/uk_UA/firefly.php | 348 ++--- 18 files changed, 1303 insertions(+), 1281 deletions(-) diff --git a/app/Api/V2/Response/Sum/AutoSum.php b/app/Api/V2/Response/Sum/AutoSum.php index 8303e9da1d..691d3c3eff 100644 --- a/app/Api/V2/Response/Sum/AutoSum.php +++ b/app/Api/V2/Response/Sum/AutoSum.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V2\Response\Sum; use Closure; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionCurrency; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; @@ -42,6 +43,7 @@ class AutoSum * @param Closure $getSum * * @return array + * @throws FireflyException */ public function autoSum(Collection $objects, Closure $getCurrency, Closure $getSum): array { @@ -66,6 +68,6 @@ class AutoSum } var_dump(array_values($return)); - exit; + throw new FireflyException('Not implemented'); } } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 9081d6c219..7336581724 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -179,10 +179,11 @@ class OperatorQuerySearch implements SearchInterface /** * @inheritDoc + * @throws FireflyException */ public function hasModifiers(): bool { - die(__METHOD__); + throw new FireflyException('Not implemented'); } /** diff --git a/config/firefly.php b/config/firefly.php index 75c82aaabf..1758714bf9 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -175,7 +175,8 @@ return [ 'ko_KR' => ['name_locale' => 'Korean', 'name_english' => 'Korean'], // 'lt_LT' => ['name_locale' => 'Lietuvių', 'name_english' => 'Lithuanian'], - 'nb_NO' => ['name_locale' => 'Norsk', 'name_english' => 'Norwegian'], + 'nb_NO' => ['name_locale' => 'Norsk Bokmål', 'name_english' => 'Norwegian Bokmål'], + 'nn_NO' => ['name_locale' => 'Norsk Nynorsk', 'name_english' => 'Norwegian Nynorsk'], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'], 'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish'], 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)'], diff --git a/database/migrations/2016_06_16_000000_create_support_tables.php b/database/migrations/2016_06_16_000000_create_support_tables.php index 2fa73f45fb..b8d599b6f2 100644 --- a/database/migrations/2016_06_16_000000_create_support_tables.php +++ b/database/migrations/2016_06_16_000000_create_support_tables.php @@ -32,6 +32,9 @@ use Illuminate\Database\Schema\Blueprint; */ class CreateSupportTables extends Migration { + private const TABLE_ALREADY_EXISTS = 'If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'; + private const TABLE_ERROR = 'Could not create table "%s": %s'; + /** * Reverse the migrations. */ @@ -86,8 +89,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "account_types": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'account_types', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -110,8 +113,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "configuration": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'configuration', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -138,8 +141,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "transaction_currencies": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'transaction_currencies', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -167,8 +170,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "jobs": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'jobs', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -190,8 +193,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "password_resets": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'password_resets', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -216,8 +219,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "permission_role": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'permission_role', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -240,8 +243,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "permissions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'permissions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -264,8 +267,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "roles": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'roles', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -289,8 +292,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "sessions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'sessions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -315,8 +318,8 @@ class CreateSupportTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "transaction_types": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'transaction_types', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } diff --git a/database/migrations/2016_06_16_000001_create_users_table.php b/database/migrations/2016_06_16_000001_create_users_table.php index 30729aada8..12cc7dfdb9 100644 --- a/database/migrations/2016_06_16_000001_create_users_table.php +++ b/database/migrations/2016_06_16_000001_create_users_table.php @@ -32,6 +32,9 @@ use Illuminate\Database\Schema\Blueprint; */ class CreateUsersTable extends Migration { + private const TABLE_ALREADY_EXISTS = 'If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'; + private const TABLE_ERROR = 'Could not create table "%s": %s'; + /** * Reverse the migrations. */ @@ -62,8 +65,8 @@ class CreateUsersTable extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "users": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'users', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } diff --git a/database/migrations/2016_06_16_000002_create_main_tables.php b/database/migrations/2016_06_16_000002_create_main_tables.php index 39fd4e345e..abfa0d45cf 100644 --- a/database/migrations/2016_06_16_000002_create_main_tables.php +++ b/database/migrations/2016_06_16_000002_create_main_tables.php @@ -32,6 +32,9 @@ use Illuminate\Database\Schema\Blueprint; */ class CreateMainTables extends Migration { + private const TABLE_ALREADY_EXISTS = 'If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'; + private const TABLE_ERROR = 'Could not create table "%s": %s'; + /** * Reverse the migrations. */ @@ -108,8 +111,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "accounts": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'accounts', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -127,8 +130,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "account_meta": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'account_meta', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -160,8 +163,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "attachments": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'attachments', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -194,8 +197,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "bills": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'bills', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -220,8 +223,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "budgets": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'budgets', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } if (!Schema::hasTable('budget_limits')) { @@ -240,8 +243,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "budget_limits": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'budget_limits', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } if (!Schema::hasTable('limit_repetitions')) { @@ -259,8 +262,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "limit_repetitions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'limit_repetitions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -287,8 +290,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "categories": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'categories', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -316,8 +319,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "piggy_banks": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'piggy_banks', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -336,8 +339,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "piggy_bank_repetitions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'piggy_bank_repetitions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -359,8 +362,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "preferences": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'preferences', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -385,8 +388,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "role_user": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'role_user', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -412,32 +415,37 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "rule_groups": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'rule_groups', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } if (!Schema::hasTable('rules')) { - Schema::create( - 'rules', - static function (Blueprint $table) { - $table->increments('id'); - $table->timestamps(); - $table->softDeletes(); - $table->integer('user_id', false, true); - $table->integer('rule_group_id', false, true); - $table->string('title', 255); - $table->text('description')->nullable(); - $table->integer('order', false, true)->default(0); - $table->boolean('active')->default(1); - $table->boolean('stop_processing')->default(0); + try { + Schema::create( + 'rules', + static function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('rule_group_id', false, true); + $table->string('title', 255); + $table->text('description')->nullable(); + $table->integer('order', false, true)->default(0); + $table->boolean('active')->default(1); + $table->boolean('stop_processing')->default(0); - // link user id to users table - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + // link user id to users table + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - // link rule group id to rule group table - $table->foreign('rule_group_id')->references('id')->on('rule_groups')->onDelete('cascade'); - } - ); + // link rule group id to rule group table + $table->foreign('rule_group_id')->references('id')->on('rule_groups')->onDelete('cascade'); + } + ); + } catch (QueryException $e) { + Log::error(sprintf(self::TABLE_ERROR, 'rules', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); + } } if (!Schema::hasTable('rule_actions')) { try { @@ -460,8 +468,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "rule_actions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'rule_actions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } if (!Schema::hasTable('rule_triggers')) { @@ -485,8 +493,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "rule_triggers": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'rule_triggers', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -519,8 +527,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "tags": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'tags', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } @@ -558,8 +566,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "transaction_journals": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'transaction_journals', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -578,8 +586,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "journal_meta": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'journal_meta', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -599,8 +607,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "tag_transaction_journal": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'tag_transaction_journal', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -617,8 +625,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "budget_transaction_journal": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'budget_transaction_journal', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -635,8 +643,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "category_transaction_journal": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'category_transaction_journal', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -657,8 +665,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "piggy_bank_events": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'piggy_bank_events', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -680,8 +688,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "transactions": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'transactions', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -699,8 +707,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "budget_transaction": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'budget_transaction', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } @@ -718,8 +726,8 @@ class CreateMainTables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not create table "category_transaction": %s', $e->getMessage())); - Log::error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + Log::error(sprintf(self::TABLE_ERROR, 'category_transaction', $e->getMessage())); + Log::error(self::TABLE_ALREADY_EXISTS); } } } diff --git a/database/migrations/2016_09_12_121359_fix_nullables.php b/database/migrations/2016_09_12_121359_fix_nullables.php index e1b55ca23b..9a112b1bea 100644 --- a/database/migrations/2016_09_12_121359_fix_nullables.php +++ b/database/migrations/2016_09_12_121359_fix_nullables.php @@ -32,6 +32,9 @@ use Illuminate\Database\Schema\Blueprint; */ class FixNullables extends Migration { + private const COLUMN_ALREADY_EXISTS = 'If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'; + private const TABLE_UPDATE_ERROR = 'Could not update table "%s": %s'; + /** * Reverse the migrations. */ @@ -54,8 +57,8 @@ class FixNullables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not update table: %s', $e->getMessage())); - Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); + Log::error(sprintf(self::TABLE_UPDATE_ERROR, 'rule_groups', $e->getMessage())); + Log::error(self::COLUMN_ALREADY_EXISTS); } } @@ -68,8 +71,8 @@ class FixNullables extends Migration } ); } catch (QueryException $e) { - Log::error(sprintf('Could not execute query: %s', $e->getMessage())); - Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'); + Log::error(sprintf(self::TABLE_UPDATE_ERROR, 'rules', $e->getMessage())); + Log::error(self::COLUMN_ALREADY_EXISTS); } } } diff --git a/resources/lang/.gitignore b/resources/lang/.gitignore index ddb387aeec..60399433bc 100644 --- a/resources/lang/.gitignore +++ b/resources/lang/.gitignore @@ -8,3 +8,4 @@ sr_CS tlh_AA ceb_PH fil_PH +nn_NO diff --git a/resources/lang/pt_PT/breadcrumbs.php b/resources/lang/pt_PT/breadcrumbs.php index 9c54277d6c..553e09c75d 100644 --- a/resources/lang/pt_PT/breadcrumbs.php +++ b/resources/lang/pt_PT/breadcrumbs.php @@ -35,21 +35,21 @@ declare(strict_types=1); return [ - 'home' => 'Inicio', + 'home' => 'Início', 'budgets' => 'Orçamentos', 'subscriptions' => 'Subscrições', 'transactions' => 'Transações', 'title_expenses' => 'Despesas', 'title_withdrawal' => 'Despesas', - 'title_revenue' => 'Receita / renda', - 'title_deposit' => 'Receita / renda', + 'title_revenue' => 'Receita / rendimento', + 'title_deposit' => 'Receita / rendimento', 'title_transfer' => 'Transferências', 'title_transfers' => 'Transferências', 'edit_currency' => 'Editar moeda ":name"', 'delete_currency' => 'Apagar moeda ":name"', 'newPiggyBank' => 'Criar mealheiro', 'edit_piggyBank' => 'Editar mealheiro ":name"', - 'preferences' => 'Preferencias', + 'preferences' => 'Preferências', 'profile' => 'Perfil', 'accounts' => 'Contas', 'changePassword' => 'Alterar password', @@ -95,9 +95,9 @@ return [ 'edit_object_group' => 'Editar grupo ":title"', 'delete_object_group' => 'Apagar grupo ":title"', 'logout_others' => 'Sair de outras sessões', - 'asset_accounts' => 'Conta de ativos', - 'expense_accounts' => 'Conta de despesas', - 'revenue_accounts' => 'Conta de receitas', - 'liabilities_accounts' => 'Conta de passivos', + 'asset_accounts' => 'Contas de ativos', + 'expense_accounts' => 'Contas de despesas', + 'revenue_accounts' => 'Contas de receitas', + 'liabilities_accounts' => 'Passivos', 'placeholder' => '[Placeholder]', ]; diff --git a/resources/lang/pt_PT/demo.php b/resources/lang/pt_PT/demo.php index d52ad143dd..b81f29a364 100644 --- a/resources/lang/pt_PT/demo.php +++ b/resources/lang/pt_PT/demo.php @@ -35,17 +35,17 @@ declare(strict_types=1); return [ - 'no_demo_text' => 'Lamentamos, nao existe uma explicacao-demonstracao extra para esta pagina.', - 'see_help_icon' => 'No entanto, o icone no canto superior direito pode te informar de mais.', - 'index' => 'Bem vindo ao Firefly III! Nesta pagina poderá ver um resumo rápido das suas finanças. Para mais informações, verifique as contas → Contas de Ativos, os Orçamentos e claro a página dos Relatórios. Em alternativa dê uma volta pela plataforma e observe onde acaba por parar.', - 'accounts-index' => 'Contas de ativos são as contas bancárias pessoais. Contas de despesas são contas onde se gasta dinheiro, como lojas e amigos. Contas de receitas são contas que se recebe dinheiro, como o salário, governo e outras fontes de rendimentos. Contas de passivos são onde as dividas e empréstimos tais como um crédito bancários, hipotecas, crédito habitação. Nesta página podes editá-los ou removê-los.', - 'budgets-index' => 'Esta pagina dá-lhe uma visão geral sobre os seus orçamentos. A barra de topo mostra o montante disponível para ser orçamentado. Isto pode ser personalizado para qualquer período ao clicar no montante do lado direito. O montante que atualmente já gastou encontra-se exibido na barra abaixo. Por baixo dela, encontra as despesas por orçamento e o que orçamentou de cada um deles.', - 'reports-index-start' => 'O Firefly III suporta inúmeros tipos de relatórios. Leia melhor sobre eles ao clicar no ícone , no canto superior direito.', - 'reports-index-examples' => 'Certifica-te que olhas estes exemplos: visao geral das financas mensal, visao geral das financas anual e visao geral do orcamento.', - 'currencies-index' => 'O Firefly III suporta múltiplas moedas. No entanto é colocado o Euro como moeda padrão. Pode ser definido para Dólares Americanos entre outras. Como pode verificar, uma pequena seleção de moedas foram incluidas, no entanto, se desejar, pode adicionar outras. Alterar a moeda padrão não vai alterar a moeda padrão das transações já existentes. Contudo, o Firefly III suporta o uso de múltiplas moedas em simultâneo.', + 'no_demo_text' => 'Lamento, não existe nenhuma explicação-demonstração extra para esta página.', + 'see_help_icon' => 'No entanto, o ícone no canto superior direito poderá dar mais informação.', + 'index' => 'Bem-vindo ao Firefly III! Nesta página poderá ver um resumo rápido das suas finanças. Para mais informações, verifique as contas → Contas de Ativos, os Orçamentos e claro a página dos Relatórios. Em alternativa dê uma volta pela plataforma e observe onde acaba por parar.', + 'accounts-index' => 'Contas de ativos são as contas bancárias pessoais. Contas de despesas são contas onde se gasta dinheiro, como lojas e amigos. Contas de receitas são contas de onde se recebe dinheiro, como o salário, governo e outras fontes de rendimentos. Contas de passivos são onde as dividas e empréstimos tais como cartões de crédito ou créditos à habitação. Nesta página podes editá-los ou removê-los.', + 'budgets-index' => 'Esta pagina dá-lhe uma visão geral sobre os seus orçamentos. A barra de topo mostra o montante disponível para ser orçamentado. Isto pode ser personalizado para qualquer período ao clicar no montante do lado direito. O montante que atualmente já gastou encontra-se exibido na barra abaixo. Mais abaixo, encontra as despesas por orçamento e o que orçamentou para cada uma delas.', + 'reports-index-start' => 'O Firefly III suporta vários tipos de relatórios. Leia sobre eles ao clicar no ícone , no canto superior direito.', + 'reports-index-examples' => 'Certifique-se de que vê estes exemplos: uma visão geral mensal das finanças, visão geral anual das finanças e uma visão geral do orçamento.', + 'currencies-index' => 'O Firefly III suporta múltiplas moedas. Embora seja definido o Euro como moeda padrão, pode ser definido para o Dólar Americano ou outra. Como pode ver, foi incluída uma pequena seleção de moedas, mas, se assim o desejar, pode adicionar outras. No entanto, alterar a moeda padrão não vai alterar a moeda das transações já existentes. O Firefly III suporta o uso de múltiplas moedas em simultâneo.', 'transactions-index' => 'Estas despesas, depósitos e transferências não são particularmente imaginativas. Foram geradas automaticamente.', - 'piggy-banks-index' => 'Existem 3 mealheiros. Usa o botão mais e menos para atualizar o montante de dinheiro em cada mealheiro. Clica no nome do mealheiro para ver a administração de cada mealheiro.', - 'profile-index' => 'Nao te esquecas que a plataforma demo reinicia a cada 4 horas. O teu acesso pode ser revogado a qualquer altura. Isto acontece automaticamente e nao e um problema na plataforma.', + 'piggy-banks-index' => 'Como pode ver, existem três mealheiros. Use os botões mais e menos para atualizar o montante de dinheiro em cada mealheiro. Clica no nome do mealheiro para ver a administração de cada mealheiro.', + 'profile-index' => 'Lembre-se que a plataforma demo reinicia a cada 4 horas. O seu acesso pode ser revogado a qualquer altura. Acontece automaticamente e não é um erro na plataforma.', ]; /* diff --git a/resources/lang/pt_PT/email.php b/resources/lang/pt_PT/email.php index 86164e1bf4..44eb309782 100644 --- a/resources/lang/pt_PT/email.php +++ b/resources/lang/pt_PT/email.php @@ -38,12 +38,12 @@ return [ // common items 'greeting' => 'Olá,', 'closing' => 'Beep boop,', - 'signature' => 'O robô de email Firefly III', - 'footer_ps' => 'PS: Esta mensagem foi enviada porque um pedido do IP :ipAddress a activou.', + 'signature' => 'O robot de email do Firefly III', + 'footer_ps' => 'PS: Esta mensagem foi enviada porque um pedido do IP :ipAddress a ativou.', // admin test 'admin_test_subject' => 'Uma mensagem de teste da instalação do Firefly III', - 'admin_test_body' => 'Esta é uma mensagem de teste da sua plataforma Firefly III. Foi enviada para :email.', + 'admin_test_body' => 'Esta é uma mensagem de teste da sua instância do Firefly III. Foi enviada para :email.', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -58,37 +58,37 @@ return [ // invite - 'invitation_created_subject' => 'An invitation has been created', - 'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.', - 'invite_user_subject' => 'You\'ve been invited to create a Firefly III account.', - 'invitation_introduction' => 'You\'ve been invited to create a Firefly III account on **:host**. Firefly III is a personal, self-hosted, private personal finance manager. All the cool kids are using it.', - 'invitation_invited_by' => 'You\'ve been invited by ":admin" and this invitation was sent to ":invitee". That\'s you, right?', - 'invitation_url' => 'The invitation is valid for 48 hours and can be redeemed by surfing to [Firefly III](:url). Enjoy!', + 'invitation_created_subject' => 'Foi criado um convite', + 'invitation_created_body' => 'O utilizador administrador ":email" criou um convite que pode ser usado por alguém que detenha o email ":invitee". O convite será válido por 48 horas.', + 'invite_user_subject' => 'Foi convidado para criar uma conta no Firefly III.', + 'invitation_introduction' => 'Foi convidado para criar uma conta no Firefly III em **:host**. O Firefly III é um gestor de finanças pessoais, pessoal e auto alojado. Toda a malta fixe já o usa.', + 'invitation_invited_by' => 'Foi convidado por ":admin" e este convite foi enviado para ":invitee". É você, certo?', + 'invitation_url' => 'Este convite é válido por 48 horas e pode ser utilizado navegando para [Firefly III](:url). Divirta-se!', // new IP - 'login_from_new_ip' => 'Nova sessão no Firefly III', - 'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)', - 'new_ip_body' => 'O Firefly III detectou uma nova sessão na sua conta de um endereço IP desconhecido. Se nunca iniciou sessão a partir endereço IP abaixo, ou foi há mais de seis meses, o Firefly III irá avisá-lo.', - 'new_ip_warning' => 'Se reconhecer este endereço IP ou sessão, pode ignorar esta mensagem. Se não iniciou sessão ou não tenha ideia do que possa ser este inicio de sessão, verifique a segurança da sua senha, altere-a e desconecte-se de todas as outras sessões iniciadas. Para fazer isso, vá á sua página de perfil. Claro que você já activou 2FA, não é? Mantenha-se seguro!', + 'login_from_new_ip' => 'Novo início de sessão no Firefly III', + 'slack_login_from_new_ip' => 'Novo início de sessão no Firefly III, a partir do IP :ip (:host)', + 'new_ip_body' => 'O Firefly III detetou um novo início de sessão na sua conta a partir de um endereço IP desconhecido. Se nunca iniciou sessão a partir do endereço IP abaixo, ou foi há mais de seis meses, o Firefly III irá avisá-lo.', + 'new_ip_warning' => 'Se reconhecer este endereço IP ou sessão, pode ignorar esta mensagem. Se não iniciou sessão ou não tenha ideia do que possa ser este inicio de sessão, verifique a segurança da sua palavra-passe, altere-a e termine todas as outras sessões iniciadas. Para fazer isso, vá à sua página de perfil. É claro que já activou a 2FA, não é? Mantenha-se seguro!', 'ip_address' => 'Endereço IP', - 'host_name' => 'Servidor', + 'host_name' => 'Anfitrião', 'date_time' => 'Data + hora', // access token created 'access_token_created_subject' => 'Foi criado um novo token de acesso', - 'access_token_created_body' => 'Alguém (em principio você) acabou de criar um novo Token de Acesso da API Firefly III para sua conta de utilizador.', - 'access_token_created_explanation' => 'With this token, they can access **all** of your financial records through the Firefly III API.', - 'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url', + 'access_token_created_body' => 'Alguém (em principio você) acabou de criar um novo Token de Acesso da API Firefly III para a sua conta de utilizador.', + 'access_token_created_explanation' => 'Com este token, eles podem aceder a **todos** os seus registos financeiros através da API do Firefly III.', + 'access_token_created_revoke' => 'Se não foi você, por favor, revogue este token assim que for possível em :url', // registered - 'registered_subject' => 'Bem vindo ao Firefly III!', - 'registered_subject_admin' => 'A new user has registered', - 'admin_new_user_registered' => 'A new user has registered. User **:email** was given user ID #:id.', - 'registered_welcome' => 'Welcome to [Firefly III](:address). Your registration has made it, and this email is here to confirm it. Yay!', - 'registered_pw' => 'If you have forgotten your password already, please reset it using [the password reset tool](:address/password/reset).', + 'registered_subject' => 'Bem-vindo ao Firefly III!', + 'registered_subject_admin' => 'Foi registado um novo utilizador', + 'admin_new_user_registered' => 'Foi registado um novo utilizador. Ao utilizador **:email** foi dada a ID #:id.', + 'registered_welcome' => 'Bem-vindo ao [Firefly III](:address). O seu registo foi bem-sucedido e este email está aqui para o confirmar. Viva!', + 'registered_pw' => 'Se já se esqueceu da sua palavra-passe, por favor, reponha-a usando [a ferramenta de redefinição de palavras-passe](:address/password/reset).', 'registered_help' => 'Existe um ícone de ajuda no canto superior direito de cada página. Se precisar de ajuda, clique-lhe!', - 'registered_doc_html' => 'If you haven\'t already, please read the [grand theory](https://docs.firefly-iii.org/about-firefly-iii/personal-finances).', - 'registered_doc_text' => 'If you haven\'t already, please also read the first use guide and the full description.', + 'registered_doc_html' => 'Se ainda não o fez, por favor, leia a [grande teoria](https://docs.firefly-iii.org/about-firefly-iii/personal-finances).', + 'registered_doc_text' => 'Se ainda não o fez, por favor, leia também o guia da primeira utilização e a descrição completa.', 'registered_closing' => 'Aproveite!', 'registered_firefly_iii_link' => 'Firefly III:', 'registered_pw_reset_link' => 'Alteração da senha:', @@ -107,48 +107,48 @@ return [ // new version - 'new_version_email_subject' => 'A new Firefly III version is available', + 'new_version_email_subject' => 'Está disponível uma nova versão do Firefly III', // email change 'email_change_subject' => 'O seu endereço de e-mail do Firefly III mudou', 'email_change_body_to_new' => 'Ou você ou alguém com acesso à sua conta do Firefly III alterou o endereço de email associado. Se não estava a espera deste aviso, ignore o mesmo e apague-o.', - 'email_change_body_to_old' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this to happen, you **must** follow the "undo"-link below to protect your account!', + 'email_change_body_to_old' => 'Você, ou alguém com acesso à sua conta no Firefly III, alterou o seu endereço de email. Se não esperava que isto acontecesse, **deverá** usar a ligação "anular" abaixo para proteger a sua conta!', 'email_change_ignore' => 'Se iniciou esta mudança, pode ignorar esta mensagem sem medo.', 'email_change_old' => 'O endereço de email antigo era: :email', - 'email_change_old_strong' => 'The old email address was: **:email**', + 'email_change_old_strong' => 'O endereço de email anterior era: **:email**', 'email_change_new' => 'O novo endereço de email é: :email', - 'email_change_new_strong' => 'The new email address is: **:email**', + 'email_change_new_strong' => 'O novo endereço de email é: **:email**', 'email_change_instructions' => 'Não pode utilizar o Firefly III até confirmar esta alteração. Por favor carregue no link abaixo para confirmar a mesma.', 'email_change_undo_link' => 'Para desfazer a mudança, carregue neste link:', // OAuth token created 'oauth_created_subject' => 'Um novo cliente OAuth foi criado', - 'oauth_created_body' => 'Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It\'s labeled ":name" and has callback URL `:url`.', - 'oauth_created_explanation' => 'With this client, they can access **all** of your financial records through the Firefly III API.', - 'oauth_created_undo' => 'If this wasn\'t you, please revoke this client as soon as possible at `:url`', + 'oauth_created_body' => 'Alguém (esperemos que você) acabou de criar cliente de API OAuth no Firefly III para a sua conta de utilizador. Foi designado ":name" e tem o URL de resposta `:url`.', + 'oauth_created_explanation' => 'Com este cliente será possível aceder a **todos** os seus registos financeiros através da API do Firefly III.', + 'oauth_created_undo' => 'Se não foi você, por favor, revogue este cliente assim que for possível em `:url`', // reset password - 'reset_pw_subject' => 'O pedido de mudança de senha', - 'reset_pw_instructions' => 'Alguém acabou de tentar redefinir a sua palavra passe. Se foi você carregue no link abaixo para acabar o processo.', - 'reset_pw_warning' => '**PLEASE** verify that the link actually goes to the Firefly III you expect it to go!', + 'reset_pw_subject' => 'O seu pedido de redefinição da palavra-passe', + 'reset_pw_instructions' => 'Alguém tentou redefinir a sua palavra-passe. Se foi você, carregue no link abaixo para acabar o processo.', + 'reset_pw_warning' => '**POR FAVOR** verifique que a ligação atual vai ter ao Firefly III esperado!', // error 'error_subject' => 'Ocorreu um erro no Firefly III', 'error_intro' => 'Firefly III v:version encontrou um erro: :errorMessage.', 'error_type' => 'O erro foi do tipo ":class".', - 'error_timestamp' => 'Ocorreu um erro às: :time.', + 'error_timestamp' => 'O erro ocorreu às: :time.', 'error_location' => 'Este erro ocorreu no ficheiro ":file" na linha :line com o código :code.', 'error_user' => 'O erro foi encontrado pelo utilizador #:id, :email.', - 'error_no_user' => 'Não havia nenhum utilizador conectado para este erro ou nenhum utilizador foi detectado.', + 'error_no_user' => 'Não havia nenhum utilizador em sessão para este erro ou nenhum utilizador foi detetado.', 'error_ip' => 'O endereço de IP associado a este erro é: :ip', 'error_url' => 'O URL é: :url', - 'error_user_agent' => 'User agent: :userAgent', - 'error_stacktrace' => 'O rastreamento da pilha completo abaixo. Se acha que é um bug no Firefly III, pode reencaminhar este email para james@firefly-iii.org. -Isto pode ajudar a compor a bug que acabou de encontrar.', - 'error_github_html' => 'Se preferir, pode também abrir uma nova issue no GitHub.', - 'error_github_text' => 'Se preferir, pode também abrir uma nova issue em https://github.com/firefly-iii/firefly-iii/issues.', + 'error_user_agent' => 'Agente de utilizador: :userAgent', + 'error_stacktrace' => 'O rastreamento da pilha completo abaixo. Se acha que é um erro no Firefly III, pode reencaminhar este email para james@firefly-iii.org. +Isto pode ajudar a corrigir o erro que acabou de encontrar.', + 'error_github_html' => 'Se preferir, pode também abrir uma nova questão no GitHub.', + 'error_github_text' => 'Se preferir, pode também abrir uma nova questão em https://github.com/firefly-iii/firefly-iii/issues.', 'error_stacktrace_below' => 'O rastreamento da pilha completo é:', - 'error_headers' => 'The following headers may also be relevant:', + 'error_headers' => 'Os cabeçalhos seguintes também podem ser relevantes:', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -164,18 +164,18 @@ Isto pode ajudar a compor a bug que acabou de encontrar.', // report new journals 'new_journals_subject' => 'O Firefly III criou uma nova transação|O Firefly III criou :count novas transações', - 'new_journals_header' => 'O Firefly III criou uma transação para si. Pode encontrar a mesma na sua instância do Firefly III.|O Firefly III criou :count transações para si. Pode encontrar as mesmas na sua instância do Firefly III:', + 'new_journals_header' => 'O Firefly III criou uma transação por si. Pode encontrá-la na sua instância do Firefly III.|O Firefly III criou :count transações por si. Pode encontrá-las na sua instância do Firefly III:', // bill warning - 'bill_warning_subject_end_date' => 'Your bill ":name" is due to end in :diff days', - 'bill_warning_subject_now_end_date' => 'Your bill ":name" is due to end TODAY', - 'bill_warning_subject_extension_date' => 'Your bill ":name" is due to be extended or cancelled in :diff days', - 'bill_warning_subject_now_extension_date' => 'Your bill ":name" is due to be extended or cancelled TODAY', - 'bill_warning_end_date' => 'Your bill **":name"** is due to end on :date. This moment will pass in about **:diff days**.', - 'bill_warning_extension_date' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass in about **:diff days**.', - 'bill_warning_end_date_zero' => 'Your bill **":name"** is due to end on :date. This moment will pass **TODAY!**', - 'bill_warning_extension_date_zero' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass **TODAY!**', - 'bill_warning_please_action' => 'Please take the appropriate action.', + 'bill_warning_subject_end_date' => 'O encargo ":name" irá terminar em :diff dias', + 'bill_warning_subject_now_end_date' => 'O encargo ":name" está para terminar HOJE', + 'bill_warning_subject_extension_date' => 'O encargo ":name" deve ser prorrogado ou cancelado em :diff dias', + 'bill_warning_subject_now_extension_date' => 'O encargo ":name" deve ser prorrogado ou cancelado HOJE', + 'bill_warning_end_date' => 'O encargo ":name" deve ser pago até :date. Faltam cerca de **:diff dias**.', + 'bill_warning_extension_date' => 'O encargo ":name" deve ser prorrogado ou cancelado até :date. Faltam cerca de **:diff dias**.', + 'bill_warning_end_date_zero' => 'O encargo **:name** deve ser pago em :date. Esse dia é **HOJE!**', + 'bill_warning_extension_date_zero' => 'O encargo **:name** deve ser prorrogado ou cancelado no dia :date. Esse dia é **HOJE!**', + 'bill_warning_please_action' => 'Por favor, tome as medidas apropriadas.', ]; /* diff --git a/resources/lang/pt_PT/errors.php b/resources/lang/pt_PT/errors.php index 5c08f69208..666d026f2d 100644 --- a/resources/lang/pt_PT/errors.php +++ b/resources/lang/pt_PT/errors.php @@ -36,20 +36,20 @@ declare(strict_types=1); return [ '404_header' => 'Firefly III não encontrou esta página.', - '404_page_does_not_exist' => 'A página solicitada não existe. Por favor, verifique se não inseriu a URL errada. Pode se ter enganado?', - '404_send_error' => 'Se você foi redirecionado para esta página automaticamente, por favor aceite as minhas desculpas. Há uma referência a este erro nos seus ficheiros de registo e ficaria muito agradecido se me pudesse enviar.', - '404_github_link' => 'Se você tem certeza de que esta página existe, abra um ticket no GitHub.', + '404_page_does_not_exist' => 'A página solicitada não existe. Por favor, verifique se não inseriu o URL errado. Talvez se tenha enganado a digitar?', + '404_send_error' => 'Se foi redirecionado para esta página automaticamente, por favor, aceite as minhas desculpas. Há uma referência a este erro nos seus ficheiros de registo e ficaria muito agradecido se ma pudesse enviar.', + '404_github_link' => 'Se tem a certeza de que esta página existe, abra um ticket no GitHub.', 'whoops' => 'Oops', - 'fatal_error' => 'Aconteceu um erro fatal. Por favor verifique os ficheiros de log em "storage/logs" ou use "docker logs -f [container]" para verificar o que se passa.', + 'fatal_error' => 'Aconteceu um erro fatal. Por favor, verifique os ficheiros de log em "storage/logs" ou use "docker logs -f [container]" para verificar o que se passa.', 'maintenance_mode' => 'O Firefly III está em modo de manutenção.', 'be_right_back' => 'Volto já!', - 'check_back' => 'Firefly III está desligado para manutenção. Volte já a seguir.', + 'check_back' => 'Firefly III está desligado para manutenção. Por favor, passe por cá depois.', 'error_occurred' => 'Oops! Ocorreu um erro.', 'db_error_occurred' => 'Oops! Ocorreu um erro na base de dados.', 'error_not_recoverable' => 'Infelizmente, este erro não era recuperável :(. Firefly III avariou. O erro é:', 'error' => 'Erro', 'error_location' => 'O erro ocorreu no ficheiro ":file" na linha :line com o código :code.', - 'stacktrace' => 'Rasteamento da pilha', + 'stacktrace' => 'Rastreamento da pilha', 'more_info' => 'Mais informação', /* @@ -64,16 +64,16 @@ return [ */ - 'collect_info' => 'Por favor recolha mais informação na diretoria storage/logs que é onde encontra os ficheiros de log. Se estiver a utilizar Docker, utilize docker logs -f [container].', + 'collect_info' => 'Por favor, recolha mais informação na pasta storage/logs que é onde encontra os ficheiros de log. Se estiver a utilizar Docker, utilize docker logs -f [container].', 'collect_info_more' => 'Pode ler mais sobre a recolha de informação de erros em nas FAQ.', 'github_help' => 'Obter ajuda no GitHub', - 'github_instructions' => 'É mais que bem vindo a abrir uma nova issue no GitHub.', + 'github_instructions' => 'Esteja completamente à vontade em abrir uma nova questão no GitHub.', 'use_search' => 'Use a pesquisa!', 'include_info' => 'Inclua a informação da página de depuração.', - 'tell_more' => 'Diga-nos mais que "diz Whoops! no ecrã"', + 'tell_more' => 'Diga-nos mais do que "diz Oops! no ecrã"', 'include_logs' => 'Incluir relatório de erros (ver acima).', 'what_did_you_do' => 'Diga-nos o que estava a fazer.', - 'offline_header' => 'Você provavelmente está offline', + 'offline_header' => 'Provavelmente está offline', 'offline_unreachable' => 'O Firefly III está inacessível. O seu dispositivo está offline ou o servidor não está a funcionar.', 'offline_github' => 'Se tem a certeza que o seu dispositivo e o servidor estão online, por favor, abra um ticket no GitHub.', diff --git a/resources/lang/pt_PT/firefly.php b/resources/lang/pt_PT/firefly.php index db7f4238e9..622e8ac348 100644 --- a/resources/lang/pt_PT/firefly.php +++ b/resources/lang/pt_PT/firefly.php @@ -88,52 +88,52 @@ return [ 'new_transfer' => 'Nova transferência', 'new_transfers' => 'Nova transferência', 'new_asset_account' => 'Nova conta de ativos', - 'new_expense_account' => 'Nova conta de ativos', + 'new_expense_account' => 'Nova conta de gastos', 'new_revenue_account' => 'Nova conta de receitas', 'new_liabilities_account' => 'Novo passivo', 'new_budget' => 'Novo orçamento', - 'new_bill' => 'Nova fatura', - 'block_account_logout' => 'Foi desconectado. Contas bloqueadas não podem utilizar o website. Já se registou com um e-mail válido?', + 'new_bill' => 'Nova despesa', + 'block_account_logout' => 'A sua sessão foi terminada. Contas bloqueadas não podem utilizar este site. Já se registou com um email válido?', 'flash_success' => 'Sucesso!', 'flash_info' => 'Mensagem', 'flash_warning' => 'Aviso!', 'flash_error' => 'Erro!', 'flash_danger' => 'Perigo!', - 'flash_info_multiple' => 'Tens 1 mensagem|Tens :count mensagens', - 'flash_error_multiple' => 'Tens 1 erro|Tens :count erros', - 'net_worth' => 'Património liquido', - 'help_for_this_page' => 'Ajuda para esta pagina', + 'flash_info_multiple' => 'Tem uma mensagem|Tem :count mensagens', + 'flash_error_multiple' => 'Há um erro|Há :count erros', + 'net_worth' => 'Posição global', + 'help_for_this_page' => 'Ajuda para esta página', 'help_for_this_page_body' => 'Pode encontrar mais informações sobre esta página na documentação.', 'two_factor_welcome' => 'Olá!', - 'two_factor_enter_code' => 'Para continuar, por favor introduza o código da sua autenticação de 2 passos. A sua aplicação pode gera-lo para si.', + 'two_factor_enter_code' => 'Para continuar, por favor introduza o código da sua autenticação de 2 passos. A sua aplicação pode gerá-lo para si.', 'two_factor_code_here' => 'Introduza o código aqui', 'two_factor_title' => 'Autenticação de 2 passos', 'authenticate' => 'Autenticar', 'two_factor_forgot_title' => 'Perda da autenticação 2 passos', - 'two_factor_forgot' => 'Esqueci-me do meu código da autenticação de 2 passos.', + 'two_factor_forgot' => 'Esqueci-me da minha cena de 2 passos.', 'two_factor_lost_header' => 'Perdeu a sua autenticação de 2 passos?', - 'two_factor_lost_intro' => 'Se também perdu os códigos de backup, está com azar. Não é algo que possa resolver pela interface web. Têm duas alternativas.', + 'two_factor_lost_intro' => 'Se também perdeu os códigos de backup, está com azar. Não é algo que se possa resolver pela interface web. Têm duas alternativas.', 'two_factor_lost_fix_self' => 'Se executar sua própria instância do Firefly III, leia esta linha no FAQ para instruções.', - 'two_factor_lost_fix_owner' => 'Caso contrario, envie um email ao dono da plataforma(:site_owner) e pede-lhe para reiniciar a tua autenticação de 2 passos.', - 'mfa_backup_code' => 'Utilizou um código de backup para aceder ao Firefly III. Ele não pode ser utilizado novamente, é melhor risca-lo na sua lista.', + 'two_factor_lost_fix_owner' => 'Caso contrario, envie um email ao dono da plataforma, :site_owner e peça-lhe para reiniciar a sua autenticação de 2 passos.', + 'mfa_backup_code' => 'Utilizou um código de backup para aceder ao Firefly III. Esse código não pode ser utilizado novamente, é melhor riscá-lo da sua lista.', 'pref_two_factor_new_backup_codes' => 'Obter novos códigos de backup', 'pref_two_factor_backup_code_count' => 'Tem :count código de backup válido.|Tem :count códigos de backup válidos.', '2fa_i_have_them' => 'Guardei-os!', - 'warning_much_data' => ':days dias de dados pode demorar.', + 'warning_much_data' => ':days dias de dados podem demorar a carregar.', 'registered' => 'Registou-se com sucesso!', 'Default asset account' => 'Conta de ativos principal', - 'no_budget_pointer' => 'Parece que ainda não tem orçamentos. Pode criar-los na página de orçamentos. Orçamentos podem ajudá-lo a controlar as despesas.', - 'no_bill_pointer' => 'Parece que ainda não tem faturas. Pode criar-las na página de faturas. Faturas podem ajudá-lo a controlar as despesas.', + 'no_budget_pointer' => 'Parece que ainda não tem orçamentos. Pode criá-los na página de orçamentos. Os orçamentos podem ajudá-lo a controlar as despesas.', + 'no_bill_pointer' => 'Parece que ainda não tem Despesas. Pode criá-las na página de despesas. As Despesas podem ajudá-lo a controlar os gastos.', 'Savings account' => 'Conta poupança', - 'Credit card' => 'Cartão de credito', + 'Credit card' => 'Cartão de crédito', 'source_accounts' => 'Conta de origem|Contas de origem', 'destination_accounts' => 'Conta de destino|Contas de destino', - 'user_id_is' => 'O teu id de utilizador: :user', + 'user_id_is' => 'O seu id de utilizador: :user', 'field_supports_markdown' => 'Este campo suporta Formatacao de Texto.', - 'need_more_help' => 'Se precisares de mais ajuda para usar o Firefly III, por favor abre um ticket no Github.', - 'reenable_intro_text' => 'Também pode reactivar o guia de introdução.', + 'need_more_help' => 'Se precisar de mais ajuda para usar o Firefly III, por favor abra um ticket no Github.', + 'reenable_intro_text' => 'Também pode reativar o guia de introdução.', 'intro_boxes_after_refresh' => 'Os tutoriais de introdução vão aparecer quando atualizar a página.', - 'show_all_no_filter' => 'Mostrar todas as transações sem agrupar as mesmas por data.', + 'show_all_no_filter' => 'Mostrar todas as transações sem as agrupar por data.', 'expenses_by_category' => 'Despesas por categoria', 'expenses_by_budget' => 'Despesas por orçamento', 'income_by_category' => 'Receitas por categoria', @@ -147,23 +147,23 @@ return [ 'spent_in_specific_double' => 'Gasto na conta ":account"', 'earned_in_specific_double' => 'Ganho na conta ":account"', 'source_account' => 'Conta de origem', - 'source_account_reconciliation' => 'Não pode editar a conta de origem de uma transacção de reconciliação.', + 'source_account_reconciliation' => 'Não pode editar a conta de origem de uma transação de reconciliação.', 'destination_account' => 'Conta de destino', - 'destination_account_reconciliation' => 'Não pode editar a conta de destino de uma transacção de reconciliação.', + 'destination_account_reconciliation' => 'Não pode editar a conta de destino de uma transação de reconciliação.', 'sum_of_expenses_in_budget' => 'Total gasto no orçamento ":budget"', 'left_in_budget_limit' => 'Restante para gastar com base no orçamentado', 'current_period' => 'Período atual', 'show_the_current_period_and_overview' => 'Mostrar o período atual e a visão geral', - 'pref_languages_locale' => 'Para que um idioma diferente do inglês funcione correctamente, o sistema operativo deve utilizar as definições locais corretas. Se eles não estiverem presentes, dados de moeda, datas e valores podem estar formatados incorrectamente.', - 'budget_in_period' => 'Todas as transacções para o orçamento ":name" entre :start e :end em :currency', - 'chart_budget_in_period' => 'Gráfico para todas as transacções do orçamento ":name" entre :start e :end em :currency', - 'chart_budget_in_period_only_currency' => 'O valor orçamentado foi em :currency, por isso o gráfico apenas irá-lhe mostrar transações em :currency.', - 'chart_account_in_period' => 'Gráfico para todas as transacções da conta ":name" (:balance) entre :start e :end', - 'chart_category_in_period' => 'Gráfico para todas as transacções da categoria ":name" entre :start e :end', - 'chart_category_all' => 'Gráfico para todas as transacções da categoria ":name"', - 'clone_withdrawal' => 'Duplicar este levantamento', - 'clone_deposit' => 'Duplicar este depósito', - 'clone_transfer' => 'Duplicar esta transferência', + 'pref_languages_locale' => 'Para que um idioma diferente do inglês funcione corretamente, o sistema operativo deve utilizar as definições locais corretas. Se eles não estiverem presentes, dados de moeda, datas e valores podem estar formatados incorretamente.', + 'budget_in_period' => 'Todas as transações para o orçamento ":name" entre :start e :end em :currency', + 'chart_budget_in_period' => 'Gráfico para todas as transações do orçamento ":name" entre :start e :end em :currency', + 'chart_budget_in_period_only_currency' => 'O valor orçamentado foi em :currency, por isso o gráfico apenas irá mostrar transações em :currency.', + 'chart_account_in_period' => 'Gráfico para todas as transações da conta ":name" (:balance) entre :start e :end', + 'chart_category_in_period' => 'Gráfico para todas as transações da categoria ":name" entre :start e :end', + 'chart_category_all' => 'Gráfico para todas as transações da categoria ":name"', + 'clone_withdrawal' => 'Clonar este levantamento', + 'clone_deposit' => 'Clonar este depósito', + 'clone_transfer' => 'Clonar esta transferência', 'multi_select_no_selection' => 'Nenhum selecionado', 'multi_select_select_all' => 'Selecionar todos', 'multi_select_n_selected' => 'selecionados', @@ -183,9 +183,9 @@ return [ 'journals_in_period_for_account' => 'Todas as transações da conta :name entre :start e :end', 'journals_in_period_for_account_js' => 'Todas as transações para a conta {title} entre {start} e {end}', 'transferred' => 'Transferido', - 'all_withdrawal' => 'Todas as despesas', + 'all_withdrawal' => 'Todos os gastos', 'all_transactions' => 'Todas as transações', - 'title_withdrawal_between' => 'Todas as despesas entre :start e :end', + 'title_withdrawal_between' => 'Todos os gastos entre :start e :end', 'all_deposit' => 'Todas as receitas', 'title_deposit_between' => 'Todas as receitas entre :start e :end', 'all_transfers' => 'Todas as transferências', @@ -199,52 +199,52 @@ return [ 'journals_in_period_for_category' => 'Todas as transacções da categoria :name entre :start e :end', 'journals_in_period_for_tag' => 'Todas as transações da etiqueta :tag entre :start e :end', 'not_available_demo_user' => 'A funcionalidade que tentou aceder não está disponível para utilizadores demo.', - 'exchange_rate_instructions' => 'A conta de activos "@name" apenas aceita transacções em @native_currency. Se prefer usar @foreign_currency em vez disso, tem que inserir o valor em @native_currency também:', - 'transfer_exchange_rate_instructions' => 'A conta de activos de origem "@source_name" apenas aceita transacções em @source_currency. A conta de activos de destino "@dest_name" apenas aceita transacções em @dest_currency. Tem que fornecer o valor transferido correctamente em ambas as moedas.', + 'exchange_rate_instructions' => 'A conta de ativos "@name" apenas aceita transações em @native_currency. Se preferir usar @foreign_currency, tem de inserir o valor em @native_currency também:', + 'transfer_exchange_rate_instructions' => 'A conta de ativos de origem "@source_name" apenas aceita transações em @source_currency. A conta de ativos de destino "@dest_name" apenas aceita transações em @dest_currency. Tem de fornecer o valor transferido corretamente em ambas as moedas.', 'transaction_data' => 'Data de Transação', - 'invalid_server_configuration' => 'Configuracao de servidor invalida', - 'invalid_locale_settings' => 'O Firefly III é incapaz de formatar quantidades monetárias porque o seu servidor têm em falta os pacotes necessários para tal. Existem instruções para resolver isto.', + 'invalid_server_configuration' => 'Configuração de servidor inválida', + 'invalid_locale_settings' => 'O Firefly III é incapaz de formatar quantidades monetárias porque o seu servidor tem em falta os pacotes necessários. Existem instruções para resolver isto.', 'quickswitch' => 'Troca rápida', - 'sign_in_to_start' => 'Regista-te para iniciar sessao', - 'sign_in' => 'Iniciar sessao', + 'sign_in_to_start' => 'Registe-se para iniciar sessão', + 'sign_in' => 'Iniciar sessão', 'register_new_account' => 'Registar nova conta', - 'forgot_my_password' => 'Esqueci-me da password', + 'forgot_my_password' => 'Esqueci-me da palavra-passe', 'problems_with_input' => 'Existem alguns problemas com o valor introduzido.', - 'reset_password' => 'Redefinir a password', - 'button_reset_password' => 'Redefinir a password', + 'reset_password' => 'Redefinir a palavra-passe', + 'button_reset_password' => 'Redefinir a palavra-passe', 'reset_button' => 'Redefinir', - 'want_to_login' => 'Fazer login', - 'login_page_title' => 'Login no Firefly III', + 'want_to_login' => 'Quero iniciar sessão', + 'login_page_title' => 'Iniciar sessão no Firefly III', 'register_page_title' => 'Registar no Firefly III', - 'forgot_pw_page_title' => 'Esqueceste a password do Firefly III', - 'reset_pw_page_title' => 'Reiniciar a password do Firefly III', - 'cannot_reset_demo_user' => 'Nao podes reiniciar a password do utilizador de demonstracao.', + 'forgot_pw_page_title' => 'Esqueceu a palavra-passe do Firefly III', + 'reset_pw_page_title' => 'Reiniciar a palavra-passe do Firefly III', + 'cannot_reset_demo_user' => 'Não pode reiniciar a palavra-passe do utilizador de demonstração.', 'no_att_demo_user' => 'O utilizador demo não pode enviar anexos.', 'button_register' => 'Registar', - 'authorization' => 'Autorizacao', - 'active_bills_only' => 'apenas faturas ativas', - 'active_bills_only_total' => 'todas as faturas ativas', + 'authorization' => 'Autorização', + 'active_bills_only' => 'apenas despesas ativas', + 'active_bills_only_total' => 'todas as despesas ativas', 'active_exp_bills_only' => 'apenas faturas ativas e esperadas', - 'active_exp_bills_only_total' => 'todas as faturas ativas e esperadas', + 'active_exp_bills_only_total' => 'todas as despesas esperadas ativas', 'per_period_sum_1D' => 'Previsão de custos diários', 'per_period_sum_1W' => 'Previsão de custos semanais', 'per_period_sum_1M' => 'Previsão de custos mensais', 'per_period_sum_3M' => 'Custos trimestrais esperados', 'per_period_sum_6M' => 'Previsão de custos semestrais', 'per_period_sum_1Y' => 'Previsão de custos anuais', - 'average_per_bill' => 'média por fatura', + 'average_per_bill' => 'média por despesa', 'expected_total' => 'total esperado', - 'reconciliation_account_name' => ':name Reconciliação (:currency)', + 'reconciliation_account_name' => ':name reconciliação (:currency)', 'saved' => 'Guardado', 'advanced_options' => 'Opções avançadas', - 'advanced_options_explain' => 'Algumas páginas no Firefly III têm opções avançadas escondidas atrás deste botão. Esta página não tem nada chique aqui, mas veja as outras!', + 'advanced_options_explain' => 'Algumas páginas no Firefly III têm opções avançadas escondidas atrás deste botão. Esta página não tem nada sofisticado aqui, mas veja as outras!', 'here_be_dragons' => 'Hic sunt dracones', // Webhooks 'webhooks' => 'Webhooks', 'webhooks_breadcrumb' => 'Webhooks', 'no_webhook_messages' => 'Não existem mensagens novas', - 'webhook_trigger_STORE_TRANSACTION' => 'Ao criar transação', + 'webhook_trigger_STORE_TRANSACTION' => 'Após criar transação', 'webhook_trigger_UPDATE_TRANSACTION' => 'Ao atualizar transação', 'webhook_trigger_DESTROY_TRANSACTION' => 'Ao eliminar transação', 'webhook_response_TRANSACTIONS' => 'Detalhes da transação', @@ -404,7 +404,7 @@ return [ 'search_modifier_notes_starts' => 'As notas da transacção começam com ":value"', 'search_modifier_not_notes_starts' => 'As notas da transação não começam com ":value"', 'search_modifier_notes_ends' => 'As notas da transacção acabam com ":value"', - 'search_modifier_not_notes_ends' => 'The transaction notes do not end with ":value"', + 'search_modifier_not_notes_ends' => 'As notas da transação não terminam em ":value"', 'search_modifier_notes_is' => 'As notas da transacção são exactamente ":value"', 'search_modifier_not_notes_is' => 'As notas da transação não são exatamente ":value"', 'search_modifier_no_notes' => 'A transacção não pode tem notas', @@ -420,61 +420,61 @@ return [ 'search_modifier_source_account_is' => 'O nome da conta de origem é exactamente ":value"', 'search_modifier_not_source_account_is' => 'A conta de origem não é ":value"', 'search_modifier_source_account_contains' => 'O nome da conta de origem contém ":value"', - 'search_modifier_not_source_account_contains' => 'Source account name does not contain ":value"', + 'search_modifier_not_source_account_contains' => 'A conta de origem não contém ":value"', 'search_modifier_source_account_starts' => 'Nome da conta de origem começa com ":value"', - 'search_modifier_not_source_account_starts' => 'Source account name does not start with ":value"', + 'search_modifier_not_source_account_starts' => 'O nome da conta de origem não começa com ":value"', 'search_modifier_source_account_ends' => 'O nome da conta de origem acaba com ":value"', - 'search_modifier_not_source_account_ends' => 'Source account name does not end with ":value"', + 'search_modifier_not_source_account_ends' => 'O nome da conta de origem não termina em ":value"', 'search_modifier_source_account_id' => 'ID da conta de origem é :value', - 'search_modifier_not_source_account_id' => 'Source account ID is not :value', + 'search_modifier_not_source_account_id' => 'O ID da conta de origem não é :value', 'search_modifier_source_account_nr_is' => 'Número da conta de origem (IBAN) é ":value"', - 'search_modifier_not_source_account_nr_is' => 'Source account number (IBAN) is not ":value"', + 'search_modifier_not_source_account_nr_is' => 'O número (IBAN) da conta de origem não é ":value"', 'search_modifier_source_account_nr_contains' => 'Número da conta de origem (IBAN) contém ":value"', - 'search_modifier_not_source_account_nr_contains' => 'Source account number (IBAN) does not contain ":value"', + 'search_modifier_not_source_account_nr_contains' => 'O número (IBAN) da conta de origem não contém ":value"', 'search_modifier_source_account_nr_starts' => 'Número da conta de origem (IBAN) começa com ":value"', - 'search_modifier_not_source_account_nr_starts' => 'Source account number (IBAN) does not start with ":value"', - 'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends on ":value"', - 'search_modifier_not_source_account_nr_ends' => 'Source account number (IBAN) does not end on ":value"', + 'search_modifier_not_source_account_nr_starts' => 'O número (IBAN) da conta de origem não começa com ":value"', + 'search_modifier_source_account_nr_ends' => 'O número (IBAN) da conta de origem termina em ":value"', + 'search_modifier_not_source_account_nr_ends' => 'O número (IBAN) da conta de origem não termina em ":value"', 'search_modifier_destination_account_is' => 'O nome da conta de destino é exactamente ":value"', - 'search_modifier_not_destination_account_is' => 'Destination account name is not ":value"', + 'search_modifier_not_destination_account_is' => 'O nome da conta de destino não é ":value"', 'search_modifier_destination_account_contains' => 'Nome da conta de destino contém ":value"', - 'search_modifier_not_destination_account_contains' => 'Destination account name does not contain ":value"', + 'search_modifier_not_destination_account_contains' => 'O nome da conta de destino não contém ":value"', 'search_modifier_destination_account_starts' => 'O nome da conta de destino começa com ":value"', - 'search_modifier_not_destination_account_starts' => 'Destination account name does not start with ":value"', - 'search_modifier_destination_account_ends' => 'Destination account name ends on ":value"', - 'search_modifier_not_destination_account_ends' => 'Destination account name does not end on ":value"', + 'search_modifier_not_destination_account_starts' => 'O nome da conta de destino não começa com ":value"', + 'search_modifier_destination_account_ends' => 'O nome da conta de destino termina em ":value"', + 'search_modifier_not_destination_account_ends' => 'O nome da conta de destino não termina em ":value"', 'search_modifier_destination_account_id' => 'ID da conta de destino é :value', - 'search_modifier_not_destination_account_id' => 'Destination account ID is not :value', - 'search_modifier_destination_is_cash' => 'Destination account is the "(cash)" account', - 'search_modifier_not_destination_is_cash' => 'Destination account is not the "(cash)" account', - 'search_modifier_source_is_cash' => 'Source account is the "(cash)" account', - 'search_modifier_not_source_is_cash' => 'Source account is not the "(cash)" account', + 'search_modifier_not_destination_account_id' => 'O ID da conta de destino não é :value', + 'search_modifier_destination_is_cash' => 'A conta de destino é a conta "(caixa)"', + 'search_modifier_not_destination_is_cash' => 'A conta de destino não é a conta "(caixa)"', + 'search_modifier_source_is_cash' => 'A conta de origem é a conta "(caixa)"', + 'search_modifier_not_source_is_cash' => 'A conta de origem não é a conta "(caixa)"', 'search_modifier_destination_account_nr_is' => 'Número da conta de destino (IBAN) é ":value"', - 'search_modifier_not_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"', + 'search_modifier_not_destination_account_nr_is' => 'O número (IBAN) da conta de destino é ":value"', 'search_modifier_destination_account_nr_contains' => 'O número da conta de destino (IBAN) contém ":value"', - 'search_modifier_not_destination_account_nr_contains' => 'Destination account number (IBAN) does not contain ":value"', + 'search_modifier_not_destination_account_nr_contains' => 'O número (IBAN) da conta de destino não contém ":value"', 'search_modifier_destination_account_nr_starts' => 'Número da conta de destino (IBAN) começa com ":value"', - 'search_modifier_not_destination_account_nr_starts' => 'Destination account number (IBAN) does not start with ":value"', + 'search_modifier_not_destination_account_nr_starts' => 'O número (IBAN) da conta de destino não começa com ":value"', 'search_modifier_destination_account_nr_ends' => 'Número da conta de destino (IBAN) acaba com ":value"', - 'search_modifier_not_destination_account_nr_ends' => 'Destination account number (IBAN) does not end with ":value"', + 'search_modifier_not_destination_account_nr_ends' => 'O número (IBAN) da conta de destino não termina com ":value"', 'search_modifier_account_id' => 'O ID da conta de origem ou destino é/são: :value', - 'search_modifier_not_account_id' => 'Source or destination account ID\'s is/are not: :value', + 'search_modifier_not_account_id' => 'As ID\'s das contas de origem ou destino não são: :value', 'search_modifier_category_is' => 'A categoria é ":value"', - 'search_modifier_not_category_is' => 'Category is not ":value"', + 'search_modifier_not_category_is' => 'A categoria não é ":value"', 'search_modifier_budget_is' => 'O orçamento é ":value"', - 'search_modifier_not_budget_is' => 'Budget is not ":value"', + 'search_modifier_not_budget_is' => 'O orçamento não é ":value"', 'search_modifier_bill_is' => 'A fatura é ":value"', - 'search_modifier_not_bill_is' => 'Bill is not ":value"', + 'search_modifier_not_bill_is' => 'A fatura não é ":value"', 'search_modifier_transaction_type' => 'Tipo de transacção é ":value"', - 'search_modifier_not_transaction_type' => 'Transaction type is not ":value"', + 'search_modifier_not_transaction_type' => 'O tipo de transação não é ":value"', 'search_modifier_tag_is' => 'A etiqueta é ":value"', - 'search_modifier_not_tag_is' => 'No tag is ":value"', + 'search_modifier_not_tag_is' => 'Nenhuma etiqueta é ":value"', 'search_modifier_date_on_year' => 'A transação é do ano ":value"', - 'search_modifier_not_date_on_year' => 'Transaction is not in year ":value"', + 'search_modifier_not_date_on_year' => 'A transação não é no ano ":value"', 'search_modifier_date_on_month' => 'A transação é do mês ":value"', - 'search_modifier_not_date_on_month' => 'Transaction is not in month ":value"', + 'search_modifier_not_date_on_month' => 'A transação não é no mês ":value"', 'search_modifier_date_on_day' => 'A transação é no dia do mês ":value"', - 'search_modifier_not_date_on_day' => 'Transaction is not on day of month ":value"', + 'search_modifier_not_date_on_day' => 'A transação não é no dia do mês ":value"', 'search_modifier_date_before_year' => 'A transação é antes ou no ano ":value"', 'search_modifier_date_before_month' => 'A transação é antes ou no mês ":value"', 'search_modifier_date_before_day' => 'A transação é antes ou no dia do mês ":value"', @@ -485,37 +485,37 @@ return [ // new 'search_modifier_tag_is_not' => 'Nenhuma etiqueta é ":value"', - 'search_modifier_not_tag_is_not' => 'Tag is ":value"', + 'search_modifier_not_tag_is_not' => 'A etiqueta é ":value"', 'search_modifier_account_is' => 'Qualquer das contas é ":value"', - 'search_modifier_not_account_is' => 'Neither account is ":value"', + 'search_modifier_not_account_is' => 'Nenhuma das contas é ":value"', 'search_modifier_account_contains' => 'Qualquer das contas contêm ":value"', - 'search_modifier_not_account_contains' => 'Neither account contains ":value"', + 'search_modifier_not_account_contains' => 'Nenhuma das contas contém ":value"', 'search_modifier_account_ends' => 'Qualquer das contas acaba com ":value"', - 'search_modifier_not_account_ends' => 'Neither account ends with ":value"', + 'search_modifier_not_account_ends' => 'Nenhuma das contas termina com ":value"', 'search_modifier_account_starts' => 'Qualquer das contas começa com ":value"', - 'search_modifier_not_account_starts' => 'Neither account starts with ":value"', + 'search_modifier_not_account_starts' => 'Nenhuma das contas começa com ":value"', 'search_modifier_account_nr_is' => 'Qualquer um dos números de conta/IBAN é ":value"', - 'search_modifier_not_account_nr_is' => 'Neither account number / IBAN is ":value"', + 'search_modifier_not_account_nr_is' => 'Nenhum número / IBAN de conta é ":value"', 'search_modifier_account_nr_contains' => 'Qualquer um dos números de conta/IBAN contêm ":value"', - 'search_modifier_not_account_nr_contains' => 'Neither account number / IBAN contains ":value"', + 'search_modifier_not_account_nr_contains' => 'Nenhum número / IBAN de conta contém ":value"', 'search_modifier_account_nr_ends' => 'Qualquer um dos números de conta/IBAN acaba em ":value"', - 'search_modifier_not_account_nr_ends' => 'Neither account number / IBAN ends with ":value"', + 'search_modifier_not_account_nr_ends' => 'Nenhum número / IBAN de conta termina com ":value"', 'search_modifier_account_nr_starts' => 'Qualquer um dos números de conta/IBAN começa com ":value"', - 'search_modifier_not_account_nr_starts' => 'Neither account number / IBAN starts with ":value"', + 'search_modifier_not_account_nr_starts' => 'Nenhum número / IBAN de conta começa com ":value"', 'search_modifier_category_contains' => 'A categoria contém ":value"', - 'search_modifier_not_category_contains' => 'Category does not contain ":value"', - 'search_modifier_category_ends' => 'Category ends on ":value"', - 'search_modifier_not_category_ends' => 'Category does not end on ":value"', + 'search_modifier_not_category_contains' => 'A categoria não contém ":value"', + 'search_modifier_category_ends' => 'A categoria termina em ":value"', + 'search_modifier_not_category_ends' => 'A categoria não termina em ":value"', 'search_modifier_category_starts' => 'A categoria começa com ":value"', - 'search_modifier_not_category_starts' => 'Category does not start with ":value"', + 'search_modifier_not_category_starts' => 'A categoria não começa com ":value"', 'search_modifier_budget_contains' => 'O orçamento contém ":value"', - 'search_modifier_not_budget_contains' => 'Budget does not contain ":value"', + 'search_modifier_not_budget_contains' => 'O orçamento não contém ":value"', 'search_modifier_budget_ends' => 'O orçamento acaba com ":value"', - 'search_modifier_not_budget_ends' => 'Budget does not end on ":value"', + 'search_modifier_not_budget_ends' => 'O orçamento não termina em ":value"', 'search_modifier_budget_starts' => 'O orçamento começa com ":value"', - 'search_modifier_not_budget_starts' => 'Budget does not start with ":value"', + 'search_modifier_not_budget_starts' => 'O orçamento não começa com ":value"', 'search_modifier_bill_contains' => 'A fatura contêm ":value"', - 'search_modifier_not_bill_contains' => 'Bill does not contain ":value"', + 'search_modifier_not_bill_contains' => 'A fatura não contém ":value"', 'search_modifier_bill_ends' => 'A fatura termina com ":value"', 'search_modifier_not_bill_ends' => 'A fatura não termina em ":value"', 'search_modifier_bill_starts' => 'A fatura começa com ":value"', @@ -571,141 +571,141 @@ return [ 'search_modifier_interest_date_before_month' => 'A data de juros da transação é anterior ou no mês de :value', 'search_modifier_interest_date_before_day' => 'A data de juros da transação é anterior ou no dia do mês :value', 'search_modifier_interest_date_after_year' => 'A data de juros da transação é posterior ou no ano :value', - 'search_modifier_interest_date_after_month' => 'Transaction interest date is after or in month ":value"', - 'search_modifier_interest_date_after_day' => 'Transaction interest date is after or on day of month ":value"', - 'search_modifier_book_date_on_year' => 'Transaction book date is in year ":value"', - 'search_modifier_book_date_on_month' => 'Transaction book date is in month ":value"', - 'search_modifier_book_date_on_day' => 'Transaction book date is on day of month ":value"', - 'search_modifier_not_book_date_on_year' => 'Transaction book date is not in year ":value"', - 'search_modifier_not_book_date_on_month' => 'Transaction book date is not in month ":value"', - 'search_modifier_not_book_date_on_day' => 'Transaction book date is not on day of month ":value"', - 'search_modifier_book_date_before_year' => 'Transaction book date is before or in year ":value"', - 'search_modifier_book_date_before_month' => 'Transaction book date is before or in month ":value"', - 'search_modifier_book_date_before_day' => 'Transaction book date is before or on day of month ":value"', - 'search_modifier_book_date_after_year' => 'Transaction book date is after or in year ":value"', - 'search_modifier_book_date_after_month' => 'Transaction book date is after or in month ":value"', - 'search_modifier_book_date_after_day' => 'Transaction book date is after or on day of month ":value"', - 'search_modifier_process_date_on_year' => 'Transaction process date is in year ":value"', - 'search_modifier_process_date_on_month' => 'Transaction process date is in month ":value"', - 'search_modifier_process_date_on_day' => 'Transaction process date is on day of month ":value"', - 'search_modifier_not_process_date_on_year' => 'Transaction process date is not in year ":value"', - 'search_modifier_not_process_date_on_month' => 'Transaction process date is not in month ":value"', - 'search_modifier_not_process_date_on_day' => 'Transaction process date is not on day of month ":value"', - 'search_modifier_process_date_before_year' => 'Transaction process date is before or in year ":value"', - 'search_modifier_process_date_before_month' => 'Transaction process date is before or in month ":value"', - 'search_modifier_process_date_before_day' => 'Transaction process date is before or on day of month ":value"', - 'search_modifier_process_date_after_year' => 'Transaction process date is after or in year ":value"', - 'search_modifier_process_date_after_month' => 'Transaction process date is after or in month ":value"', - 'search_modifier_process_date_after_day' => 'Transaction process date is after or on day of month ":value"', - 'search_modifier_due_date_on_year' => 'Transaction due date is in year ":value"', - 'search_modifier_due_date_on_month' => 'Transaction due date is in month ":value"', - 'search_modifier_due_date_on_day' => 'Transaction due date is on day of month ":value"', - 'search_modifier_not_due_date_on_year' => 'Transaction due date is not in year ":value"', - 'search_modifier_not_due_date_on_month' => 'Transaction due date is not in month ":value"', - 'search_modifier_not_due_date_on_day' => 'Transaction due date is not on day of month ":value"', - 'search_modifier_due_date_before_year' => 'Transaction due date is before or in year ":value"', - 'search_modifier_due_date_before_month' => 'Transaction due date is before or in month ":value"', - 'search_modifier_due_date_before_day' => 'Transaction due date is before or on day of month ":value"', - 'search_modifier_due_date_after_year' => 'Transaction due date is after or in year ":value"', - 'search_modifier_due_date_after_month' => 'Transaction due date is after or in month ":value"', - 'search_modifier_due_date_after_day' => 'Transaction due date is after or on day of month ":value"', - 'search_modifier_payment_date_on_year' => 'Transaction payment date is in year ":value"', - 'search_modifier_payment_date_on_month' => 'Transaction payment date is in month ":value"', - 'search_modifier_payment_date_on_day' => 'Transaction payment date is on day of month ":value"', - 'search_modifier_not_payment_date_on_year' => 'Transaction payment date is not in year ":value"', - 'search_modifier_not_payment_date_on_month' => 'Transaction payment date is not in month ":value"', - 'search_modifier_not_payment_date_on_day' => 'Transaction payment date is not on day of month ":value"', - 'search_modifier_payment_date_before_year' => 'Transaction payment date is before or in year ":value"', - 'search_modifier_payment_date_before_month' => 'Transaction payment date is before or in month ":value"', - 'search_modifier_payment_date_before_day' => 'Transaction payment date is before or on day of month ":value"', - 'search_modifier_payment_date_after_year' => 'Transaction payment date is after or in year ":value"', - 'search_modifier_payment_date_after_month' => 'Transaction payment date is after or in month ":value"', - 'search_modifier_payment_date_after_day' => 'Transaction payment date is after or on day of month ":value"', - 'search_modifier_invoice_date_on_year' => 'Transaction invoice date is in year ":value"', - 'search_modifier_invoice_date_on_month' => 'Transaction invoice date is in month ":value"', - 'search_modifier_invoice_date_on_day' => 'Transaction invoice date is on day of month ":value"', - 'search_modifier_not_invoice_date_on_year' => 'Transaction invoice date is not in year ":value"', - 'search_modifier_not_invoice_date_on_month' => 'Transaction invoice date is not in month ":value"', - 'search_modifier_not_invoice_date_on_day' => 'Transaction invoice date is not on day of month ":value"', - 'search_modifier_invoice_date_before_year' => 'Transaction invoice date is before or in year ":value"', - 'search_modifier_invoice_date_before_month' => 'Transaction invoice date is before or in month ":value"', - 'search_modifier_invoice_date_before_day' => 'Transaction invoice date is before or on day of month ":value"', - 'search_modifier_invoice_date_after_year' => 'Transaction invoice date is after or in year ":value"', - 'search_modifier_invoice_date_after_month' => 'Transaction invoice date is after or in month ":value"', - 'search_modifier_invoice_date_after_day' => 'Transaction invoice date is after or on day of month ":value"', + 'search_modifier_interest_date_after_month' => 'A data de juros da transação é no mês ":value" ou posterior', + 'search_modifier_interest_date_after_day' => 'A data de juros da transação é no dia do mês ":value" ou posterior', + 'search_modifier_book_date_on_year' => 'A data da transação é no ano ":value"', + 'search_modifier_book_date_on_month' => 'A data da transação é no mês ":value"', + 'search_modifier_book_date_on_day' => 'A data da transação é no dia do mês ":value"', + 'search_modifier_not_book_date_on_year' => 'A data da transação não é no ano ":value"', + 'search_modifier_not_book_date_on_month' => 'A data da transação não é no mês ":value"', + 'search_modifier_not_book_date_on_day' => 'A data da transação não é no dia do mês ":value"', + 'search_modifier_book_date_before_year' => 'A data da transação é no ano ":value" ou anterior', + 'search_modifier_book_date_before_month' => 'A data da transação é no mês ":value" ou anterior', + 'search_modifier_book_date_before_day' => 'A data da transação é no dia do mês ":value" ou anterior', + 'search_modifier_book_date_after_year' => 'A data da transação é no ano ":value" ou posterior', + 'search_modifier_book_date_after_month' => 'A data da transação é no mês ":value" ou posterior', + 'search_modifier_book_date_after_day' => 'A data da transação é no dia do mês ":value" ou posterior', + 'search_modifier_process_date_on_year' => 'A data de processamento da transação é no ano ":value"', + 'search_modifier_process_date_on_month' => 'A data de processamento da transação é no mês ":value"', + 'search_modifier_process_date_on_day' => 'A data de processamento da transação é no dia do mês ":value"', + 'search_modifier_not_process_date_on_year' => 'A data de processamento da transação não é no ano ":value"', + 'search_modifier_not_process_date_on_month' => 'A data de processamento da transação não é no mês ":value"', + 'search_modifier_not_process_date_on_day' => 'A data de processamento da transação não é no dia do ":value"', + 'search_modifier_process_date_before_year' => 'A data de processamento da transação é no ano ":value" ou anterior', + 'search_modifier_process_date_before_month' => 'A data de processamento da transação é no mês ":value" ou anterior', + 'search_modifier_process_date_before_day' => 'A data de processamento da transação é no dia do mês ":value" ou anterior', + 'search_modifier_process_date_after_year' => 'A data de processamento da transação é no ano ":value" ou posterior', + 'search_modifier_process_date_after_month' => 'A data de processamento da transação é no mês ":value" ou posterior', + 'search_modifier_process_date_after_day' => 'A data de processamento da transação é no dia do mês ":value" ou posterior', + 'search_modifier_due_date_on_year' => 'A data de vencimento da transação é no ano ":value"', + 'search_modifier_due_date_on_month' => 'A data de vencimento da transação é no mês ":value"', + 'search_modifier_due_date_on_day' => 'A data de vencimento da transação é no dia do mês ":value"', + 'search_modifier_not_due_date_on_year' => 'A data de vencimento da transação não é no ano ":value"', + 'search_modifier_not_due_date_on_month' => 'A data de vencimento da transação não é no mês ":value"', + 'search_modifier_not_due_date_on_day' => 'A data de vencimento da transação não é no dia do ":value"', + 'search_modifier_due_date_before_year' => 'A data de vencimento da transação é no ano ":value" ou anterior', + 'search_modifier_due_date_before_month' => 'A data de vencimento da transação é no mês ":value" ou anterior', + 'search_modifier_due_date_before_day' => 'A data de vencimento da transação é no dia do mês ":value" ou anterior', + 'search_modifier_due_date_after_year' => 'A data de vencimento da transação é no ano ":value" ou posterior', + 'search_modifier_due_date_after_month' => 'A data de vencimento da transação é no mês ":value" ou posterior', + 'search_modifier_due_date_after_day' => 'A data de vencimento da transação é no dia do mês ":value" ou posterior', + 'search_modifier_payment_date_on_year' => 'A data de pagamento da transação é no ano ":value"', + 'search_modifier_payment_date_on_month' => 'A data de pagamento da transação é no mês ":value"', + 'search_modifier_payment_date_on_day' => 'A data de pagamento da transação é no dia do mês ":value"', + 'search_modifier_not_payment_date_on_year' => 'A data de pagamento da transação não é no ano ":value"', + 'search_modifier_not_payment_date_on_month' => 'A data de pagamento da transação não é no mês ":value"', + 'search_modifier_not_payment_date_on_day' => 'A data de pagamento da transação não é no dia do mês ":value"', + 'search_modifier_payment_date_before_year' => 'A data de pagamento da transação é no ano ":value" ou anterior', + 'search_modifier_payment_date_before_month' => 'A data de pagamento da transação é no mês ":value" ou anterior', + 'search_modifier_payment_date_before_day' => 'A data de pagamento da transação é no dia do mês ":value" ou anterior', + 'search_modifier_payment_date_after_year' => 'A data de pagamento da transação é no ano ":value" ou posterior', + 'search_modifier_payment_date_after_month' => 'A data de pagamento da transação é no mês ":value" ou posterior', + 'search_modifier_payment_date_after_day' => 'A data de pagamento da transação é no dia do mês ":value" ou posterior', + 'search_modifier_invoice_date_on_year' => 'A data da fatura da transação é no ano ":value"', + 'search_modifier_invoice_date_on_month' => 'A data da fatura da transação é no mês ":value"', + 'search_modifier_invoice_date_on_day' => 'A data da fatura da transação é no dia do mês ":value"', + 'search_modifier_not_invoice_date_on_year' => 'A data da fatura da transação não é no ano ":value"', + 'search_modifier_not_invoice_date_on_month' => 'A data da fatura da transação não é no mês ":value"', + 'search_modifier_not_invoice_date_on_day' => 'A data da fatura da transação não é no dia do mês ":value"', + 'search_modifier_invoice_date_before_year' => 'A data da fatura da transação não é anterior ou no ano ":value"', + 'search_modifier_invoice_date_before_month' => 'A data da fatura da transação não é anterior ou no mês ":value"', + 'search_modifier_invoice_date_before_day' => 'A data da fatura da transação não é anterior ou no dia do mês ":value"', + 'search_modifier_invoice_date_after_year' => 'A data da fatura da transação não é posterior ou no ano ":value"', + 'search_modifier_invoice_date_after_month' => 'A data da fatura da transação não é posterior ou no mês ":value"', + 'search_modifier_invoice_date_after_day' => 'A data da fatura da transação não é posterior ou no dia do mês ":value"', // other dates - 'search_modifier_updated_at_on_year' => 'Transaction was last updated in year ":value"', - 'search_modifier_updated_at_on_month' => 'Transaction was last updated in month ":value"', - 'search_modifier_updated_at_on_day' => 'Transaction was last updated on day of month ":value"', - 'search_modifier_not_updated_at_on_year' => 'Transaction was not last updated in year ":value"', - 'search_modifier_not_updated_at_on_month' => 'Transaction was not last updated in month ":value"', - 'search_modifier_not_updated_at_on_day' => 'Transaction was not last updated on day of month ":value"', - 'search_modifier_updated_at_before_year' => 'Transaction was last updated in or before year ":value"', - 'search_modifier_updated_at_before_month' => 'Transaction was last updated in or before month ":value"', - 'search_modifier_updated_at_before_day' => 'Transaction was last updated on or before day of month ":value"', - 'search_modifier_updated_at_after_year' => 'Transaction was last updated in or after year ":value"', - 'search_modifier_updated_at_after_month' => 'Transaction was last updated in or after month ":value"', - 'search_modifier_updated_at_after_day' => 'Transaction was last updated on or after day of month ":value"', - 'search_modifier_created_at_on_year' => 'Transaction was created in year ":value"', - 'search_modifier_created_at_on_month' => 'Transaction was created in month ":value"', - 'search_modifier_created_at_on_day' => 'Transaction was created on day of month ":value"', - 'search_modifier_not_created_at_on_year' => 'Transaction was not created in year ":value"', - 'search_modifier_not_created_at_on_month' => 'Transaction was not created in month ":value"', - 'search_modifier_not_created_at_on_day' => 'Transaction was not created on day of month ":value"', - 'search_modifier_created_at_before_year' => 'Transaction was created in or before year ":value"', - 'search_modifier_created_at_before_month' => 'Transaction was created in or before month ":value"', - 'search_modifier_created_at_before_day' => 'Transaction was created on or before day of month ":value"', - 'search_modifier_created_at_after_year' => 'Transaction was created in or after year ":value"', - 'search_modifier_created_at_after_month' => 'Transaction was created in or after month ":value"', - 'search_modifier_created_at_after_day' => 'Transaction was created on or after day of month ":value"', - 'search_modifier_interest_date_before' => 'Transaction interest date is on or before ":value"', - 'search_modifier_interest_date_after' => 'Transaction interest date is on or after ":value"', - 'search_modifier_book_date_on' => 'Transaction book date is on ":value"', - 'search_modifier_not_book_date_on' => 'Transaction book date is not on ":value"', - 'search_modifier_book_date_before' => 'Transaction book date is on or before ":value"', - 'search_modifier_book_date_after' => 'Transaction book date is on or after ":value"', - 'search_modifier_process_date_on' => 'Transaction process date is on ":value"', - 'search_modifier_not_process_date_on' => 'Transaction process date is not on ":value"', - 'search_modifier_process_date_before' => 'Transaction process date is on or before ":value"', - 'search_modifier_process_date_after' => 'Transaction process date is on or after ":value"', - 'search_modifier_due_date_on' => 'Transaction due date is on ":value"', - 'search_modifier_not_due_date_on' => 'Transaction due date is not on ":value"', - 'search_modifier_due_date_before' => 'Transaction due date is on or before ":value"', - 'search_modifier_due_date_after' => 'Transaction due date is on or after ":value"', - 'search_modifier_payment_date_on' => 'Transaction payment date is on ":value"', - 'search_modifier_not_payment_date_on' => 'Transaction payment date is not on ":value"', - 'search_modifier_payment_date_before' => 'Transaction payment date is on or before ":value"', - 'search_modifier_payment_date_after' => 'Transaction payment date is on or after ":value"', - 'search_modifier_invoice_date_on' => 'Transaction invoice date is on ":value"', - 'search_modifier_not_invoice_date_on' => 'Transaction invoice date is not on ":value"', - 'search_modifier_invoice_date_before' => 'Transaction invoice date is on or before ":value"', - 'search_modifier_invoice_date_after' => 'Transaction invoice date is on or after ":value"', - 'search_modifier_created_at_on' => 'Transaction was created on ":value"', - 'search_modifier_not_created_at_on' => 'Transaction was not created on ":value"', - 'search_modifier_created_at_before' => 'Transaction was created on or before ":value"', - 'search_modifier_created_at_after' => 'Transaction was created on or after ":value"', - 'search_modifier_updated_at_on' => 'Transaction was updated on ":value"', - 'search_modifier_not_updated_at_on' => 'Transaction was not updated on ":value"', - 'search_modifier_updated_at_before' => 'Transaction was updated on or before ":value"', - 'search_modifier_updated_at_after' => 'Transaction was updated on or after ":value"', + 'search_modifier_updated_at_on_year' => 'A transação foi atualizada pela última vez no ano ":value"', + 'search_modifier_updated_at_on_month' => 'A transação foi atualizada pela última vez no mês ":value"', + 'search_modifier_updated_at_on_day' => 'A transação foi atualizada pela última vez no dia do mês ":value"', + 'search_modifier_not_updated_at_on_year' => 'A transação não foi atualizada pela última vez no ano ":value"', + 'search_modifier_not_updated_at_on_month' => 'A transação não foi atualizada pela última vez no mês ":value"', + 'search_modifier_not_updated_at_on_day' => 'A transação não foi atualizada pela última vez no dia do mês ":value"', + 'search_modifier_updated_at_before_year' => 'A transação foi atualizada pela última vez antes de ou no ano ":value"', + 'search_modifier_updated_at_before_month' => 'A transação foi atualizada pela última vez antes de ou no mês ":value"', + 'search_modifier_updated_at_before_day' => 'A transação foi atualizada pela última vez antes de ou no dia do mês ":value"', + 'search_modifier_updated_at_after_year' => 'A transação foi atualizada pela última vez no ano ":value" ou posterior', + 'search_modifier_updated_at_after_month' => 'A transação foi atualizada pela última vez no mês ":value" ou posterior', + 'search_modifier_updated_at_after_day' => 'A transação foi atualizada pela última vez no dia do mês ":value" ou posterior', + 'search_modifier_created_at_on_year' => 'A transação foi criada no ano ":value"', + 'search_modifier_created_at_on_month' => 'A transação foi criada no mês ":value"', + 'search_modifier_created_at_on_day' => 'A transação foi criada no dia do mês ":value"', + 'search_modifier_not_created_at_on_year' => 'A transação não foi criada no ano ":value"', + 'search_modifier_not_created_at_on_month' => 'A transação não foi criada no mês ":value"', + 'search_modifier_not_created_at_on_day' => 'A transação não foi criada no dia do mês ":value"', + 'search_modifier_created_at_before_year' => 'A transação foi criada no ano ":value" ou anterior', + 'search_modifier_created_at_before_month' => 'A transação foi criada no mês ":value" ou anterior', + 'search_modifier_created_at_before_day' => 'A transação foi criada no dia do mês ":value" ou anterior', + 'search_modifier_created_at_after_year' => 'A transação foi criada no ano ":value" ou posterior', + 'search_modifier_created_at_after_month' => 'A transação foi criada no mês ":value" ou posterior', + 'search_modifier_created_at_after_day' => 'A transação foi criada no dia do mês ":value" ou posterior', + 'search_modifier_interest_date_before' => 'A data de juros da transação é no dia ":value" ou anterior', + 'search_modifier_interest_date_after' => 'A data de juros da transação é no dia ":value" ou posterior', + 'search_modifier_book_date_on' => 'A data da transação é ":value"', + 'search_modifier_not_book_date_on' => 'A data da transação não é ":value"', + 'search_modifier_book_date_before' => 'A data da transação é o dia ":value" ou anterior', + 'search_modifier_book_date_after' => 'A data da transação é o dia ":value" ou posterior', + 'search_modifier_process_date_on' => 'A data de processamento da transação é ":value"', + 'search_modifier_not_process_date_on' => 'A data de processamento da transação não é ":value"', + 'search_modifier_process_date_before' => 'A data de processamento da transação é ":value" ou anterior', + 'search_modifier_process_date_after' => 'A data de processamento da transação é ":value" ou posterior', + 'search_modifier_due_date_on' => 'A data de vencimento da transação é ":value"', + 'search_modifier_not_due_date_on' => 'A data de vencimento da transação não é ":value"', + 'search_modifier_due_date_before' => 'A data de vencimento da transação é ":value" ou anterior', + 'search_modifier_due_date_after' => 'A data de vencimento da transação é ":value" ou posterior', + 'search_modifier_payment_date_on' => 'A data de pagamento da transação é ":value"', + 'search_modifier_not_payment_date_on' => 'A data de pagamento da transação não é ":value"', + 'search_modifier_payment_date_before' => 'A data de pagamento da transação é no dia ":value" ou anterior', + 'search_modifier_payment_date_after' => 'A data de pagamento da transação é no dia ":value" ou posterior', + 'search_modifier_invoice_date_on' => 'A data da fatura da transação é ":value"', + 'search_modifier_not_invoice_date_on' => 'A data da fatura da transação não é ":value"', + 'search_modifier_invoice_date_before' => 'A data da fatura da transação é no dia ":value" ou anterior', + 'search_modifier_invoice_date_after' => 'A data da fatura da transação é no dia ":value" ou posterior', + 'search_modifier_created_at_on' => 'A transação foi criada em ":value"', + 'search_modifier_not_created_at_on' => 'A transação não foi criada em ":value"', + 'search_modifier_created_at_before' => 'A transação foi criada no dia ":value" ou anterior', + 'search_modifier_created_at_after' => 'A transação foi criada no dia ":value" ou posterior', + 'search_modifier_updated_at_on' => 'A transação foi atualizada em ":value"', + 'search_modifier_not_updated_at_on' => 'A transação não foi atualizada em ":value"', + 'search_modifier_updated_at_before' => 'A transação foi atualizada no dia ":value" ou anterior', + 'search_modifier_updated_at_after' => 'A transação foi atualizada no dia ":value" ou posterior', - 'search_modifier_attachment_name_is' => 'Any attachment\'s name is ":value"', - 'search_modifier_attachment_name_contains' => 'Any attachment\'s name contains ":value"', - 'search_modifier_attachment_name_starts' => 'Any attachment\'s name starts with ":value"', - 'search_modifier_attachment_name_ends' => 'Any attachment\'s name ends with ":value"', - 'search_modifier_attachment_notes_are' => 'Any attachment\'s notes are ":value"', - 'search_modifier_attachment_notes_contains' => 'Any attachment\'s notes contain ":value"', - 'search_modifier_attachment_notes_starts' => 'Any attachment\'s notes start with ":value"', - 'search_modifier_attachment_notes_ends' => 'Any attachment\'s notes end with ":value"', - 'search_modifier_not_attachment_name_is' => 'Any attachment\'s name is not ":value"', - 'search_modifier_not_attachment_name_contains' => 'Any attachment\'s name does not contain ":value"', - 'search_modifier_not_attachment_name_starts' => 'Any attachment\'s name does not start with ":value"', - 'search_modifier_not_attachment_name_ends' => 'Any attachment\'s name does not end with ":value"', - 'search_modifier_not_attachment_notes_are' => 'Any attachment\'s notes are not ":value"', - 'search_modifier_not_attachment_notes_contains' => 'Any attachment\'s notes do not contain ":value"', - 'search_modifier_not_attachment_notes_starts' => 'Any attachment\'s notes start with ":value"', - 'search_modifier_not_attachment_notes_ends' => 'Any attachment\'s notes do not end with ":value"', - 'search_modifier_sepa_ct_is' => 'SEPA CT is ":value"', + 'search_modifier_attachment_name_is' => 'Qualquer nome de anexo é ":value"', + 'search_modifier_attachment_name_contains' => 'Qualquer nome de anexo contém ":value"', + 'search_modifier_attachment_name_starts' => 'Qualquer nome de anexo começa com ":value"', + 'search_modifier_attachment_name_ends' => 'Qualquer nome de anexo termina com ":value"', + 'search_modifier_attachment_notes_are' => 'Quaisquer notas de anexo são ":value"', + 'search_modifier_attachment_notes_contains' => 'Quaisquer notas de anexo contêm ":value"', + 'search_modifier_attachment_notes_starts' => 'Quaisquer notas de anexo começam com ":value"', + 'search_modifier_attachment_notes_ends' => 'Quaisquer notas de anexo terminam com ":value"', + 'search_modifier_not_attachment_name_is' => 'Qualquer nome de anexo não é ":value"', + 'search_modifier_not_attachment_name_contains' => 'Qualquer nome de anexo não contém ":value"', + 'search_modifier_not_attachment_name_starts' => 'Qualquer nome de anexo não começa com ":value"', + 'search_modifier_not_attachment_name_ends' => 'Qualquer nome de anexo não termina com ":value"', + 'search_modifier_not_attachment_notes_are' => 'Quaisquer notas de anexo não são ":value"', + 'search_modifier_not_attachment_notes_contains' => 'Quaisquer notas de anexo não contêm ":value"', + 'search_modifier_not_attachment_notes_starts' => 'Quaisquer notas de anexo não começam com ":value"', + 'search_modifier_not_attachment_notes_ends' => 'Quaisquer notas de anexo não terminam com ":value"', + 'search_modifier_sepa_ct_is' => 'SEPA TC é ":value"', 'update_rule_from_query' => 'Atualizar regra ":rule" da pesquisa', 'create_rule_from_query' => 'Criar nova regra a partir da pesquisa', 'rule_from_search_words' => 'O mecanismo de regras tem dificuldade com ":string". A regra sugerida que se encaixa na pesquisa pode mostrar resultados diferentes. Por favor, verifique os gatilhos das regras cuidadosamente.', @@ -745,7 +745,7 @@ return [ 'yearly' => 'anual', // rules - 'is_not_rule_trigger' => 'Not', + 'is_not_rule_trigger' => 'Não', 'cannot_fire_inactive_rules' => 'Você não pode executar regras inactivas.', 'rules' => 'Regras', 'rule_name' => 'Nome da regra', @@ -824,14 +824,14 @@ return [ 'rule_trigger_source_account_is' => 'O nome da conta de origem é ":trigger_value"', 'rule_trigger_source_account_contains_choice' => 'Nome da conta de origem contém..', 'rule_trigger_source_account_contains' => 'Nome da conta de origem contém ":trigger_value"', - 'rule_trigger_account_id_choice' => 'Either account ID is exactly..', - 'rule_trigger_account_id' => 'Either account ID is exactly :trigger_value', + 'rule_trigger_account_id_choice' => 'Qualquer ID de conta é exatamente..', + 'rule_trigger_account_id' => 'Qualquer ID de conta é exatamente :trigger_value', 'rule_trigger_source_account_id_choice' => 'O ID da conta de origem é exatamente..', 'rule_trigger_source_account_id' => 'O ID da conta de origem é exactamente :trigger_value', 'rule_trigger_destination_account_id_choice' => 'O ID da conta de destino é exatamente..', 'rule_trigger_destination_account_id' => 'O ID da conta de destino é exactamente :trigger_value', - 'rule_trigger_account_is_cash_choice' => 'Either account is cash', - 'rule_trigger_account_is_cash' => 'Either account is cash', + 'rule_trigger_account_is_cash_choice' => 'Qualquer uma das contas é de caixa', + 'rule_trigger_account_is_cash' => 'Qualquer uma das contas é de caixa', 'rule_trigger_source_is_cash_choice' => 'A conta de origem é uma conta (dinheiro)', 'rule_trigger_source_is_cash' => 'A conta de origem é uma conta (dinheiro)', 'rule_trigger_destination_is_cash_choice' => 'A conta de destino é uma conta (dinheiro)', @@ -867,7 +867,7 @@ return [ 'rule_trigger_amount_less_choice' => 'O montante é menos de..', 'rule_trigger_amount_less' => 'Quantia é menor que :trigger_value', 'rule_trigger_amount_is_choice' => 'O montante é..', - 'rule_trigger_amount_is' => 'Amount is :trigger_value', + 'rule_trigger_amount_is' => 'O montante é :trigger_value', 'rule_trigger_amount_more_choice' => 'O montante é maior que..', 'rule_trigger_amount_more' => 'Quantia é maior que :trigger_value', 'rule_trigger_description_starts_choice' => 'A descricao comeca com..', @@ -878,20 +878,20 @@ return [ 'rule_trigger_description_contains' => 'Descrição contém ":trigger_value"', 'rule_trigger_description_is_choice' => 'A descrição é..', 'rule_trigger_description_is' => 'Descrição é ":trigger_value"', - 'rule_trigger_date_on_choice' => 'Transaction date is..', - 'rule_trigger_date_on' => 'Transaction date is ":trigger_value"', + 'rule_trigger_date_on_choice' => 'A data da transação é..', + 'rule_trigger_date_on' => 'A data da transação é ":trigger_value"', 'rule_trigger_date_before_choice' => 'Data de transacção é anterior..', 'rule_trigger_date_before' => 'Data da transacção é antes de ":trigger_value"', 'rule_trigger_date_after_choice' => 'Data da transacção é após..', 'rule_trigger_date_after' => 'Data da transacção é após ":trigger_value"', - 'rule_trigger_created_at_on_choice' => 'Transaction was made on..', - 'rule_trigger_created_at_on' => 'Transaction was made on ":trigger_value"', - 'rule_trigger_updated_at_on_choice' => 'Transaction was last edited on..', - 'rule_trigger_updated_at_on' => 'Transaction was last edited on ":trigger_value"', + 'rule_trigger_created_at_on_choice' => 'A transação foi realizada em..', + 'rule_trigger_created_at_on' => 'A transação foi realizada em ":trigger_value"', + 'rule_trigger_updated_at_on_choice' => 'A transação foi editada pela última vez em..', + 'rule_trigger_updated_at_on' => 'A transação foi editada pela última vez em ":trigger_value"', 'rule_trigger_budget_is_choice' => 'O orçamento é..', 'rule_trigger_budget_is' => 'O orçamento é ":trigger_value"', - 'rule_trigger_tag_is_choice' => 'Any tag is..', - 'rule_trigger_tag_is' => 'Any tag is ":trigger_value"', + 'rule_trigger_tag_is_choice' => 'Uma etiqueta é..', + 'rule_trigger_tag_is' => 'Qualquer etiqueta é ":trigger_value"', 'rule_trigger_currency_is_choice' => 'A moeda da transação é..', 'rule_trigger_currency_is' => 'A moeda da transacção é ":trigger_value"', 'rule_trigger_foreign_currency_is_choice' => 'A moeda estrangeira da transacção é..', @@ -918,292 +918,292 @@ return [ 'rule_trigger_any_notes' => 'A transacção tem notas (quaisquer)', 'rule_trigger_no_notes_choice' => 'Não tem notas', 'rule_trigger_no_notes' => 'A transacção não tem notas', - 'rule_trigger_notes_is_choice' => 'Notes are..', - 'rule_trigger_notes_is' => 'Notes are ":trigger_value"', - 'rule_trigger_notes_contains_choice' => 'Notes contain..', - 'rule_trigger_notes_contains' => 'Notes contain ":trigger_value"', - 'rule_trigger_notes_starts_choice' => 'Notes start with..', - 'rule_trigger_notes_starts' => 'Notes start with ":trigger_value"', - 'rule_trigger_notes_ends_choice' => 'Notes end with..', - 'rule_trigger_notes_ends' => 'Notes end with ":trigger_value"', + 'rule_trigger_notes_is_choice' => 'As notas são..', + 'rule_trigger_notes_is' => 'As notas são ":trigger_value"', + 'rule_trigger_notes_contains_choice' => 'As notas contêm..', + 'rule_trigger_notes_contains' => 'As notas contêm ":trigger_value"', + 'rule_trigger_notes_starts_choice' => 'As notas comecam com..', + 'rule_trigger_notes_starts' => 'As notas começam com ":trigger_value"', + 'rule_trigger_notes_ends_choice' => 'As notas terminam com..', + 'rule_trigger_notes_ends' => 'Notas acabam com ":trigger_value"', 'rule_trigger_bill_is_choice' => 'A fatura é..', 'rule_trigger_bill_is' => 'A fatura é ":trigger_value"', 'rule_trigger_external_id_is_choice' => 'O ID Externo é..', - 'rule_trigger_external_id_is' => 'External ID is ":trigger_value"', - 'rule_trigger_internal_reference_is_choice' => 'Internal reference is..', - 'rule_trigger_internal_reference_is' => 'Internal reference is ":trigger_value"', + 'rule_trigger_external_id_is' => 'O ID externo é ":trigger_value"', + 'rule_trigger_internal_reference_is_choice' => 'A referência interna é..', + 'rule_trigger_internal_reference_is' => 'A referência interna é ":trigger_value"', 'rule_trigger_journal_id_choice' => 'O ID do diário de transações é..', 'rule_trigger_journal_id' => 'O ID do diário de transações é ":trigger_value"', - 'rule_trigger_no_external_url' => 'Transaction has no external URL', - 'rule_trigger_any_external_url' => 'Transaction has an external URL', - 'rule_trigger_any_external_url_choice' => 'Transaction has an external URL', - 'rule_trigger_no_external_url_choice' => 'Transaction has no external URL', + 'rule_trigger_no_external_url' => 'A transação não tem nenhum URL externo', + 'rule_trigger_any_external_url' => 'A transação tem um URL externo', + 'rule_trigger_any_external_url_choice' => 'A transação tem um URL externo', + 'rule_trigger_no_external_url_choice' => 'A transação não tem um URL externo', 'rule_trigger_id_choice' => 'ID da transação é..', - 'rule_trigger_id' => 'Transaction ID is ":trigger_value"', - 'rule_trigger_sepa_ct_is_choice' => 'SEPA CT is..', - 'rule_trigger_sepa_ct_is' => 'SEPA CT is ":trigger_value"', + 'rule_trigger_id' => 'O ID da transação é ":trigger_value"', + 'rule_trigger_sepa_ct_is_choice' => 'SEPA CT é..', + 'rule_trigger_sepa_ct_is' => 'SEPA TC é ":trigger_value"', // new values: - 'rule_trigger_user_action_choice' => 'User action is ":trigger_value"', - 'rule_trigger_tag_is_not_choice' => 'No tag is..', - 'rule_trigger_tag_is_not' => 'No tag is ":trigger_value"', - 'rule_trigger_account_is_choice' => 'Either account is exactly..', - 'rule_trigger_account_is' => 'Either account is exactly ":trigger_value"', - 'rule_trigger_account_contains_choice' => 'Either account contains..', - 'rule_trigger_account_contains' => 'Either account contains ":trigger_value"', - 'rule_trigger_account_ends_choice' => 'Either account ends with..', - 'rule_trigger_account_ends' => 'Either account ends with ":trigger_value"', - 'rule_trigger_account_starts_choice' => 'Either account starts with..', - 'rule_trigger_account_starts' => 'Either account starts with ":trigger_value"', - 'rule_trigger_account_nr_is_choice' => 'Either account number / IBAN is..', - 'rule_trigger_account_nr_is' => 'Either account number / IBAN is ":trigger_value"', - 'rule_trigger_account_nr_contains_choice' => 'Either account number / IBAN contains..', - 'rule_trigger_account_nr_contains' => 'Either account number / IBAN contains ":trigger_value"', - 'rule_trigger_account_nr_ends_choice' => 'Either account number / IBAN ends with..', - 'rule_trigger_account_nr_ends' => 'Either account number / IBAN ends with ":trigger_value"', - 'rule_trigger_account_nr_starts_choice' => 'Either account number / IBAN starts with..', - 'rule_trigger_account_nr_starts' => 'Either account number / IBAN starts with ":trigger_value"', - 'rule_trigger_category_contains_choice' => 'Category contains..', - 'rule_trigger_category_contains' => 'Category contains ":trigger_value"', - 'rule_trigger_category_ends_choice' => 'Category ends with..', - 'rule_trigger_category_ends' => 'Category ends with ":trigger_value"', - 'rule_trigger_category_starts_choice' => 'Category starts with..', - 'rule_trigger_category_starts' => 'Category starts with ":trigger_value"', + 'rule_trigger_user_action_choice' => 'A ação de utilizador é ":trigger_value"', + 'rule_trigger_tag_is_not_choice' => 'Nenhuma etiqueta é..', + 'rule_trigger_tag_is_not' => 'Nenhuma etiqueta é ":trigger_value"', + 'rule_trigger_account_is_choice' => 'Qualquer das contas é exatamente..', + 'rule_trigger_account_is' => 'Qualquer das contas é exatamente ":trigger_value"', + 'rule_trigger_account_contains_choice' => 'Qualquer das contas contém..', + 'rule_trigger_account_contains' => 'Qualquer das contas contém ":trigger_value"', + 'rule_trigger_account_ends_choice' => 'Qualquer uma das contas termina com..', + 'rule_trigger_account_ends' => 'Qualquer uma das contas termina com ":trigger_value"', + 'rule_trigger_account_starts_choice' => 'Qualquer uma das contas começa com..', + 'rule_trigger_account_starts' => 'Qualquer uma das contas começa com ":trigger_value"', + 'rule_trigger_account_nr_is_choice' => 'Qualquer um dos números de conta / IBAN é..', + 'rule_trigger_account_nr_is' => 'Qualquer um dos números de conta / IBAN ":trigger_value"', + 'rule_trigger_account_nr_contains_choice' => 'Qualquer um dos números de conta / IBAN contém..', + 'rule_trigger_account_nr_contains' => 'Qualquer um dos números de conta / IBAN contém ":trigger_value"', + 'rule_trigger_account_nr_ends_choice' => 'Qualquer um dos números de conta / IBAN termina com..', + 'rule_trigger_account_nr_ends' => 'Qualquer um dos números de conta / IBAN termina com ":trigger_value"', + 'rule_trigger_account_nr_starts_choice' => 'Qualquer um dos números de conta / IBAN começa com..', + 'rule_trigger_account_nr_starts' => 'Qualquer um dos números de conta / IBAN começa com ":trigger_value"', + 'rule_trigger_category_contains_choice' => 'A categoria contém..', + 'rule_trigger_category_contains' => 'A categoria contém ":trigger_value"', + 'rule_trigger_category_ends_choice' => 'A categoria termina com..', + 'rule_trigger_category_ends' => 'A categoria termina com ":trigger_value"', + 'rule_trigger_category_starts_choice' => 'A categoria começa com..', + 'rule_trigger_category_starts' => 'A categoria começa com ":trigger_value"', 'rule_trigger_budget_contains_choice' => 'O orçamento contém..', - 'rule_trigger_budget_contains' => 'Budget contains ":trigger_value"', - 'rule_trigger_budget_ends_choice' => 'Budget ends with..', - 'rule_trigger_budget_ends' => 'Budget ends with ":trigger_value"', - 'rule_trigger_budget_starts_choice' => 'Budget starts with..', - 'rule_trigger_budget_starts' => 'Budget starts with ":trigger_value"', - 'rule_trigger_bill_contains_choice' => 'Bill contains..', - 'rule_trigger_bill_contains' => 'Bill contains ":trigger_value"', - 'rule_trigger_bill_ends_choice' => 'Bill ends with..', - 'rule_trigger_bill_ends' => 'Bill ends with ":trigger_value"', - 'rule_trigger_bill_starts_choice' => 'Bill starts with..', - 'rule_trigger_bill_starts' => 'Bill starts with ":trigger_value"', - 'rule_trigger_external_id_contains_choice' => 'External ID contains..', - 'rule_trigger_external_id_contains' => 'External ID contains ":trigger_value"', - 'rule_trigger_external_id_ends_choice' => 'External ID ends with..', - 'rule_trigger_external_id_ends' => 'External ID ends with ":trigger_value"', - 'rule_trigger_external_id_starts_choice' => 'External ID starts with..', - 'rule_trigger_external_id_starts' => 'External ID starts with ":trigger_value"', - 'rule_trigger_internal_reference_contains_choice' => 'Internal reference contains..', - 'rule_trigger_internal_reference_contains' => 'Internal reference contains ":trigger_value"', - 'rule_trigger_internal_reference_ends_choice' => 'Internal reference ends with..', - 'rule_trigger_internal_reference_ends' => 'Internal reference ends with ":trigger_value"', - 'rule_trigger_internal_reference_starts_choice' => 'Internal reference starts with..', - 'rule_trigger_internal_reference_starts' => 'Internal reference starts with ":trigger_value"', - 'rule_trigger_external_url_is_choice' => 'External URL is..', - 'rule_trigger_external_url_is' => 'External URL is ":trigger_value"', - 'rule_trigger_external_url_contains_choice' => 'External URL contains..', - 'rule_trigger_external_url_contains' => 'External URL contains ":trigger_value"', - 'rule_trigger_external_url_ends_choice' => 'External URL ends with..', - 'rule_trigger_external_url_ends' => 'External URL ends with ":trigger_value"', - 'rule_trigger_external_url_starts_choice' => 'External URL starts with..', - 'rule_trigger_external_url_starts' => 'External URL starts with ":trigger_value"', - 'rule_trigger_has_no_attachments_choice' => 'Has no attachments', - 'rule_trigger_has_no_attachments' => 'Transaction has no attachments', - 'rule_trigger_recurrence_id_choice' => 'Recurring transaction ID is..', - 'rule_trigger_recurrence_id' => 'Recurring transaction ID is ":trigger_value"', - 'rule_trigger_interest_date_on_choice' => 'Interest date is on..', - 'rule_trigger_interest_date_on' => 'Interest date is on ":trigger_value"', - 'rule_trigger_interest_date_before_choice' => 'Interest date is before..', - 'rule_trigger_interest_date_before' => 'Interest date is before ":trigger_value"', - 'rule_trigger_interest_date_after_choice' => 'Interest date is after..', - 'rule_trigger_interest_date_after' => 'Interest date is after ":trigger_value"', - 'rule_trigger_book_date_on_choice' => 'Book date is on..', - 'rule_trigger_book_date_on' => 'Book date is on ":trigger_value"', - 'rule_trigger_book_date_before_choice' => 'Book date is before..', - 'rule_trigger_book_date_before' => 'Book date is before ":trigger_value"', - 'rule_trigger_book_date_after_choice' => 'Book date is after..', - 'rule_trigger_book_date_after' => 'Book date is after ":trigger_value"', - 'rule_trigger_process_date_on_choice' => 'Process date is on..', - 'rule_trigger_process_date_on' => 'Process date is ":trigger_value"', - 'rule_trigger_process_date_before_choice' => 'Process date is before..', - 'rule_trigger_process_date_before' => 'Process date is before ":trigger_value"', - 'rule_trigger_process_date_after_choice' => 'Process date is after..', - 'rule_trigger_process_date_after' => 'Process date is after ":trigger_value"', - 'rule_trigger_due_date_on_choice' => 'Due date is on..', - 'rule_trigger_due_date_on' => 'Due date is on ":trigger_value"', - 'rule_trigger_due_date_before_choice' => 'Due date is before..', - 'rule_trigger_due_date_before' => 'Due date is before ":trigger_value"', - 'rule_trigger_due_date_after_choice' => 'Due date is after..', - 'rule_trigger_due_date_after' => 'Due date is after ":trigger_value"', - 'rule_trigger_payment_date_on_choice' => 'Payment date is on..', - 'rule_trigger_payment_date_on' => 'Payment date is on ":trigger_value"', - 'rule_trigger_payment_date_before_choice' => 'Payment date is before..', - 'rule_trigger_payment_date_before' => 'Payment date is before ":trigger_value"', - 'rule_trigger_payment_date_after_choice' => 'Payment date is after..', - 'rule_trigger_payment_date_after' => 'Payment date is after ":trigger_value"', - 'rule_trigger_invoice_date_on_choice' => 'Invoice date is on..', - 'rule_trigger_invoice_date_on' => 'Invoice date is on ":trigger_value"', - 'rule_trigger_invoice_date_before_choice' => 'Invoice date is before..', - 'rule_trigger_invoice_date_before' => 'Invoice date is before ":trigger_value"', - 'rule_trigger_invoice_date_after_choice' => 'Invoice date is after..', - 'rule_trigger_invoice_date_after' => 'Invoice date is after ":trigger_value"', - 'rule_trigger_created_at_before_choice' => 'Transaction was created before..', - 'rule_trigger_created_at_before' => 'Transaction was created before ":trigger_value"', - 'rule_trigger_created_at_after_choice' => 'Transaction was created after..', - 'rule_trigger_created_at_after' => 'Transaction was created after ":trigger_value"', - 'rule_trigger_updated_at_before_choice' => 'Transaction was last updated before..', - 'rule_trigger_updated_at_before' => 'Transaction was last updated before ":trigger_value"', - 'rule_trigger_updated_at_after_choice' => 'Transaction was last updated after..', - 'rule_trigger_updated_at_after' => 'Transaction was last updated after ":trigger_value"', - 'rule_trigger_foreign_amount_is_choice' => 'Foreign amount is exactly..', - 'rule_trigger_foreign_amount_is' => 'Foreign amount is exactly ":trigger_value"', - 'rule_trigger_foreign_amount_less_choice' => 'Foreign amount is less than..', - 'rule_trigger_foreign_amount_less' => 'Foreign amount is less than ":trigger_value"', - 'rule_trigger_foreign_amount_more_choice' => 'Foreign amount is more than..', - 'rule_trigger_foreign_amount_more' => 'Foreign amount is more than ":trigger_value"', - 'rule_trigger_attachment_name_is_choice' => 'Any attachment\'s name is..', - 'rule_trigger_attachment_name_is' => 'Any attachment\'s name is ":trigger_value"', - 'rule_trigger_attachment_name_contains_choice' => 'Any attachment\'s name contains..', - 'rule_trigger_attachment_name_contains' => 'Any attachment\'s name contains ":trigger_value"', - 'rule_trigger_attachment_name_starts_choice' => 'Any attachment\'s name starts with..', - 'rule_trigger_attachment_name_starts' => 'Any attachment\'s name starts with ":trigger_value"', - 'rule_trigger_attachment_name_ends_choice' => 'Any attachment\'s name ends with..', - 'rule_trigger_attachment_name_ends' => 'Any attachment\'s name ends with ":trigger_value"', - 'rule_trigger_attachment_notes_are_choice' => 'Any attachment\'s notes are..', - 'rule_trigger_attachment_notes_are' => 'Any attachment\'s notes are ":trigger_value"', - 'rule_trigger_attachment_notes_contains_choice' => 'Any attachment\'s notes contain..', - 'rule_trigger_attachment_notes_contains' => 'Any attachment\'s notes contain ":trigger_value"', - 'rule_trigger_attachment_notes_starts_choice' => 'Any attachment\'s notes start with..', - 'rule_trigger_attachment_notes_starts' => 'Any attachment\'s notes start with ":trigger_value"', - 'rule_trigger_attachment_notes_ends_choice' => 'Any attachment\'s notes end with..', - 'rule_trigger_attachment_notes_ends' => 'Any attachment\'s notes end with ":trigger_value"', - 'rule_trigger_reconciled_choice' => 'Transaction is reconciled', - 'rule_trigger_reconciled' => 'Transaction is reconciled', - 'rule_trigger_exists_choice' => 'Any transaction matches(!)', - 'rule_trigger_exists' => 'Any transaction matches', + 'rule_trigger_budget_contains' => 'O orçamento contém ":trigger_value"', + 'rule_trigger_budget_ends_choice' => 'O orçamento termina com..', + 'rule_trigger_budget_ends' => 'O orçamento termina com ":trigger_value"', + 'rule_trigger_budget_starts_choice' => 'O orçamento começa com..', + 'rule_trigger_budget_starts' => 'O orçamento começa com ":trigger_value"', + 'rule_trigger_bill_contains_choice' => 'A fatura contém..', + 'rule_trigger_bill_contains' => 'A fatura contém ":trigger_value"', + 'rule_trigger_bill_ends_choice' => 'A fatura termina com..', + 'rule_trigger_bill_ends' => 'A fatura termina com ":trigger_value"', + 'rule_trigger_bill_starts_choice' => 'A fatura começa com..', + 'rule_trigger_bill_starts' => 'A fatura começa com ":trigger_value"', + 'rule_trigger_external_id_contains_choice' => 'O ID externo contém..', + 'rule_trigger_external_id_contains' => 'O ID externo contém ":trigger_value"', + 'rule_trigger_external_id_ends_choice' => 'O ID externo termina com..', + 'rule_trigger_external_id_ends' => 'O ID externo termina com ":trigger_value"', + 'rule_trigger_external_id_starts_choice' => 'O ID externo começa com..', + 'rule_trigger_external_id_starts' => 'O ID externo começa com ":trigger_value"', + 'rule_trigger_internal_reference_contains_choice' => 'A referência interna contém..', + 'rule_trigger_internal_reference_contains' => 'A referência interna contém ":trigger_value"', + 'rule_trigger_internal_reference_ends_choice' => 'A referência interna termina com..', + 'rule_trigger_internal_reference_ends' => 'Uma referência interna termina com ":trigger_value"', + 'rule_trigger_internal_reference_starts_choice' => 'Uma referência interna começa com..', + 'rule_trigger_internal_reference_starts' => 'Uma referência interna começa com ":trigger_value"', + 'rule_trigger_external_url_is_choice' => 'O URL externo é..', + 'rule_trigger_external_url_is' => 'O URL externo é ":trigger_value"', + 'rule_trigger_external_url_contains_choice' => 'O URL externo contém..', + 'rule_trigger_external_url_contains' => 'O URL externo contém ":trigger_value"', + 'rule_trigger_external_url_ends_choice' => 'O URL externo termina com..', + 'rule_trigger_external_url_ends' => 'O URL externo termina com ":trigger_value"', + 'rule_trigger_external_url_starts_choice' => 'O URL externo começa com..', + 'rule_trigger_external_url_starts' => 'O URL externo começa com ":trigger_value"', + 'rule_trigger_has_no_attachments_choice' => 'Não tem anexos', + 'rule_trigger_has_no_attachments' => 'A transação não tem anexos', + 'rule_trigger_recurrence_id_choice' => 'O ID de transação recorrente é..', + 'rule_trigger_recurrence_id' => 'O ID de transação recorrente é ":trigger_value"', + 'rule_trigger_interest_date_on_choice' => 'A data de juros é..', + 'rule_trigger_interest_date_on' => 'A data de juros é ":trigger_value"', + 'rule_trigger_interest_date_before_choice' => 'A data de juros é antes de..', + 'rule_trigger_interest_date_before' => 'A data de juros é antes de ":trigger_value"', + 'rule_trigger_interest_date_after_choice' => 'A data de juros é após..', + 'rule_trigger_interest_date_after' => 'A data de juros é após ":trigger_value"', + 'rule_trigger_book_date_on_choice' => 'A data de agendamento é..', + 'rule_trigger_book_date_on' => 'A data de agendamento é ":trigger_value"', + 'rule_trigger_book_date_before_choice' => 'A data de agendamento é anterior a..', + 'rule_trigger_book_date_before' => 'A data de agendamento é anterior a ":trigger_value"', + 'rule_trigger_book_date_after_choice' => 'A data de agendamento é posterior a..', + 'rule_trigger_book_date_after' => 'A data de agendamento é posterior a ":trigger_value"', + 'rule_trigger_process_date_on_choice' => 'A data de processamento é..', + 'rule_trigger_process_date_on' => 'A data de processamento é ":trigger_value"', + 'rule_trigger_process_date_before_choice' => 'A data de processamento é anterior a..', + 'rule_trigger_process_date_before' => 'A data de processamento é anterior a ":trigger_value"', + 'rule_trigger_process_date_after_choice' => 'A data de processamento é posterior a..', + 'rule_trigger_process_date_after' => 'A data de processamento é posterior a ":trigger_value"', + 'rule_trigger_due_date_on_choice' => 'A data de vencimento é..', + 'rule_trigger_due_date_on' => 'A data de vencimento é ":trigger_value"', + 'rule_trigger_due_date_before_choice' => 'A data de vencimento é anterior a..', + 'rule_trigger_due_date_before' => 'A data de vencimento é anterior a ":trigger_value"', + 'rule_trigger_due_date_after_choice' => 'A data de vencimento é posterior a..', + 'rule_trigger_due_date_after' => 'A data de vencimento é posterior a ":trigger_value"', + 'rule_trigger_payment_date_on_choice' => 'A data de pagamento é..', + 'rule_trigger_payment_date_on' => 'A data de pagamento é ":trigger_value"', + 'rule_trigger_payment_date_before_choice' => 'A data de pagamento é antes de..', + 'rule_trigger_payment_date_before' => 'A data de pagamento é antes de ":trigger_value"', + 'rule_trigger_payment_date_after_choice' => 'A data de pagamento é posterior a..', + 'rule_trigger_payment_date_after' => 'A data de pagamento é posterior a ":trigger_value"', + 'rule_trigger_invoice_date_on_choice' => 'A data da fatura é..', + 'rule_trigger_invoice_date_on' => 'A data da fatura é ":trigger_value"', + 'rule_trigger_invoice_date_before_choice' => 'A data da fatura é anterior a..', + 'rule_trigger_invoice_date_before' => 'A data da fatura é anterior a ":trigger_value"', + 'rule_trigger_invoice_date_after_choice' => 'A data da fatura é posterior a..', + 'rule_trigger_invoice_date_after' => 'A data da fatura é posterior a ":trigger_value"', + 'rule_trigger_created_at_before_choice' => 'A transação foi criada antes de..', + 'rule_trigger_created_at_before' => 'A transação foi criada antes de ":trigger_value"', + 'rule_trigger_created_at_after_choice' => 'A transação foi criada depois de..', + 'rule_trigger_created_at_after' => 'A transação foi criada depois de ":trigger_value"', + 'rule_trigger_updated_at_before_choice' => 'A última atualização da transação foi em..', + 'rule_trigger_updated_at_before' => 'A última atualização da transação foi em ":trigger_value"', + 'rule_trigger_updated_at_after_choice' => 'A última atualização da transação foi depois de..', + 'rule_trigger_updated_at_after' => 'A última atualização da transação foi depois de ":trigger_value"', + 'rule_trigger_foreign_amount_is_choice' => 'O montante em moeda estrangeira é exatamente..', + 'rule_trigger_foreign_amount_is' => 'O montante em moeda estrangeira é exatamente ":trigger_value"', + 'rule_trigger_foreign_amount_less_choice' => 'O montante em moeda estrangeira é inferior a..', + 'rule_trigger_foreign_amount_less' => 'O montante em moeda estrangeira é inferior a ":trigger_value"', + 'rule_trigger_foreign_amount_more_choice' => 'O montante em moeda estrangeira é superior a..', + 'rule_trigger_foreign_amount_more' => 'O montante em moeda estrangeira é superior a ":trigger_value"', + 'rule_trigger_attachment_name_is_choice' => 'O nome de qualquer anexo é..', + 'rule_trigger_attachment_name_is' => 'O nome de qualquer anexo é ":trigger_value"', + 'rule_trigger_attachment_name_contains_choice' => 'O nome de qualquer anexo contém..', + 'rule_trigger_attachment_name_contains' => 'O nome de qualquer anexo contém ":trigger_value"', + 'rule_trigger_attachment_name_starts_choice' => 'O nome de qualquer anexo começa com..', + 'rule_trigger_attachment_name_starts' => 'O nome de qualquer anexo começa com ":trigger_value"', + 'rule_trigger_attachment_name_ends_choice' => 'O nome de qualquer anexo termina com..', + 'rule_trigger_attachment_name_ends' => 'O nome de qualquer anexo termina com ":trigger_value"', + 'rule_trigger_attachment_notes_are_choice' => 'Quaisquer notas de anexo são..', + 'rule_trigger_attachment_notes_are' => 'Quaisquer notas de anexo são ":trigger_value"', + 'rule_trigger_attachment_notes_contains_choice' => 'Quaisquer notas de anexo contêm..', + 'rule_trigger_attachment_notes_contains' => 'Quaisquer notas de anexo contêm ":trigger_value"', + 'rule_trigger_attachment_notes_starts_choice' => 'Quaisquer notas de anexo começam com..', + 'rule_trigger_attachment_notes_starts' => 'Quaisquer notas de anexo começam com ":trigger_value"', + 'rule_trigger_attachment_notes_ends_choice' => 'Quaisquer notas de anexo terminam com..', + 'rule_trigger_attachment_notes_ends' => 'Quaisquer notas de anexo terminam com ":trigger_value"', + 'rule_trigger_reconciled_choice' => 'A transação está reconciliada', + 'rule_trigger_reconciled' => 'A transação está reconciliada', + 'rule_trigger_exists_choice' => 'Qualquer transação corresponde(!)', + 'rule_trigger_exists' => 'Qualquer transação corresponde', // more values for new types: - 'rule_trigger_not_account_id' => 'Account ID is not ":trigger_value"', - 'rule_trigger_not_source_account_id' => 'Source account ID is not ":trigger_value"', - 'rule_trigger_not_destination_account_id' => 'Destination account ID is not ":trigger_value"', - 'rule_trigger_not_transaction_type' => 'Transaction type is not ":trigger_value"', - 'rule_trigger_not_tag_is' => 'Tag is not ":trigger_value"', - 'rule_trigger_not_tag_is_not' => 'Tag is ":trigger_value"', - 'rule_trigger_not_description_is' => 'Description is not ":trigger_value"', - 'rule_trigger_not_description_contains' => 'Description does not contain', - 'rule_trigger_not_description_ends' => 'Description does not end with ":trigger_value"', - 'rule_trigger_not_description_starts' => 'Description does not start with ":trigger_value"', - 'rule_trigger_not_notes_is' => 'Notes are not ":trigger_value"', - 'rule_trigger_not_notes_contains' => 'Notes do not contain ":trigger_value"', - 'rule_trigger_not_notes_ends' => 'Notes do not end on ":trigger_value"', - 'rule_trigger_not_notes_starts' => 'Notes do not start with ":trigger_value"', - 'rule_trigger_not_source_account_is' => 'Source account is not ":trigger_value"', - 'rule_trigger_not_source_account_contains' => 'Source account does not contain ":trigger_value"', - 'rule_trigger_not_source_account_ends' => 'Source account does not end on ":trigger_value"', - 'rule_trigger_not_source_account_starts' => 'Source account does not start with ":trigger_value"', - 'rule_trigger_not_source_account_nr_is' => 'Source account number / IBAN is not ":trigger_value"', - 'rule_trigger_not_source_account_nr_contains' => 'Source account number / IBAN does not contain ":trigger_value"', - 'rule_trigger_not_source_account_nr_ends' => 'Source account number / IBAN does not end on ":trigger_value"', - 'rule_trigger_not_source_account_nr_starts' => 'Source account number / IBAN does not start with ":trigger_value"', - 'rule_trigger_not_destination_account_is' => 'Destination account is not ":trigger_value"', - 'rule_trigger_not_destination_account_contains' => 'Destination account does not contain ":trigger_value"', - 'rule_trigger_not_destination_account_ends' => 'Destination account does not end on ":trigger_value"', - 'rule_trigger_not_destination_account_starts' => 'Destination account does not start with ":trigger_value"', - 'rule_trigger_not_destination_account_nr_is' => 'Destination account number / IBAN is not ":trigger_value"', - 'rule_trigger_not_destination_account_nr_contains' => 'Destination account number / IBAN does not contain ":trigger_value"', - 'rule_trigger_not_destination_account_nr_ends' => 'Destination account number / IBAN does not end on ":trigger_value"', - 'rule_trigger_not_destination_account_nr_starts' => 'Destination account number / IBAN does not start with ":trigger_value"', - 'rule_trigger_not_account_is' => 'Neither account is ":trigger_value"', - 'rule_trigger_not_account_contains' => 'Neither account contains ":trigger_value"', - 'rule_trigger_not_account_ends' => 'Neither account ends on ":trigger_value"', - 'rule_trigger_not_account_starts' => 'Neither account starts with ":trigger_value"', - 'rule_trigger_not_account_nr_is' => 'Neither account number / IBAN is ":trigger_value"', - 'rule_trigger_not_account_nr_contains' => 'Neither account number / IBAN contains ":trigger_value"', - 'rule_trigger_not_account_nr_ends' => 'Neither account number / IBAN ends on ":trigger_value"', - 'rule_trigger_not_account_nr_starts' => 'Neither account number / IBAN starts with ":trigger_value"', - 'rule_trigger_not_category_is' => 'Category is not ":trigger_value"', - 'rule_trigger_not_category_contains' => 'Category does not contain ":trigger_value"', - 'rule_trigger_not_category_ends' => 'Category does not end on ":trigger_value"', - 'rule_trigger_not_category_starts' => 'Category does not start with ":trigger_value"', - 'rule_trigger_not_budget_is' => 'Budget is not ":trigger_value"', - 'rule_trigger_not_budget_contains' => 'Budget does not contain ":trigger_value"', - 'rule_trigger_not_budget_ends' => 'Budget does not end on ":trigger_value"', - 'rule_trigger_not_budget_starts' => 'Budget does not start with ":trigger_value"', - 'rule_trigger_not_bill_is' => 'Bill is not is ":trigger_value"', - 'rule_trigger_not_bill_contains' => 'Bill does not contain ":trigger_value"', - 'rule_trigger_not_bill_ends' => 'Bill does not end on ":trigger_value"', - 'rule_trigger_not_bill_starts' => 'Bill does not end with ":trigger_value"', - 'rule_trigger_not_external_id_is' => 'External ID is not ":trigger_value"', - 'rule_trigger_not_external_id_contains' => 'External ID does not contain ":trigger_value"', - 'rule_trigger_not_external_id_ends' => 'External ID does not end on ":trigger_value"', - 'rule_trigger_not_external_id_starts' => 'External ID does not start with ":trigger_value"', - 'rule_trigger_not_internal_reference_is' => 'Internal reference is not ":trigger_value"', - 'rule_trigger_not_internal_reference_contains' => 'Internal reference does not contain ":trigger_value"', - 'rule_trigger_not_internal_reference_ends' => 'Internal reference does not end on ":trigger_value"', - 'rule_trigger_not_internal_reference_starts' => 'Internal reference does not start with ":trigger_value"', - 'rule_trigger_not_external_url_is' => 'External URL is not ":trigger_value"', - 'rule_trigger_not_external_url_contains' => 'External URL does not contain ":trigger_value"', - 'rule_trigger_not_external_url_ends' => 'External URL does not end on ":trigger_value"', - 'rule_trigger_not_external_url_starts' => 'External URL does not start with ":trigger_value"', - 'rule_trigger_not_currency_is' => 'Currency is not ":trigger_value"', - 'rule_trigger_not_foreign_currency_is' => 'Foreign currency is not ":trigger_value"', - 'rule_trigger_not_id' => 'Transaction ID is not ":trigger_value"', - 'rule_trigger_not_journal_id' => 'Transaction journal ID is not ":trigger_value"', - 'rule_trigger_not_recurrence_id' => 'Recurrence ID is not ":trigger_value"', - 'rule_trigger_not_date_on' => 'Date is not on ":trigger_value"', - 'rule_trigger_not_date_before' => 'Date is not before ":trigger_value"', - 'rule_trigger_not_date_after' => 'Date is not after ":trigger_value"', - 'rule_trigger_not_interest_date_on' => 'Interest date is not on ":trigger_value"', - 'rule_trigger_not_interest_date_before' => 'Interest date is not before ":trigger_value"', - 'rule_trigger_not_interest_date_after' => 'Interest date is not after ":trigger_value"', - 'rule_trigger_not_book_date_on' => 'Book date is not on ":trigger_value"', - 'rule_trigger_not_book_date_before' => 'Book date is not before ":trigger_value"', - 'rule_trigger_not_book_date_after' => 'Book date is not after ":trigger_value"', - 'rule_trigger_not_process_date_on' => 'Process date is not on ":trigger_value"', - 'rule_trigger_not_process_date_before' => 'Process date is not before ":trigger_value"', - 'rule_trigger_not_process_date_after' => 'Process date is not after ":trigger_value"', - 'rule_trigger_not_due_date_on' => 'Due date is not on ":trigger_value"', - 'rule_trigger_not_due_date_before' => 'Due date is not before ":trigger_value"', - 'rule_trigger_not_due_date_after' => 'Due date is not after ":trigger_value"', - 'rule_trigger_not_payment_date_on' => 'Payment date is not on ":trigger_value"', - 'rule_trigger_not_payment_date_before' => 'Payment date is not before ":trigger_value"', - 'rule_trigger_not_payment_date_after' => 'Payment date is not after ":trigger_value"', - 'rule_trigger_not_invoice_date_on' => 'Invoice date is not on ":trigger_value"', - 'rule_trigger_not_invoice_date_before' => 'Invoice date is not before ":trigger_value"', - 'rule_trigger_not_invoice_date_after' => 'Invoice date is not after ":trigger_value"', - 'rule_trigger_not_created_at_on' => 'Transaction is not created on ":trigger_value"', - 'rule_trigger_not_created_at_before' => 'Transaction is not created before ":trigger_value"', - 'rule_trigger_not_created_at_after' => 'Transaction is not created after ":trigger_value"', - 'rule_trigger_not_updated_at_on' => 'Transaction is not updated on ":trigger_value"', - 'rule_trigger_not_updated_at_before' => 'Transaction is not updated before ":trigger_value"', - 'rule_trigger_not_updated_at_after' => 'Transaction is not updated after ":trigger_value"', - 'rule_trigger_not_amount_is' => 'Transaction amount is not ":trigger_value"', - 'rule_trigger_not_amount_less' => 'Transaction amount is more than ":trigger_value"', - 'rule_trigger_not_amount_more' => 'Transaction amount is less than ":trigger_value"', - 'rule_trigger_not_foreign_amount_is' => 'Foreign transaction amount is not ":trigger_value"', - 'rule_trigger_not_foreign_amount_less' => 'Foreign transaction amount is more than ":trigger_value"', - 'rule_trigger_not_foreign_amount_more' => 'Foreign transaction amount is less than ":trigger_value"', - 'rule_trigger_not_attachment_name_is' => 'No attachment is named ":trigger_value"', - 'rule_trigger_not_attachment_name_contains' => 'No attachment name contains ":trigger_value"', - 'rule_trigger_not_attachment_name_starts' => 'No attachment name starts with ":trigger_value"', - 'rule_trigger_not_attachment_name_ends' => 'No attachment name ends on ":trigger_value"', - 'rule_trigger_not_attachment_notes_are' => 'No attachment notes are ":trigger_value"', - 'rule_trigger_not_attachment_notes_contains' => 'No attachment notes contain ":trigger_value"', - 'rule_trigger_not_attachment_notes_starts' => 'No attachment notes start with ":trigger_value"', - 'rule_trigger_not_attachment_notes_ends' => 'No attachment notes end on ":trigger_value"', - 'rule_trigger_not_reconciled' => 'Transaction is not reconciled', - 'rule_trigger_not_exists' => 'Transaction does not exist', - 'rule_trigger_not_has_attachments' => 'Transaction has no attachments', - 'rule_trigger_not_has_any_category' => 'Transaction has no category', - 'rule_trigger_not_has_any_budget' => 'Transaction has no category', - 'rule_trigger_not_has_any_bill' => 'Transaction has no bill', - 'rule_trigger_not_has_any_tag' => 'Transaction has no tags', - 'rule_trigger_not_any_notes' => 'Transaction has no notes', - 'rule_trigger_not_any_external_url' => 'Transaction has no external URL', - 'rule_trigger_not_has_no_attachments' => 'Transaction has a (any) attachment(s)', - 'rule_trigger_not_has_no_category' => 'Transaction has a (any) category', - 'rule_trigger_not_has_no_budget' => 'Transaction has a (any) budget', - 'rule_trigger_not_has_no_bill' => 'Transaction has a (any) bill', - 'rule_trigger_not_has_no_tag' => 'Transaction has a (any) tag', - 'rule_trigger_not_no_notes' => 'Transaction has any notes', - 'rule_trigger_not_no_external_url' => 'Transaction has an external URL', - 'rule_trigger_not_source_is_cash' => 'Source account is not a cash account', - 'rule_trigger_not_destination_is_cash' => 'Destination account is not a cash account', - 'rule_trigger_not_account_is_cash' => 'Neither account is a cash account', + 'rule_trigger_not_account_id' => 'O ID da conta não é ":trigger_value"', + 'rule_trigger_not_source_account_id' => 'O ID da conta de origem não é ":trigger_value"', + 'rule_trigger_not_destination_account_id' => 'O ID da conta de destino não é ":trigger_value"', + 'rule_trigger_not_transaction_type' => 'O tipo de transação não é ":trigger_value"', + 'rule_trigger_not_tag_is' => 'A etiqueta não é ":trigger_value"', + 'rule_trigger_not_tag_is_not' => 'A etiqueta é ":trigger_value"', + 'rule_trigger_not_description_is' => 'A descrição não é ":trigger_value"', + 'rule_trigger_not_description_contains' => 'A descrição não contém', + 'rule_trigger_not_description_ends' => 'A descrição não termina com ":trigger_value"', + 'rule_trigger_not_description_starts' => 'A descrição não começa com ":trigger_value"', + 'rule_trigger_not_notes_is' => 'As notas não são ":trigger_value"', + 'rule_trigger_not_notes_contains' => 'As notas não contêm ":trigger_value"', + 'rule_trigger_not_notes_ends' => 'As notas não terminam em ":trigger_value"', + 'rule_trigger_not_notes_starts' => 'As notas não começam com ":trigger_value"', + 'rule_trigger_not_source_account_is' => 'A conta de origem não é ":trigger_value"', + 'rule_trigger_not_source_account_contains' => 'A conta de origem não contém ":trigger_value"', + 'rule_trigger_not_source_account_ends' => 'A conta de origem não termina em ":trigger_value"', + 'rule_trigger_not_source_account_starts' => 'A conta de origem não começa com ":trigger_value"', + 'rule_trigger_not_source_account_nr_is' => 'O número / IBAN da conta de origem não é ":trigger_value"', + 'rule_trigger_not_source_account_nr_contains' => 'O número / IBAN da conta de origem não contém ":trigger_value"', + 'rule_trigger_not_source_account_nr_ends' => 'O número / IBAN da conta de origem não termina em ":trigger_value"', + 'rule_trigger_not_source_account_nr_starts' => 'O número / IBAN da conta de origem não começa com ":trigger_value"', + 'rule_trigger_not_destination_account_is' => 'A conta de destino não é ":trigger_value"', + 'rule_trigger_not_destination_account_contains' => 'A conta de destino não contém ":trigger_value"', + 'rule_trigger_not_destination_account_ends' => 'A conta de destino não termina em ":trigger_value"', + 'rule_trigger_not_destination_account_starts' => 'A conta de destino não começa com ":trigger_value"', + 'rule_trigger_not_destination_account_nr_is' => 'O número / IBAN da conta de destino não é ":trigger_value"', + 'rule_trigger_not_destination_account_nr_contains' => 'O número / IBAN da conta de destino não contém ":trigger_value"', + 'rule_trigger_not_destination_account_nr_ends' => 'O número / IBAN da conta de destino não termina em ":trigger_value"', + 'rule_trigger_not_destination_account_nr_starts' => 'O número / IBAN da conta de destino não começa com ":trigger_value"', + 'rule_trigger_not_account_is' => 'Nenhuma das contas é ":trigger_value"', + 'rule_trigger_not_account_contains' => 'Nenhuma das contas contém ":trigger_value"', + 'rule_trigger_not_account_ends' => 'Nenhuma das contas termina em ":trigger_value"', + 'rule_trigger_not_account_starts' => 'Nenhuma das contas começa com ":trigger_value"', + 'rule_trigger_not_account_nr_is' => 'Nenhum dos números de conta / IBAN é ":trigger_value"', + 'rule_trigger_not_account_nr_contains' => 'Nenhum dos números de conta / IBAN contém ":trigger_value"', + 'rule_trigger_not_account_nr_ends' => 'Nenhum dos números de conta / IBAN termina em ":trigger_value"', + 'rule_trigger_not_account_nr_starts' => 'Nenhum dos números de conta / IBAN começa com ":trigger_value"', + 'rule_trigger_not_category_is' => 'A categoria não é ":trigger_value"', + 'rule_trigger_not_category_contains' => 'A categoria não contém ":trigger_value"', + 'rule_trigger_not_category_ends' => 'A categoria não termina em ":trigger_value"', + 'rule_trigger_not_category_starts' => 'A categoria não começa com ":trigger_value"', + 'rule_trigger_not_budget_is' => 'O orçamento não é ":trigger_value"', + 'rule_trigger_not_budget_contains' => 'O orçamento não contém ":trigger_value"', + 'rule_trigger_not_budget_ends' => 'O orçamento não termina em ":trigger_value"', + 'rule_trigger_not_budget_starts' => 'O orçamento não começa com ":trigger_value"', + 'rule_trigger_not_bill_is' => 'A fatura não é ":trigger_value"', + 'rule_trigger_not_bill_contains' => 'A fatura não contém ":trigger_value"', + 'rule_trigger_not_bill_ends' => 'A fatura não termina em ":trigger_value"', + 'rule_trigger_not_bill_starts' => 'A despesa não termina com ":trigger_value"', + 'rule_trigger_not_external_id_is' => 'O ID externo não é ":trigger_value"', + 'rule_trigger_not_external_id_contains' => 'O ID externo não contém ":trigger_value"', + 'rule_trigger_not_external_id_ends' => 'O ID externo não termina em ":trigger_value"', + 'rule_trigger_not_external_id_starts' => 'O ID externo não começa com ":trigger_value"', + 'rule_trigger_not_internal_reference_is' => 'A referência interna não é ":trigger_value"', + 'rule_trigger_not_internal_reference_contains' => 'A referência interna não contém ":trigger_value"', + 'rule_trigger_not_internal_reference_ends' => 'A referência interna não termina em ":trigger_value"', + 'rule_trigger_not_internal_reference_starts' => 'A referência interna não começa com ":trigger_value"', + 'rule_trigger_not_external_url_is' => 'O URL externo não é ":trigger_value"', + 'rule_trigger_not_external_url_contains' => 'O URL externo não contém ":trigger_value"', + 'rule_trigger_not_external_url_ends' => 'O URL externo não termina em ":trigger_value"', + 'rule_trigger_not_external_url_starts' => 'O URL externo não começa com ":trigger_value"', + 'rule_trigger_not_currency_is' => 'A moeda não é ":trigger_value"', + 'rule_trigger_not_foreign_currency_is' => 'A moeda estrangeira não é ":trigger_value"', + 'rule_trigger_not_id' => 'O ID da transação não é ":trigger_value"', + 'rule_trigger_not_journal_id' => 'O ID do diário de transações não é ":trigger_value"', + 'rule_trigger_not_recurrence_id' => 'O ID da recorrência não é ":trigger_value"', + 'rule_trigger_not_date_on' => 'A data não é ":trigger_value"', + 'rule_trigger_not_date_before' => 'A data é anterior a ":trigger_value"', + 'rule_trigger_not_date_after' => 'A data não é posterior a ":trigger_value"', + 'rule_trigger_not_interest_date_on' => 'A data de juros não é ":trigger_value"', + 'rule_trigger_not_interest_date_before' => 'A data de juros não é anterior a ":trigger_value"', + 'rule_trigger_not_interest_date_after' => 'A data de juros não é posterior a ":trigger_value"', + 'rule_trigger_not_book_date_on' => 'A data de registo não é ":trigger_value"', + 'rule_trigger_not_book_date_before' => 'A data de registo não é anterior a ":trigger_value"', + 'rule_trigger_not_book_date_after' => 'A data de registo não é posterior a ":trigger_value"', + 'rule_trigger_not_process_date_on' => 'A data de processamento não é ":trigger_value"', + 'rule_trigger_not_process_date_before' => 'A data de processamento não é anterior a ":trigger_value"', + 'rule_trigger_not_process_date_after' => 'A data de processamento não é posterior a ":trigger_value"', + 'rule_trigger_not_due_date_on' => 'A data de vencimento não é ":trigger_value"', + 'rule_trigger_not_due_date_before' => 'A data de vencimento não é anterior a ":trigger_value"', + 'rule_trigger_not_due_date_after' => 'A data de vencimento não é posterior a ":trigger_value"', + 'rule_trigger_not_payment_date_on' => 'A data de pagamento não é ":trigger_value"', + 'rule_trigger_not_payment_date_before' => 'A data de pagamento não é anterior a ":trigger_value"', + 'rule_trigger_not_payment_date_after' => 'A data de pagamento não é posterior a ":trigger_value"', + 'rule_trigger_not_invoice_date_on' => 'A data da fatura não é ":trigger_value"', + 'rule_trigger_not_invoice_date_before' => 'A data da fatura não é anterior a ":trigger_value"', + 'rule_trigger_not_invoice_date_after' => 'A data da fatura não é posterior a ":trigger_value"', + 'rule_trigger_not_created_at_on' => 'A transação não foi criada em ":trigger_value"', + 'rule_trigger_not_created_at_before' => 'A transação não foi criada antes de ":trigger_value"', + 'rule_trigger_not_created_at_after' => 'A transação não foi criada depois de ":trigger_value"', + 'rule_trigger_not_updated_at_on' => 'A transação não foi alterada em ":trigger_value"', + 'rule_trigger_not_updated_at_before' => 'A transação não foi alterada antes de ":trigger_value"', + 'rule_trigger_not_updated_at_after' => 'A transação não foi alterada depois de ":trigger_value"', + 'rule_trigger_not_amount_is' => 'O montante da transação não é ":trigger_value"', + 'rule_trigger_not_amount_less' => 'O montante da transação é superior a ":trigger_value"', + 'rule_trigger_not_amount_more' => 'O montante da transação é inferior a ":trigger_value"', + 'rule_trigger_not_foreign_amount_is' => 'O montante em moeda estrangeira da transação não é ":trigger_value"', + 'rule_trigger_not_foreign_amount_less' => 'O montante em moeda estrangeira da transação é superior a ":trigger_value"', + 'rule_trigger_not_foreign_amount_more' => 'O montante em moeda estrangeira da transação é inferior a ":trigger_value"', + 'rule_trigger_not_attachment_name_is' => 'Nenhum anexo tem o nome: ":trigger_value"', + 'rule_trigger_not_attachment_name_contains' => 'Nenhum anexo tem um nome contendo ":trigger_value"', + 'rule_trigger_not_attachment_name_starts' => 'Nenhum anexo tem um nome começado por ":trigger_value"', + 'rule_trigger_not_attachment_name_ends' => 'Nenhum anexo tem um nome terminado em ":trigger_value"', + 'rule_trigger_not_attachment_notes_are' => 'Nenhuma nota de anexo é ":trigger_value"', + 'rule_trigger_not_attachment_notes_contains' => 'Nenhuma nota de anexo contém ":trigger_value"', + 'rule_trigger_not_attachment_notes_starts' => 'Nenhuma nota de anexo começa com ":trigger_value"', + 'rule_trigger_not_attachment_notes_ends' => 'Nenhuma nota de anexo termina em ":trigger_value"', + 'rule_trigger_not_reconciled' => 'A transação não está reconciliada', + 'rule_trigger_not_exists' => 'A transação não existe', + 'rule_trigger_not_has_attachments' => 'A transação não tem anexos', + 'rule_trigger_not_has_any_category' => 'A transação não tem categoria', + 'rule_trigger_not_has_any_budget' => 'A transação não tem categoria', + 'rule_trigger_not_has_any_bill' => 'A transação não tem despesa', + 'rule_trigger_not_has_any_tag' => 'A transação não tem etiquetas', + 'rule_trigger_not_any_notes' => 'A transação não tem notas', + 'rule_trigger_not_any_external_url' => 'A transação não tem URL externo', + 'rule_trigger_not_has_no_attachments' => 'A transação tem pelo menos um anexo', + 'rule_trigger_not_has_no_category' => 'A transação tem uma categoria (qualquer)', + 'rule_trigger_not_has_no_budget' => 'A transação tem um orçamento (qualquer)', + 'rule_trigger_not_has_no_bill' => 'A transação tem uma despesa (qualquer)', + 'rule_trigger_not_has_no_tag' => 'A transação tem uma etiqueta (qualquer)', + 'rule_trigger_not_no_notes' => 'A transação tem pelo menos uma nota', + 'rule_trigger_not_no_external_url' => 'A transação tem um URL externo', + 'rule_trigger_not_source_is_cash' => 'A conta de origem não é uma conta de caixa', + 'rule_trigger_not_destination_is_cash' => 'A conta de destino não é uma conta de caixa', + 'rule_trigger_not_account_is_cash' => 'Nenhuma das contas é uma conta de caixa', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -1218,8 +1218,8 @@ return [ // actions - 'rule_action_delete_transaction_choice' => 'DELETE transaction(!)', - 'rule_action_delete_transaction' => 'DELETE transaction(!)', + 'rule_action_delete_transaction_choice' => 'APAGAR transação(!)', + 'rule_action_delete_transaction' => 'APAGAR transação(!)', 'rule_action_set_category' => 'Definir categoria para ":action_value"', 'rule_action_clear_category' => 'Limpar categoria', 'rule_action_set_budget' => 'Definir o orçamento para ":action_value"', @@ -1230,30 +1230,30 @@ return [ 'rule_action_set_description' => 'Definir descrição para ":action_value"', 'rule_action_append_description' => 'Acrescentar à descrição com ":action_value"', 'rule_action_prepend_description' => 'Preceder a descrição com ":action_value"', - 'rule_action_set_category_choice' => 'Set category to ..', + 'rule_action_set_category_choice' => 'Atribuir a categoria..', 'rule_action_clear_category_choice' => 'Limpar qualquer categoria', - 'rule_action_set_budget_choice' => 'Set budget to ..', + 'rule_action_set_budget_choice' => 'Atribuir o orçamento..', 'rule_action_clear_budget_choice' => 'Limpar qualquer orçamento', - 'rule_action_add_tag_choice' => 'Add tag ..', - 'rule_action_remove_tag_choice' => 'Remove tag ..', + 'rule_action_add_tag_choice' => 'Adicionar etiqueta..', + 'rule_action_remove_tag_choice' => 'Remover etiqueta..', 'rule_action_remove_all_tags_choice' => 'Remover todas as etiquetas', - 'rule_action_set_description_choice' => 'Set description to ..', - 'rule_action_update_piggy_choice' => 'Add / remove transaction amount in piggy bank ..', - 'rule_action_update_piggy' => 'Add / remove transaction amount in piggy bank ":action_value"', - 'rule_action_append_description_choice' => 'Append description with ..', - 'rule_action_prepend_description_choice' => 'Prepend description with ..', - 'rule_action_set_source_account_choice' => 'Set source account to ..', + 'rule_action_set_description_choice' => 'Atribuir a descrição..', + 'rule_action_update_piggy_choice' => 'Adicionar / remover montante da transação ao mealheiro..', + 'rule_action_update_piggy' => 'Adicionar/remover montante da transação no mealheiro ":action_value"', + 'rule_action_append_description_choice' => 'Acrescentar ao final da descrição..', + 'rule_action_prepend_description_choice' => 'Acrescentar ao início da descrição..', + 'rule_action_set_source_account_choice' => 'Atribuir a conta de origem..', 'rule_action_set_source_account' => 'Definir conta de origem para :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to ..', + 'rule_action_set_destination_account_choice' => 'Atribuir a conta de destino..', 'rule_action_set_destination_account' => 'Definir a conta de destino para :action_value', - 'rule_action_append_notes_choice' => 'Append notes with ..', + 'rule_action_append_notes_choice' => 'Acrescentar ao final das notas..', 'rule_action_append_notes' => 'Anexar notas com ":action_value"', - 'rule_action_prepend_notes_choice' => 'Prepend notes with ..', + 'rule_action_prepend_notes_choice' => 'Preceder notas com..', 'rule_action_prepend_notes' => 'Preceder notas com ":action_value"', 'rule_action_clear_notes_choice' => 'Remover todas as notas', 'rule_action_clear_notes' => 'Remover todas as notas', - 'rule_action_set_notes_choice' => 'Set notes to ..', - 'rule_action_link_to_bill_choice' => 'Link to a bill ..', + 'rule_action_set_notes_choice' => 'Atribuir as notas..', + 'rule_action_link_to_bill_choice' => 'Ligar a despesa..', 'rule_action_link_to_bill' => 'Ligar a uma fatura ":action_value"', 'rule_action_set_notes' => 'Defina notas para ":action_value"', 'rule_action_convert_deposit_choice' => 'Converter a transacção para um depósito', @@ -1262,20 +1262,20 @@ return [ 'rule_action_convert_withdrawal' => 'Converter a transacção para um levantamento de ":action_value"', 'rule_action_convert_transfer_choice' => 'Converter a transacção para uma transferência', 'rule_action_convert_transfer' => 'Converter a transacção para uma transferência de ":action_value"', - 'rule_action_append_descr_to_notes_choice' => 'Append the description to the transaction notes', - 'rule_action_append_notes_to_descr_choice' => 'Append the transaction notes to the description', - 'rule_action_move_descr_to_notes_choice' => 'Replace the current transaction notes with the description', - 'rule_action_move_notes_to_descr_choice' => 'Replace the current description with the transaction notes', - 'rule_action_append_descr_to_notes' => 'Append description to notes', - 'rule_action_append_notes_to_descr' => 'Append notes to description', - 'rule_action_move_descr_to_notes' => 'Replace notes with description', - 'rule_action_move_notes_to_descr' => 'Replace description with notes', + 'rule_action_append_descr_to_notes_choice' => 'Acrescentar a descrição às notas da transação', + 'rule_action_append_notes_to_descr_choice' => 'Acrescentar as notas da transação à descrição', + 'rule_action_move_descr_to_notes_choice' => 'Substituir as atuais notas da transação pela descrição', + 'rule_action_move_notes_to_descr_choice' => 'Substituir a atual descrição pelas notas da transação', + 'rule_action_append_descr_to_notes' => 'Acrescentar descrição às notas', + 'rule_action_append_notes_to_descr' => 'Acrescentar notas à descrição', + 'rule_action_move_descr_to_notes' => 'Substituir notas por descrição', + 'rule_action_move_notes_to_descr' => 'Substituir descrição por notas', 'rulegroup_for_bills_title' => 'Grupo de regras para faturas', - 'rulegroup_for_bills_description' => 'A special rule group for all the rules that involve bills.', - 'rule_for_bill_title' => 'Auto-generated rule for bill ":name"', - 'rule_for_bill_description' => 'This rule is auto-generated to try to match bill ":name".', + 'rulegroup_for_bills_description' => 'Um grupo especial de regras para todas as regras que envolvem despesas.', + 'rule_for_bill_title' => 'Regra gerada automaticamente para a despesa ":name"', + 'rule_for_bill_description' => 'Esta regra é gerada automaticamente para tentar corresponder à despesa ":name".', 'create_rule_for_bill' => 'Criar uma nova regra para a fatura ":name"', - 'create_rule_for_bill_txt' => 'You have just created a new bill called ":name", congratulations!Firefly III can automagically match new withdrawals to this bill. For example, whenever you pay your rent, the bill "rent" will be linked to the expense. This way, Firefly III can accurately show you which bills are due and which ones aren\'t. In order to do so, a new rule must be created. Firefly III has filled in some sensible defaults for you. Please make sure these are correct. If these values are correct, Firefly III will automatically link the correct withdrawal to the correct bill. Please check out the triggers to see if they are correct, and add some if they\'re wrong.', + 'create_rule_for_bill_txt' => 'Acabou de criar uma despesa de nome ":name", parabéns! O Firefly III pode, automagicamente, fazer corresponder novos levantamentos a esta despesa. Por exemplo, sempre que pagar a sua renda, a despesa "renda" será ligada a esse custo. Desta forma, o Firefly III pode, de forma precisa, mostrar-lhe que despesas estão vencidas e quais não estão. Para poder fazê-lo, deve criar uma regra. O Firefly III preencheu por si alguns campos com informação padrão. Por favor, certifique-se que estão certos. Se os valores estiverem corretos, o Firefly III irá, automaticamente, ligar o levantamento certo à despesa certa. Por favor, verifique os gatilhos para ver se estão certos e ajuste o que for necessário se estiverem errados.', 'new_rule_for_bill_title' => 'Regra para a fatura ":name"', 'new_rule_for_bill_description' => 'Esta regra marca as transações para a fatura ":name".', @@ -1302,12 +1302,12 @@ return [ // preferences - 'dark_mode_option_browser' => 'Let your browser decide', - 'dark_mode_option_light' => 'Always light', - 'dark_mode_option_dark' => 'Always dark', + 'dark_mode_option_browser' => 'Deixe o seu navegador decidir', + 'dark_mode_option_light' => 'Sempre claro', + 'dark_mode_option_dark' => 'Sempre escuro', 'equal_to_language' => '(igual ao idioma)', - 'dark_mode_preference' => 'Dark mode', - 'dark_mode_preference_help' => 'Tell Firefly III when to use dark mode.', + 'dark_mode_preference' => 'Modo escuro', + 'dark_mode_preference_help' => 'Diga ao Firefly III quando usar o modo escuro.', 'pref_home_screen_accounts' => 'Contas do ecrã inicial', 'pref_home_screen_accounts_help' => 'Que contas devem ser mostradas na página principal?', 'pref_view_range' => 'Ver intervalo', @@ -1318,13 +1318,13 @@ return [ 'pref_3M' => 'Três meses (trimestre)', 'pref_6M' => 'Seis meses', 'pref_1Y' => 'Um ano', - 'pref_last365' => 'Last year', + 'pref_last365' => 'No ano passado', 'pref_last90' => 'Últimos 90 dias', 'pref_last30' => 'Últimos 30 dias', 'pref_last7' => 'Últimos 7 dias', - 'pref_YTD' => 'Year to date', - 'pref_QTD' => 'Quarter to date', - 'pref_MTD' => 'Month to date', + 'pref_YTD' => 'Desde o início do ano', + 'pref_QTD' => 'Desde o início do trimestre', + 'pref_MTD' => 'Desde o início do mês', 'pref_languages' => 'Idiomas', 'pref_locale' => 'Definições locais', 'pref_languages_help' => 'Firefly III suporta vários idiomas. Qual prefere?', @@ -1353,7 +1353,7 @@ return [ 'preferences_frontpage' => 'Ecrã inicial', 'preferences_security' => 'Segurança', 'preferences_layout' => 'Disposição', - 'preferences_notifications' => 'Notifications', + 'preferences_notifications' => 'Notificações', 'pref_home_show_deposits' => 'Mostrar depósitos no ecrã inicial', 'pref_home_show_deposits_info' => 'O ecrã inicial já mostra as contas de despesas. Deveria também mostrar as receitas?', 'pref_home_do_show_deposits' => 'Sim, mostrar', @@ -1376,7 +1376,7 @@ return [ 'pref_optional_tj_internal_reference' => 'Referência interna', 'pref_optional_tj_notes' => 'Notas', 'pref_optional_tj_attachments' => 'Anexos', - 'pref_optional_tj_external_url' => 'External URL', + 'pref_optional_tj_external_url' => 'URL externo', 'pref_optional_tj_location' => 'Localização', 'pref_optional_tj_links' => 'Links transacionais', 'optional_field_meta_dates' => 'Datas', @@ -1384,32 +1384,32 @@ return [ 'optional_field_attachments' => 'Anexos', 'optional_field_meta_data' => 'Meta data opcional', 'external_url' => 'URL Externo', - 'pref_notification_bill_reminder' => 'Reminder about expiring bills', - 'pref_notification_new_access_token' => 'Alert when a new API access token is created', - 'pref_notification_transaction_creation' => 'Alert when a transaction is created automatically', - 'pref_notification_user_login' => 'Alert when you login from a new location', - 'pref_notifications' => 'Notifications', - 'pref_notifications_help' => 'Indicate if these are notifications you would like to get. Some notifications may contain sensitive financial information.', + 'pref_notification_bill_reminder' => 'Lembrete sobre despesas a expirar', + 'pref_notification_new_access_token' => 'Alerta quando é criado um token da API', + 'pref_notification_transaction_creation' => 'Alerta quando é criada uma transação de forma automática', + 'pref_notification_user_login' => 'Alerta quando inicia sessão a partir de uma nova localização', + 'pref_notifications' => 'Notificações', + 'pref_notifications_help' => 'Diga se não quiser receber algumas destas notificações. Algumas notificações podem conter informação financeira sensível.', 'slack_webhook_url' => 'Slack Webhook URL', - 'slack_webhook_url_help' => 'If you want Firefly III to notify you using Slack, enter the webhook URL here. Otherwise leave the field blank. If you are an admin, you need to set this URL in the administration as well.', + 'slack_webhook_url_help' => 'Se quiser que o Firefly III o notifique via Slack, introduza o URL do webhook aqui. Caso contrário, deixe o campo em branco. Se for um administrador, também tem de definir este URL na administração.', 'slack_url_label' => 'Slack "incoming webhook" URL', // Financial administrations - 'administration_index' => 'Financial administration', + 'administration_index' => 'Administração financeira', // profile: - 'purge_data_title' => 'Purge data from Firefly III', - 'purge_data_expl' => '"Purging" means "deleting that which is already deleted". In normal circumstances, Firefly III deletes nothing permanently. It just hides it. The button below deletes all of these previously "deleted" records FOREVER.', - 'delete_stuff_header' => 'Delete and purge data', - 'purge_all_data' => 'Purge all deleted records', - 'purge_data' => 'Purge data', - 'purged_all_records' => 'All deleted records have been purged.', - 'delete_data_title' => 'Delete data from Firefly III', - 'permanent_delete_stuff' => 'You can delete stuff from Firefly III. Using the buttons below means that your items will be removed from view and hidden. There is no undo-button for this, but the items may remain in the database where you can salvage them if necessary.', + 'purge_data_title' => 'Purgue dados do Firefly III', + 'purge_data_expl' => '"Purgar" significa "apagar o que já foi apagado". Em circunstâncias normais, o Firefly III não apaga nada de forma definitiva. Apenas esconde. O botão abaixo apaga todos esses registos anteriormente "apagados" PARA SEMPRE.', + 'delete_stuff_header' => 'Apagar e purgar dados', + 'purge_all_data' => 'Purgar todos os registos apagados', + 'purge_data' => 'Purgar dados', + 'purged_all_records' => 'Todos os registos apagados foram purgados.', + 'delete_data_title' => 'Apagar dados do Firefly III', + 'permanent_delete_stuff' => 'Pode apagar coisas do Firefly III. Usando os botões abaixo fará com que os seus itens sejam removidos da visualização e escondidos. Não há botão de anular para isto, mas os itens permanecem na base de dados e poderá recuperá-los se necessário.', 'other_sessions_logged_out' => 'Todas as outras sessões foram desconectadas.', - 'delete_unused_accounts' => 'Deleting unused accounts will clean your auto-complete lists.', - 'delete_all_unused_accounts' => 'Delete unused accounts', - 'deleted_all_unused_accounts' => 'All unused accounts are deleted', + 'delete_unused_accounts' => 'Apagar contas não utilizadas irá limpar as suas listas de preenchimento automático.', + 'delete_all_unused_accounts' => 'Apagar contas não utilizadas', + 'deleted_all_unused_accounts' => 'Todas as contas não usadas foram apagadas', 'delete_all_budgets' => 'Apagar TODOS os orçamentos', 'delete_all_categories' => 'Apagar TODAS as categorias', 'delete_all_tags' => 'Apagar TODAS as etiquetas', @@ -1486,7 +1486,7 @@ return [ 'oauth' => 'OAuth', 'profile_oauth_clients' => 'Clientes OAuth', 'profile_oauth_no_clients' => 'Não criou nenhum cliente OAuth.', - 'profile_oauth_clients_external_auth' => 'If you\'re using an external authentication provider like Authelia, OAuth Clients will not work. You can use Personal Access Tokens only.', + 'profile_oauth_clients_external_auth' => 'Se estivar a usar um provedor de autenticação externo, como o Authelia, os clientes OAuth não funcionarão. Só pode usar Tokens de Acesso Pessoal.', 'profile_oauth_clients_header' => 'Clientes', 'profile_oauth_client_id' => 'ID do Cliente', 'profile_oauth_client_name' => 'Nome', @@ -1562,9 +1562,9 @@ return [ 'title_deposit' => 'Receita / renda', 'title_transfer' => 'Transferências', 'title_transfers' => 'Transferências', - 'submission_options' => 'Submission options', - 'apply_rules_checkbox' => 'Apply rules', - 'fire_webhooks_checkbox' => 'Fire webhooks', + 'submission_options' => 'Opções de submissão', + 'apply_rules_checkbox' => 'Aplicar regras', + 'fire_webhooks_checkbox' => 'Ativar webhooks', // convert stuff: 'convert_is_already_type_Withdrawal' => 'Esta transação já e um levantamento', @@ -1706,7 +1706,7 @@ return [ 'auto_budget_none' => 'Sem orçamento automático', 'auto_budget_reset' => 'Defina um valor fixo em cada período', 'auto_budget_rollover' => 'Adicionar um montante a cada período', - 'auto_budget_adjusted' => 'Add an amount every period and correct for overspending', + 'auto_budget_adjusted' => 'Adicionar um montante todos os períodos e corrigir para gastos excessivos', 'auto_budget_period_daily' => 'Diariamente', 'auto_budget_period_weekly' => 'Semanalmente', 'auto_budget_period_monthly' => 'Mensalmente', @@ -1716,21 +1716,21 @@ return [ 'auto_budget_help' => 'Pode saber mais sobre esta funcionalidade na ajuda. Carregue no ícone(?) no canto superior direito.', 'auto_budget_reset_icon' => 'Este orçamento será definido periodicamente', 'auto_budget_rollover_icon' => 'O montante do orçamento irá aumentar periodicamente', - 'auto_budget_adjusted_icon' => 'The budget amount will increase periodically and will correct for overspending', + 'auto_budget_adjusted_icon' => 'O montante orçamentado aumentará periodicamente e será corrigido para gastos excessivos', 'remove_budgeted_amount' => 'Remover montante de orçamento no(a) :currency', // bills: 'not_expected_period' => 'Este período não foi previsto', 'not_or_not_yet' => 'Não (ainda)', - 'visit_bill' => 'Visit bill ":name" at Firefly III', + 'visit_bill' => 'Visite a despesa ":name" no Firefly III', 'match_between_amounts' => 'Fatura corresponde à transação entre :low e :high.', 'running_again_loss' => 'As transações ligadas anteriormente a esta fatura poderão perder a ligação, se coincidirem (ou não) com a(s) regra(s).', 'bill_related_rules' => 'Regras relacionadas a esta fatura', 'repeats' => 'Repete', - 'bill_end_date_help' => 'Optional field. The bill is expected to end on this date.', - 'bill_extension_date_help' => 'Optional field. The bill must be extended (or cancelled) on or before this date.', + 'bill_end_date_help' => 'Campo opcional. É esperado que a despesa cesse nesta data.', + 'bill_extension_date_help' => 'Campo opcional. A despesa deve ser prorrogada (ou cancelada) até esta data.', 'bill_end_index_line' => 'Esta fatura termina em :date', - 'bill_extension_index_line' => 'This bill must be extended or cancelled on :date', + 'bill_extension_index_line' => 'Esta despesa deve ser prorrogada ou cancelada em :date', 'connected_journals' => 'Transações ligadas entre si', 'auto_match_on' => 'Correspondido automaticamente pelo Firefly III', 'auto_match_off' => 'Nao correspondido automaticamente pelo Firefly III', @@ -1778,8 +1778,8 @@ return [ 'extension_date_is' => 'A data de extensão é {date}', // accounts: - 'i_am_owed_amount' => 'I am owed amount', - 'i_owe_amount' => 'I owe amount', + 'i_am_owed_amount' => 'Devem-me um montante', + 'i_owe_amount' => 'Eu devo um montante', 'inactive_account_link' => 'Você tem :count conta inativa (arquivada), que pode visualizar nesta página separada.| Você tem :count contas inativas (arquivadas), que pode visualizar nesta página separada.', 'all_accounts_inactive' => 'Estas são as suas contas inactivas.', 'active_account_link' => 'Este link volta para suas contas activas.', @@ -1813,7 +1813,7 @@ return [ 'make_new_revenue_account' => 'Criar conta de receitas', 'make_new_liabilities_account' => 'Criar nova conta de passivos', 'asset_accounts' => 'Conta de activos', - 'undefined_accounts' => 'Accounts', + 'undefined_accounts' => 'Contas', 'asset_accounts_inactive' => 'Contas de activos (inactivas)', 'expense_accounts' => 'Conta de despesas', 'expense_accounts_inactive' => 'Contas de despesas (desactivadas)', @@ -2294,7 +2294,7 @@ return [ 'budgeted' => 'Orçamentado', 'period' => 'Período', 'balance' => 'Saldo', - 'in_out_period' => 'In + out this period', + 'in_out_period' => 'Dentro + fora este período', 'sum' => 'Soma', 'summary' => 'Resumo', 'average' => 'Média', @@ -2302,7 +2302,7 @@ return [ 'no_tags' => '(sem etiquetas)', // piggy banks: - 'event_history' => 'Event history', + 'event_history' => 'Histórico de eventos', 'add_money_to_piggy' => 'Adicionar dinheiro ao mealheiro ":name"', 'piggy_bank' => 'Mealheiro', 'new_piggy_bank' => 'Novo mealheiro', @@ -2372,15 +2372,15 @@ return [ // administration - 'invite_is_already_redeemed' => 'The invite to ":address" has already been redeemed.', - 'invite_is_deleted' => 'The invite to ":address" has been deleted.', - 'invite_new_user_title' => 'Invite new user', - 'invite_new_user_text' => 'As an administrator, you can invite users to register on your Firefly III administration. Using the direct link you can share with them, they will be able to register an account. The invited user and their invite link will appear in the table below. You are free to share the invitation link with them.', - 'invited_user_mail' => 'Email address', - 'invite_user' => 'Invite user', - 'user_is_invited' => 'Email address ":address" was invited to Firefly III', + 'invite_is_already_redeemed' => 'O convite enviado para ":address" já foi resgatado.', + 'invite_is_deleted' => 'O convite enviado para ":address" foi apagado.', + 'invite_new_user_title' => 'Convidar novo utilizador', + 'invite_new_user_text' => 'Como administrador, pode convidar utilizadores para se registarem na sua instância do Firefly III. Usando a ligação direta que partilhar com eles, eles serão capazes de registar uma conta. O utilizador convidado e a sua ligação de convite aparecerão na tabela abaixo. Tenha a liberdade de partilhar com eles a ligação do convite.', + 'invited_user_mail' => 'Endereço de email', + 'invite_user' => 'Convidar utilizador', + 'user_is_invited' => 'O endereço de email ":address" foi convidado para o Firefly III', 'administration' => 'Administração', - 'code_already_used' => 'Invite code has been used', + 'code_already_used' => 'O código de convite foi usado', 'user_administration' => 'Administração de utilizadores', 'list_all_users' => 'Todos os utilizadores', 'all_users' => 'Todos os utilizadores', @@ -2412,23 +2412,23 @@ return [ 'delete_user' => 'Apagar utilizador ":email"', 'user_deleted' => 'O utilizador foi apagado', 'send_test_email' => 'Enviar e-mail de teste', - 'send_test_email_text' => 'To see if your installation is capable of sending email or posting Slack messages, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', + 'send_test_email_text' => 'Para confirmar se a sua instância é capaz de enviar emails ou publicar mensagens no Slack, pressione este botão. Não verá nenhum erro aqui (mesmo que exista), os ficheiros de registo (log) registarão os erros. Pode premir este botão todas as vezes que quiser. Não há controlo de spam. A mensagem será enviada para :email e deve chegar brevemente.', 'send_message' => 'Enviar mensagem', 'send_test_triggered' => 'O teste foi activado. Verifique a caixa de entrada e os arquivos de log.', 'give_admin_careful' => 'Utilizadores que obtiverem direitos de administrador podem remover as suas permissões. Tenha cuidado.', 'admin_maintanance_title' => 'Manutenção', 'admin_maintanance_expl' => 'Alguns botões sofisticados para a manutenção do Firefly III', 'admin_maintenance_clear_cache' => 'Limpar cache', - 'admin_notifications' => 'Admin notifications', - 'admin_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. If you want to get these messages over Slack as well, set the "incoming webhook" URL.', - 'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message', - 'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification', - 'admin_notification_check_new_version' => 'A new version is available', - 'admin_notification_check_invite_created' => 'A user is invited to Firefly III', - 'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed', - 'all_invited_users' => 'All invited users', - 'save_notification_settings' => 'Save settings', - 'notification_settings_saved' => 'The notification settings have been saved', + 'admin_notifications' => 'Notificações administrativas', + 'admin_notifications_expl' => 'As seguintes notificações podem ser ativadas ou desativadas pelo administrador. Se quiser receber essas mensagens através do Slack, defina a URL de "webhook de entrada".', + 'admin_notification_check_user_new_reg' => 'O utilizador recebe mensagem de boas-vindas pós-registo', + 'admin_notification_check_admin_new_reg' => 'O(s) administrador(es) recebe(m) uma notificação de registo de novo utilizador', + 'admin_notification_check_new_version' => 'Está disponível uma nova versão', + 'admin_notification_check_invite_created' => 'Um utilizador foi convidado para o Firefly III', + 'admin_notification_check_invite_redeemed' => 'Um convite de utilizador foi resgatado', + 'all_invited_users' => 'Todos os utilizadores convidados', + 'save_notification_settings' => 'Gravar definições', + 'notification_settings_saved' => 'As definições de notificação foram gravadas', 'split_transaction_title' => 'Descrição da transacção dividida', @@ -2575,8 +2575,8 @@ return [ 'no_bills_create_default' => 'Criar uma fatura', // recurring transactions - 'create_right_now' => 'Create right now', - 'no_new_transaction_in_recurrence' => 'No new transaction was created. Perhaps it was already fired for this date?', + 'create_right_now' => 'Criar agora mesmo', + 'no_new_transaction_in_recurrence' => 'Nenhuma transação nova foi criada. Talvez já tenha sido acionada nesta data?', 'recurrences' => 'Transações recorrentes', 'repeat_until_in_past' => 'Esta transação recorrente parou de repetir a :date.', 'recurring_calendar_view' => 'Calendário', @@ -2691,25 +2691,25 @@ return [ 'placeholder' => '[Placeholder]', // audit log entries - 'audit_log_entries' => 'Audit log entries', - 'ale_action_log_add' => 'Added :amount to piggy bank ":name"', - 'ale_action_log_remove' => 'Removed :amount from piggy bank ":name"', - 'ale_action_clear_budget' => 'Removed from budget', - 'ale_action_clear_category' => 'Removed from category', - 'ale_action_clear_notes' => 'Removed notes', - 'ale_action_clear_tag' => 'Cleared tag', - 'ale_action_clear_all_tags' => 'Cleared all tags', - 'ale_action_set_bill' => 'Linked to bill', - 'ale_action_set_budget' => 'Set budget', - 'ale_action_set_category' => 'Set category', - 'ale_action_set_source' => 'Set source account', - 'ale_action_set_destination' => 'Set destination account', - 'ale_action_update_transaction_type' => 'Changed transaction type', - 'ale_action_update_notes' => 'Changed notes', - 'ale_action_update_description' => 'Changed description', - 'ale_action_add_to_piggy' => 'Piggy bank', - 'ale_action_remove_from_piggy' => 'Piggy bank', - 'ale_action_add_tag' => 'Added tag', + 'audit_log_entries' => 'Detalhes do registo (log) de auditoria', + 'ale_action_log_add' => 'Adicionado :amount ao mealheiro ":name"', + 'ale_action_log_remove' => 'Removido :amount do mealheiro ":name"', + 'ale_action_clear_budget' => 'Removido do orçamento', + 'ale_action_clear_category' => 'Removido da categoria', + 'ale_action_clear_notes' => 'Notas removidas', + 'ale_action_clear_tag' => 'Etiqueta limpa', + 'ale_action_clear_all_tags' => 'Apagadas todas as etiquetas', + 'ale_action_set_bill' => 'Ligado a despesa', + 'ale_action_set_budget' => 'Definir orçamento', + 'ale_action_set_category' => 'Definir categoria', + 'ale_action_set_source' => 'Definir conta de origem', + 'ale_action_set_destination' => 'Definir conta de destino', + 'ale_action_update_transaction_type' => 'Tipo de transação alterado', + 'ale_action_update_notes' => 'Notas alteradas', + 'ale_action_update_description' => 'Descrição alterada', + 'ale_action_add_to_piggy' => 'Mealheiro', + 'ale_action_remove_from_piggy' => 'Mealheiro', + 'ale_action_add_tag' => 'Etiqueta adicionada', ]; diff --git a/resources/lang/pt_PT/form.php b/resources/lang/pt_PT/form.php index dd733649e0..a306c589db 100644 --- a/resources/lang/pt_PT/form.php +++ b/resources/lang/pt_PT/form.php @@ -38,39 +38,39 @@ return [ // new user: 'bank_name' => 'Nome do banco', 'bank_balance' => 'Saldo', - 'savings_balance' => 'Saldo nas poupancas', - 'credit_card_limit' => 'Limite do cartao de credito', + 'savings_balance' => 'Saldo nas poupanças', + 'credit_card_limit' => 'Limite do cartão de crédito', 'automatch' => 'Corresponder automaticamente', 'skip' => 'Pular', - 'enabled' => 'Activo', + 'enabled' => 'Ativo', 'name' => 'Nome', - 'active' => 'Activo', - 'amount_min' => 'Montante minimo', - 'amount_max' => 'Montante maximo', - 'match' => 'Corresponde em', - 'strict' => 'Modo restricto', + 'active' => 'Ativo', + 'amount_min' => 'Montante mínimo', + 'amount_max' => 'Montante máximo', + 'match' => 'Correspondências em', + 'strict' => 'Modo restrito', 'repeat_freq' => 'Repete', 'object_group' => 'Grupo', 'location' => 'Localização', - 'update_channel' => 'Meios de actualização', - 'currency_id' => 'Divisa', - 'transaction_currency_id' => 'Divisa', + 'update_channel' => 'Canal de atualização', + 'currency_id' => 'Moeda', + 'transaction_currency_id' => 'Moeda', 'auto_budget_currency_id' => 'Moeda', - 'external_ip' => 'IP externo do teu servidor', + 'external_ip' => 'IP externo do seu servidor', 'attachments' => 'Anexos', 'BIC' => 'BIC', - 'verify_password' => 'Verificar a seguranca da password', + 'verify_password' => 'Verificar a segurança da palavra-passe', 'source_account' => 'Conta de origem', 'destination_account' => 'Conta de destino', 'asset_destination_account' => 'Conta de destino', - 'include_net_worth' => 'Incluir no patrimonio liquido', + 'include_net_worth' => 'Incluir na posição global', 'asset_source_account' => 'Conta de origem', - 'journal_description' => 'Descricao', + 'journal_description' => 'Descrição', 'note' => 'Notas', - 'currency' => 'Divisa', - 'account_id' => 'Conta de activos', - 'budget_id' => 'Orcamento', - 'bill_id' => 'Factura', + 'currency' => 'Moeda', + 'account_id' => 'Conta de ativos', + 'budget_id' => 'Orçamento', + 'bill_id' => 'Fatura', 'opening_balance' => 'Saldo inicial', 'tagMode' => 'Modo da etiqueta', 'virtual_balance' => 'Saldo virtual', @@ -125,53 +125,53 @@ return [ 'deletePermanently' => 'Apagar permanentemente', 'cancel' => 'Cancelar', 'targetdate' => 'Data alvo', - 'startdate' => 'Data de inicio', + 'startdate' => 'Data de início', 'tag' => 'Etiqueta', 'under' => 'Sob', - 'symbol' => 'Simbolo', - 'code' => 'Codigo', + 'symbol' => 'Símbolo', + 'code' => 'Código', 'iban' => 'IBAN', 'account_number' => 'Número de conta', - 'creditCardNumber' => 'Numero do cartao de credito', - 'has_headers' => 'Cabecalhos', + 'creditCardNumber' => 'Número do cartão de crédito', + 'has_headers' => 'Cabeçalhos', 'date_format' => 'Formato da data', - 'specifix' => 'Banco - ou correccoes especificas do ficheiro', + 'specifix' => 'Banco - ou correções específicas do ficheiro', 'attachments[]' => 'Anexos', - 'title' => 'Titulo', + 'title' => 'Título', 'notes' => 'Notas', 'filename' => 'Nome do ficheiro', 'mime' => 'Formato do ficheiro', 'size' => 'Tamanho', - 'trigger' => 'Disparador', + 'trigger' => 'Gatilho', 'stop_processing' => 'Parar processamento', - 'start_date' => 'Inicio do intervalo', + 'start_date' => 'Início do intervalo', 'end_date' => 'Fim do intervalo', 'enddate' => 'Data do término', 'start' => 'Início do intervalo', 'end' => 'Fim do intervalo', 'delete_account' => 'Apagar conta ":name"', - 'delete_webhook' => 'Delete webhook ":title"', - 'delete_bill' => 'Apagar factura ":name"', - 'delete_budget' => 'Apagar orcamento ":name"', + 'delete_webhook' => 'Apagar webhook ":title"', + 'delete_bill' => 'Apagar fatura ":name"', + 'delete_budget' => 'Apagar orçamento ":name"', 'delete_category' => 'Apagar categoria ":name"', - 'delete_currency' => 'Apagar divisa ":name"', + 'delete_currency' => 'Apagar moeda ":name"', 'delete_journal' => 'Apagar transação com a descrição ":description"', 'delete_attachment' => 'Apagar anexo ":name"', 'delete_rule' => 'Apagar regra ":title"', 'delete_rule_group' => 'Apagar grupo de regras ":title"', - 'delete_link_type' => 'Apagar tipo de link ":name"', + 'delete_link_type' => 'Apagar tipo de ligação ":name"', 'delete_user' => 'Apagar utilizador ":email"', - 'delete_recurring' => 'Apagar transaccao recorrente ":title"', - 'user_areYouSure' => 'Se apagares o utilizador ":email", tudo em relacao a ele vai desaparecer. Nao existe retorno. Se apagares a tua propria conta, vais perder o acesso a esta instancia do Firefly III.', - 'attachment_areYouSure' => 'Tens a certeza que pretendes apagar o anexo chamado ":name"?', - 'account_areYouSure' => 'Tens a certeza que pretendes apagar a conta chamada ":name"?', + 'delete_recurring' => 'Apagar transação recorrente ":title"', + 'user_areYouSure' => 'Se eliminar o utilizador ":email", vai desaparecer tudo. Não existe retorno. Se eliminar a sua própria conta, vai perder o acesso a esta instância do Firefly III.', + 'attachment_areYouSure' => 'Tem a certeza que pretende apagar o anexo chamado ":name"?', + 'account_areYouSure' => 'Tem a certeza que pretende apagar a conta chamada ":name"?', 'account_areYouSure_js' => 'Tem a certeza que deseja eliminar a conta denominada por "{name}?', 'bill_areYouSure' => 'Tens a certeza que pretendes apagar a factura chamada ":name"?', 'rule_areYouSure' => 'Tens a certeza que pretendes apagar a regra com titulo ":title"?', 'object_group_areYouSure' => 'Tem certeza que quer apagar o grupo ":title"?', 'ruleGroup_areYouSure' => 'Tens a certeza que pretendes apagar o grupo de regras com titulo ":title"?', 'budget_areYouSure' => 'Tens a certeza que pretendes apagar o orcamento chamado ":name"?', - 'webhook_areYouSure' => 'Are you sure you want to delete the webhook named ":title"?', + 'webhook_areYouSure' => 'Tem a certeza que quer apagar o webhook ":title"?', 'category_areYouSure' => 'Tens a certeza que pretendes apagar a categoria chamada ":name"?', 'recurring_areYouSure' => 'Tens a certeza que pretendes apagar a transaccao recorrente chamada ":title"?', 'currency_areYouSure' => 'Tens a certeza que pretendes apagar a divisa chamada ":name"?', @@ -192,33 +192,33 @@ return [ 'tag_areYouSure' => 'Tem a certeza que pretende apagar a etiqueta ":tag"?', - 'journal_link_areYouSure' => 'Tens a certeza que pretendes apagar a ligacao entre :source e :destination?', - 'linkType_areYouSure' => 'Tens a certeza que pretendes apagar o tipo de link ":name" (":inward" / ":outward")?', + 'journal_link_areYouSure' => 'Tem a certeza que pretende apagar a ligação entre :source e :destination?', + 'linkType_areYouSure' => 'Tem a certeza que pretende apagar o tipo de link ":name" (":inward" / ":outward")?', 'permDeleteWarning' => 'Apagar elementos do Firefly III é permanente e não pode ser revertido.', - 'mass_make_selection' => 'Podes prevenir que itens sejam apagados, se removeres a caixa de seleccao.', - 'delete_all_permanently' => 'Apagar os seleccionados, permanentemente', + 'mass_make_selection' => 'Ainda pode evitar eliminar alguns itens limpando a caixa de seleção.', + 'delete_all_permanently' => 'Apagar os selecionados, permanentemente', 'update_all_journals' => 'Atualizar estas transações', - 'also_delete_transactions' => 'A transação vinculada a esta conta vai ser também apagada.|As :count transações vinculadas a esta conta serão também apagadas.', - 'also_delete_transactions_js' => 'Nenhuma transação| A única transação ligada a esta conta será também excluída.|Todas as {count} transações ligadas a esta conta serão também excluídas.', - 'also_delete_connections' => 'A transação vinculada a este tipo de ligação irá perder a conexão.|As :count transações vinculadas a este tipo de ligação irão perder as suas conexões.', - 'also_delete_rules' => 'A unica regra vinculada a este grupo de regras vai ser apagada tambem.|Todas as :count regras vinculadas a este grupo de regras vao ser apagadas tambem.', - 'also_delete_piggyBanks' => 'O unico mealheiro vinculado a esta conta vai ser apagado tambem.|Todos os :count mealheiros vinculados a esta conta vao ser apagados tambem.', + 'also_delete_transactions' => 'A única transação vinculada a esta conta vai ser também apagada.|Todas as :count transações vinculadas a esta conta serão também apagadas.', + 'also_delete_transactions_js' => 'Nenhuma transação| A única transação ligada a esta conta será também apagada.|Todas as {count} transações ligadas a esta conta serão também apagadas.', + 'also_delete_connections' => 'A única transação vinculada a este tipo de ligação irá perder a ligação.|Todas as :count transações vinculadas a este tipo de ligação irão perder as suas ligações.', + 'also_delete_rules' => 'A única regra vinculada a este grupo de regras vai ser apagada também.|Todas as :count regras vinculadas a este grupo de regras vão ser apagadas também.', + 'also_delete_piggyBanks' => 'O único mealheiro vinculado a esta conta vai ser apagado também.|Todos os :count mealheiros vinculados a esta conta vão ser apagados também.', 'also_delete_piggyBanks_js' => 'Nenhum mealheiro|O único mealheiro ligado a esta conta será também eliminado.|Todos os {count} mealheiros ligados a esta conta serão também eliminados.', 'not_delete_piggy_banks' => 'O mealheiro ligado a este grupo não vai ser apagado. Os :count mealheiros ligados a este grupo não vão ser apagados.', - 'bill_keep_transactions' => 'A única transação vinculada a esta fatura não vai ser apagada.|Todas as :count transações vinculadas a esta fatura não vão ser apagadas.', - 'budget_keep_transactions' => 'A única transação vinculada a este orçamento não vai ser apagada.|Todas as :count transações vinculadas a este orçamento não vão ser apagadas.', - 'category_keep_transactions' => 'A única transação vinculada a esta categoria não vai ser apagada.|Todas as :count transações vinculadas a esta categoria não vão ser apagadas.', - 'recurring_keep_transactions' => 'A única transação criada a partir desta transação recorrente não vai ser apagada.|Todas as :count transações criadas a partir desta transação recorrente não vão ser apagadas.', - 'tag_keep_transactions' => 'A única transação vinculada a esta etiqueta não vai ser apagada.|Todas as :count transações vinculadas a esta etiqueta não vão ser apagadas.', - 'check_for_updates' => 'Procurar actualizacoes', - 'liability_direction' => 'Responsabilidade entrada/saída', + 'bill_keep_transactions' => 'A única transação vinculada a esta fatura não vai ser apagada.|Nenhuma :count transação vinculada a esta fatura será apagada.', + 'budget_keep_transactions' => 'A única transação vinculada a este orçamento não vai ser apagada.|Nenhuma :count transação vinculada a este orçamento será apagada.', + 'category_keep_transactions' => 'A única transação vinculada a esta categoria não vai ser apagada.|Nenhuma :count transação vinculada a esta categoria será apagada.', + 'recurring_keep_transactions' => 'A única transação criada a partir desta transação recorrente não vai ser apagada.|Nenhuma :count transação criada a partir desta transação recorrente será apagada.', + 'tag_keep_transactions' => 'A única transação vinculada a esta etiqueta não vai ser apagada.|Nenhuma :count transação vinculada a esta etiqueta será apagada.', + 'check_for_updates' => 'Procurar atualizações', + 'liability_direction' => 'Passivo entrada/saída', 'delete_object_group' => 'Apagar grupo ":title"', - 'email' => 'Email', - 'password' => 'Password', - 'password_confirmation' => 'Password (novamente)', + 'email' => 'Endereço de email', + 'password' => 'Palavra-passe', + 'password_confirmation' => 'Palavra-passe (novamente)', 'blocked' => 'Bloqueado?', - 'blocked_code' => 'Razao para o bloqueio', - 'login_name' => 'Iniciar sessao', + 'blocked_code' => 'Razão para o bloqueio', + 'login_name' => 'Iniciar sessão', 'is_owner' => 'É administrador?', 'url' => 'URL', 'bill_end_date' => 'Data final', @@ -226,30 +226,30 @@ return [ // import 'apply_rules' => 'Aplicar regras', 'artist' => 'Artista', - 'album' => 'Album', - 'song' => 'Musica', + 'album' => 'Álbum', + 'song' => 'Música', // admin - 'domain' => 'Dominio', - 'single_user_mode' => 'Desactivar registo de utilizadores', - 'is_demo_site' => 'Site de demonstracao?', + 'domain' => 'Domínio', + 'single_user_mode' => 'Desativar registo de utilizadores', + 'is_demo_site' => 'Site de demonstração', // import - 'configuration_file' => 'Ficheiro de configuracao', - 'csv_comma' => 'Virgula (,)', - 'csv_semicolon' => 'Ponto e virgula (;)', - 'csv_tab' => 'Paragrafo (invisivel)', + 'configuration_file' => 'Ficheiro de configuração', + 'csv_comma' => 'Vírgula (,)', + 'csv_semicolon' => 'Ponto e vírgula (;)', + 'csv_tab' => 'Uma tabulação (invisível)', 'csv_delimiter' => 'Delimitador de campos CSV', 'client_id' => 'ID do cliente', 'app_id' => 'ID da aplicação', - 'secret' => 'Codigo secreto', - 'public_key' => 'Chave publica', - 'country_code' => 'Codigo do pais', + 'secret' => 'Código secreto', + 'public_key' => 'Chave pública', + 'country_code' => 'Código do país', 'provider_code' => 'Banco ou provedor de dados', 'fints_url' => 'URL da API FinTS', 'fints_port' => 'Porta', - 'fints_bank_code' => 'Codigo do banco', + 'fints_bank_code' => 'Código do banco', 'fints_username' => 'Utilizador', 'fints_password' => 'PIN / Password', 'fints_account' => 'Conta FinTS', @@ -293,15 +293,15 @@ return [ 'expected_on' => 'Esperado em', 'paid' => 'Pago', 'auto_budget_type' => 'Orçamento automático', - 'auto_budget_amount' => 'Valor do orçamento automático', + 'auto_budget_amount' => 'Montante do orçamento automático', 'auto_budget_period' => 'Período do orçamento automático', 'collected' => 'Recebido', 'submitted' => 'Enviado', 'key' => 'Chave', 'value' => 'Conteúdo do registo', - 'webhook_delivery' => 'Delivery', - 'webhook_response' => 'Response', - 'webhook_trigger' => 'Trigger', + 'webhook_delivery' => 'Entrega', + 'webhook_response' => 'Resposta', + 'webhook_trigger' => 'Gatilho', ]; /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. diff --git a/resources/lang/pt_PT/intro.php b/resources/lang/pt_PT/intro.php index c64f228162..83ba63667b 100644 --- a/resources/lang/pt_PT/intro.php +++ b/resources/lang/pt_PT/intro.php @@ -36,34 +36,34 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Bem vindo à pagina inicial do Firefly III. Por favor, reserve um momento para ler a nossa introdução para perceber o modo de funcionamento do Firefly III.', - 'index_accounts-chart' => 'Este gráfico mostra o saldo atual das tuas contas de ativas. Podes selecionar as contas a aparecer aqui, nas tuas preferências.', + 'index_intro' => 'Bem-vindo(a) à página inicial do Firefly III. Por favor, reserve um momento para ler a nossa introdução para perceber o modo de funcionamento do Firefly III.', + 'index_accounts-chart' => 'Este gráfico mostra o saldo atual das suas contas de ativos. Pode selecionar as contas a aparecer aqui, nas suas preferências.', 'index_box_out_holder' => 'Esta caixa e as restantes ao lado dão-lhe um breve resumo da sua situação financeira.', - 'index_help' => 'Se alguma vez precisares de ajuda com uma página ou um formulário, usa este botão.', - 'index_outro' => 'A maioria das paginas no Firefly III vão começar com um pequeno tutorial como este. Por favor contacta-me quando tiveres questões ou comentários. Desfruta!', - 'index_sidebar-toggle' => 'Para criar transações, contas ou outras coisas, usa o menu sobre este ícone.', - 'index_cash_account' => 'Estas são as contas criadas até agora. Você pode usar uma conta caixa para acompanhar as suas despesas em dinheiro, no entanto não é obrigatório usar.', + 'index_help' => 'Se alguma vez precisar de ajuda com uma página ou um formulário, use este botão.', + 'index_outro' => 'A maioria das páginas no Firefly III vão começar com um pequeno tutorial como este. Por favor, contacte-me quando tiver questões ou comentários. Desfrute!', + 'index_sidebar-toggle' => 'Para criar transações, contas ou outras coisas, use o menu sobre este ícone.', + 'index_cash_account' => 'Estas são as contas criadas até agora. Pode usar uma conta caixa para acompanhar as suas despesas em dinheiro, no entanto, não é obrigatório usar.', // transactions 'transactions_create_basic_info' => 'Adicione a informação básica da sua transação. Origem, destino, data e descrição.', - 'transactions_create_amount_info' => 'Adicione a quantia da transação. Se necessário os campos irão atualizar-se automaticamente de informações de moedas estrangeiras.', - 'transactions_create_optional_info' => 'Todos estes campos são opcionais. Adicionar meta-dados aqui irá ajudar a organizar melhor as suas transações.', + 'transactions_create_amount_info' => 'Adicione a quantia da transação. Se necessário os campos atualizar-se-ão automaticamente com informações de moedas estrangeiras.', + 'transactions_create_optional_info' => 'Estes campos são todos opcionais. Adicionar meta-dados aqui irá ajudar a organizar melhor as suas transações.', 'transactions_create_split' => 'Se quiser dividir uma transação, adicione mais divisões com este botão', // create account: - 'accounts_create_iban' => 'Atribua IBAN\'s válidos nas suas contas. Isto pode ajudar a tornar a importação de dados muito simples no futuro.', - 'accounts_create_asset_opening_balance' => 'Contas de ativos podem ter um saldo de abertura, desta forma indicando o início do seu historial no Firefly III.', + 'accounts_create_iban' => 'Atribua IBAN\'s válidos às suas contas. Isto pode ajudar a tornar a importação de dados muito simples no futuro.', + 'accounts_create_asset_opening_balance' => 'As contas de ativos podem ter um saldo de abertura, desta forma indicando o início do seu historial no Firefly III.', 'accounts_create_asset_currency' => 'O Firefly III suporta múltiplas moedas. Contas de ativos tem uma moeda principal, que deve ser definida aqui.', - 'accounts_create_asset_virtual' => 'Por vezes, pode ajudar dar a tua conta um saldo virtual: uma quantia extra sempre adicionada ou removida do saldo real.', + 'accounts_create_asset_virtual' => 'Por vezes, pode ajudar dar à sua conta um saldo virtual: uma quantia extra, sempre adicionada ou removida do saldo real.', // budgets index - 'budgets_index_intro' => 'Os orçamentos são usados para gerir as tuas finanças e fazem parte de uma das funções principais do Firefly III.', - 'budgets_index_set_budget' => 'Define o teu orçamento total para cada período, assim o Firefly III pode-te dizer se tens orçamentado todo o teu dinheiro disponível.', + 'budgets_index_intro' => 'Os orçamentos são usados para gerir as suas finanças e fazem parte de uma das funções principais do Firefly III.', + 'budgets_index_set_budget' => 'Defina o seu orçamento total para cada período, de modo que o Firefly III possa dizer se tem orçamentado todo o seu dinheiro disponível.', 'budgets_index_see_expenses_bar' => 'Ao gastar dinheiro esta barra vai sendo preenchida.', 'budgets_index_navigate_periods' => 'Navega através de intervalos para definir os orçamentos antecipadamente.', 'budgets_index_new_budget' => 'Crie novos orçamentos como achar melhor.', 'budgets_index_list_of_budgets' => 'Use esta tabela para definir os valores para cada orçamento e manter o controlo dos gastos.', - 'budgets_index_outro' => 'Para obter mais informações sobre orçamentos, verifica o ícone de ajuda no canto superior direito.', + 'budgets_index_outro' => 'Para obter mais informações sobre orçamentos, verifique o ícone de ajuda no canto superior direito.', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -80,39 +80,39 @@ return [ // reports (index) 'reports_index_intro' => 'Use estes relatórios para obter sínteses detalhadas sobre as suas finanças.', 'reports_index_inputReportType' => 'Escolha um tipo de relatório. Confira as páginas de ajuda para ter a certeza do que cada relatório mostra.', - 'reports_index_inputAccountsSelect' => 'Podes incluir ou excluir contas de ativos conforme as suas necessidades.', - 'reports_index_inputDateRange' => 'O intervalo temporal a definir é totalmente preferencial: desde 1 dia ate 10 anos.', - 'reports_index_extra-options-box' => 'Dependendo do relatório que selecionou, pode selecionar campos extra aqui. Repare nesta caixa quando mudar os tipos de relatório.', + 'reports_index_inputAccountsSelect' => 'Pode incluir ou excluir contas de ativos conforme as suas necessidades.', + 'reports_index_inputDateRange' => 'O intervalo temporal a definir é totalmente consigo: desde 1 dia até 10 anos.', + 'reports_index_extra-options-box' => 'Dependendo do relatório que selecionou, pode selecionar filtros e opções extra aqui. Repare nesta caixa quando mudar os tipos de relatório.', // reports (reports) 'reports_report_default_intro' => 'Este relatório vai-lhe dar uma visão rápida e abrangente das suas finanças. Se desejar ver algo a mais, por favor não hesite em contactar-me!', 'reports_report_audit_intro' => 'Este relatório vai-lhe dar informações detalhadas das suas contas de ativos.', - 'reports_report_audit_optionsBox' => 'Usa estes campos para mostrar ou esconder colunas que tenhas interesse.', + 'reports_report_audit_optionsBox' => 'Usa estes campos para mostrar ou esconder as colunas que pretenda.', - 'reports_report_category_intro' => 'Este relatório irá-lhe dar informações detalhadas numa ou múltiplas categorias.', - 'reports_report_category_pieCharts' => 'Estes gráficos irão-lhe dar informações de despesas e receitas, por categoria ou por conta.', - 'reports_report_category_incomeAndExpensesChart' => 'Este gráfico mostra-te as despesas e receitas por categoria.', + 'reports_report_category_intro' => 'Este relatório dar-lhe-á perspetiva sobre uma ou múltiplas categorias.', + 'reports_report_category_pieCharts' => 'Estes gráficos dar-lhe-ão perspetiva sobre despesas e receitas, por categoria ou por conta.', + 'reports_report_category_incomeAndExpensesChart' => 'Este gráfico mostra-lhe as despesas e receitas por categoria.', - 'reports_report_tag_intro' => 'Este relatório irá-lhe dar informações de uma ou múltiplas etiquetas.', - 'reports_report_tag_pieCharts' => 'Estes gráficos irão-lhe dar informações de despesas e receitas por etiqueta, conta, categoria ou orçamento.', + 'reports_report_tag_intro' => 'Este relatório dar-lhe-á informações de uma ou múltiplas etiquetas.', + 'reports_report_tag_pieCharts' => 'Estes gráficos dar-lhe-ão informações de despesas e receitas por etiqueta, conta, categoria ou orçamento.', 'reports_report_tag_incomeAndExpensesChart' => 'Este gráfico mostra-lhe as suas despesas e receitas por etiqueta.', 'reports_report_budget_intro' => 'Este relatório irá-lhe dar informações de um ou múltiplos orçamentos.', - 'reports_report_budget_pieCharts' => 'Estes gráficos irão-lhe dar informações de despesas por orçamento ou por conta.', + 'reports_report_budget_pieCharts' => 'Estes gráficos dar-lhe-ão perspetiva sobre despesas por orçamento ou por conta.', 'reports_report_budget_incomeAndExpensesChart' => 'Este gráfico mostra-lhe as suas despesas por orçamento.', // create transaction - 'transactions_create_switch_box' => 'Usa estes botoes para modares rapidamente o tipo de transaccao que desejas gravar.', - 'transactions_create_ffInput_category' => 'Podes escrever livremente neste campo. Categorias criadas previamente vao ser sugeridas.', - 'transactions_create_withdrawal_ffInput_budget' => 'Associa o teu levantamento a um orcamento para um melhor controlo financeiro.', - 'transactions_create_withdrawal_currency_dropdown_amount' => 'Usa esta caixa de seleccao quando o teu levantamento e noutra divisa.', - 'transactions_create_deposit_currency_dropdown_amount' => 'Utilize esta caixa de seleção quando o seu depósito estiver noutra moeda.', - 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Selecione um mealheiro e associe esta transferência as suas poupanças.', + 'transactions_create_switch_box' => 'Use estes botões para mudar rapidamente o tipo de transação que deseja gravar.', + 'transactions_create_ffInput_category' => 'Pode escrever livremente neste campo. Serão sugeridas categorias criadas previamente.', + 'transactions_create_withdrawal_ffInput_budget' => 'Associa o seu levantamento a um orçamento para um melhor controlo financeiro.', + 'transactions_create_withdrawal_currency_dropdown_amount' => 'Use esta caixa de seleção quando o seu levantamento é noutra moeda.', + 'transactions_create_deposit_currency_dropdown_amount' => 'Use esta caixa de seleção quando o seu depósito estiver noutra moeda.', + 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Selecione um mealheiro e associe esta transferência às suas poupanças.', // piggy banks index: - 'piggy-banks_index_saved' => 'Este campo mostra-te quando ja guardaste em cada mealheiro.', - 'piggy-banks_index_button' => 'Ao lado desta barra de progresso existem 2 butoes(+ e -) para adicionares ou removeres dinheiro de cada mealheiro.', - 'piggy-banks_index_accountStatus' => 'Para cada conta de activos com pelo menos um mealheiro, o estado e listado nesta tabela.', + 'piggy-banks_index_saved' => 'Este campo mostra-lhe quanto já poupou em cada mealheiro.', + 'piggy-banks_index_button' => 'Ao lado desta barra de progresso existem 2 botões (+ e -) para adicionar ou remover dinheiro de cada mealheiro.', + 'piggy-banks_index_accountStatus' => 'Para cada conta de ativo com pelo menos um mealheiro, o estado e listado nesta tabela.', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -127,46 +127,46 @@ return [ // create piggy - 'piggy-banks_create_name' => 'Qual e o teu objectivo? Um sofa novo, uma camara, dinheiro para emergencias?', - 'piggy-banks_create_date' => 'Podes definir uma data alvo ou um prazo limite para o teu mealheiro.', + 'piggy-banks_create_name' => 'Qual é o seu objetivo? Um sofá novo, uma câmara, dinheiro para emergências?', + 'piggy-banks_create_date' => 'Pode definir uma data alvo ou um prazo limite para o seu mealheiro.', // show piggy 'piggy-banks_show_piggyChart' => 'Este gráfico mostra-lhe o histórico deste mealheiro.', - 'piggy-banks_show_piggyDetails' => 'Alguns detalhes sobre o teu mealheiro', - 'piggy-banks_show_piggyEvents' => 'Quaisquer adicoes ou remocoes tambem serao listadas aqui.', + 'piggy-banks_show_piggyDetails' => 'Alguns detalhes sobre o seu mealheiro', + 'piggy-banks_show_piggyEvents' => 'Quaisquer adições ou remoções também serão listadas aqui.', // bill index - 'bills_index_rules' => 'Aqui tu podes ver quais regras serao validadas se esta factura for atingida', - 'bills_index_paid_in_period' => 'Este campo indica o ultimo pagamento desta factura.', - 'bills_index_expected_in_period' => 'Este campo indica, para cada factura, se, e quando a proxima factura sera atingida.', + 'bills_index_rules' => 'Aqui pode ver que regras serão validadas se esta despesa ocorrer', + 'bills_index_paid_in_period' => 'Este campo indica o último pagamento desta despesa.', + 'bills_index_expected_in_period' => 'Este campo indica, para cada despesa, se, e quando, ocorrerá novamente.', // show bill - 'bills_show_billInfo' => 'Esta tabela mostra alguma informação geral sobre esta fatura.', - 'bills_show_billButtons' => 'Usa este botao para tornar a analizar transaccoes antigas para assim elas poderem ser associadas com esta factura.', - 'bills_show_billChart' => 'Este gráfico mostra as transações associadas a esta fatura.', + 'bills_show_billInfo' => 'Esta tabela mostra alguma informação geral sobre esta despesa.', + 'bills_show_billButtons' => 'Usa este botão para tornar a analisar transações antigas para ser feita correspondência com esta despesa.', + 'bills_show_billChart' => 'Este gráfico mostra as transações associadas a esta despesa.', // create bill - 'bills_create_intro' => 'Usa as facturas para controlares o montante de dinheiro e deves em cada periodo. Pensa nas despesas como uma renda, seguro ou pagamentos de hipotecas.', - 'bills_create_name' => 'Usa um nome bem descritivo como "Renda" ou "Seguro de Vida".', + 'bills_create_intro' => 'Use as Despesas para controlar o montante de dinheiro que terá de despender em cada período. Pensa nas despesas como rendas, seguros ou pagamentos de hipotecas.', + 'bills_create_name' => 'Use um nome descritivo como "Renda" ou "Seguro de Vida".', //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', - 'bills_create_amount_min_holder' => 'Selecciona um montante minimo e maximo para esta factura.', - 'bills_create_repeat_freq_holder' => 'A maioria das facturas sao mensais, mas podes definir outra frequencia de repeticao aqui.', - 'bills_create_skip_holder' => 'Se uma factura se repete a cada 2 semanas, o campo "pular" deve ser colocado como "1" para saltar toda a semana seguinte.', + 'bills_create_amount_min_holder' => 'Selecione um montante mínimo e máximo para esta despesa.', + 'bills_create_repeat_freq_holder' => 'A maioria das despesas são mensais, mas pode definir outra frequência aqui.', + 'bills_create_skip_holder' => 'Se uma despesa se repete a cada 2 semanas, o campo "saltar" deve ser colocado como "1" para saltar semana sim, semana não.', // rules index - 'rules_index_intro' => 'O Firefly III permite-te gerir as regras que vao ser aplicadas automaticamente a qualquer transaccao que crias ou alteras.', - 'rules_index_new_rule_group' => 'Podes combinar regras em grupos para uma gestao mais facil.', - 'rules_index_new_rule' => 'Cria quantas regras quiseres.', - 'rules_index_prio_buttons' => 'Ordena-as da forma que aches correcta.', - 'rules_index_test_buttons' => 'Podes testar as tuas regras ou aplica-las a transaccoes existentes.', - 'rules_index_rule-triggers' => 'As regras tem "disparadores" e "accoes" que podes ordenar com drag-and-drop.', - 'rules_index_outro' => 'Certifica-te que olhas a pagina de ajuda no icone (?) no canto superior direito!', + 'rules_index_intro' => 'O Firefly III permite-lhe gerir regras, que serão aplicadas automaticamente a qualquer transação que cria ou altere.', + 'rules_index_new_rule_group' => 'Pode combinar regras em grupos para uma gestão mais fácil.', + 'rules_index_new_rule' => 'Crie quantas regras quiser.', + 'rules_index_prio_buttons' => 'Ordene-as da forma que achar melhor.', + 'rules_index_test_buttons' => 'Pode testar as suas regras ou aplicá-las a transações existentes.', + 'rules_index_rule-triggers' => 'As regras têm "gatilhos" e "ações" que pode ordenar com drag-and-drop.', + 'rules_index_outro' => 'Certifique-se que consulta a página de ajuda no ícone (?) no canto superior direito!', // create rule: - 'rules_create_mandatory' => 'Escolhe um titulo descriptivo e define quando a regra deve ser disparada.', - 'rules_create_ruletriggerholder' => 'Adiciona a quantidade de disparadores que necessites, mas, lembra-te que TODOS os disparadores tem de coincidir antes de qualquer accao ser disparada.', - 'rules_create_test_rule_triggers' => 'Usa este botao para ver quais transaccoes podem coincidir com a tua regra.', - 'rules_create_actions' => 'Define todas as accoes que quiseres.', + 'rules_create_mandatory' => 'Escolha um título descritivo e defina quando a regra deve ser acionada.', + 'rules_create_ruletriggerholder' => 'Adicione todos os gatilhos que quiser, mas tenha presente que TODOS os gatilhos têm de ocorrer para que qualquer ação seja acionada.', + 'rules_create_test_rule_triggers' => 'Use este botão para ver que transações podem coincidir com a sua regra.', + 'rules_create_actions' => 'Defina todas as ações que quiser.', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. diff --git a/resources/lang/pt_PT/list.php b/resources/lang/pt_PT/list.php index 617380f710..7f1db4b614 100644 --- a/resources/lang/pt_PT/list.php +++ b/resources/lang/pt_PT/list.php @@ -44,21 +44,21 @@ return [ 'balance_before' => 'Saldo antes', 'balance_after' => 'Saldo depois', 'name' => 'Nome', - 'role' => 'Regra', - 'currentBalance' => 'Saldo actual', + 'role' => 'Perfil', + 'currentBalance' => 'Saldo atual', 'linked_to_rules' => 'Regras relevantes', - 'active' => 'Esta activo?', + 'active' => 'Esta ativo?', 'percentage' => '%.', 'recurring_transaction' => 'Transação recorrente', - 'next_due' => 'Próximo prazo', + 'next_due' => 'Próximo vencimento', 'transaction_type' => 'Tipo', - 'lastActivity' => 'Ultima actividade', - 'balanceDiff' => 'Diferenca de saldo', + 'lastActivity' => 'Última atividade', + 'balanceDiff' => 'Diferença de saldo', 'other_meta_data' => 'Outros meta dados', - 'invited_at' => 'Invited at', - 'expires' => 'Invitation expires', - 'invited_by' => 'Invited by', - 'invite_link' => 'Invite link', + 'invited_at' => 'Convidado em', + 'expires' => 'O convite expira', + 'invited_by' => 'Convidado por', + 'invite_link' => 'Link do convite', 'account_type' => 'Tipo de conta', 'created_at' => 'Criado em', 'account' => 'Conta', @@ -66,8 +66,8 @@ return [ 'matchingAmount' => 'Montante', 'destination' => 'Destino', 'source' => 'Origem', - 'next_expected_match' => 'Proxima correspondencia esperada', - 'automatch' => 'Auto correspondencia?', + 'next_expected_match' => 'Próxima correspondência esperada', + 'automatch' => 'Auto correspondência?', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -82,15 +82,15 @@ return [ 'repeat_freq' => 'Repete', - 'description' => 'Descricao', + 'description' => 'Descrição', 'amount' => 'Montante', 'date' => 'Data', - 'interest_date' => 'Taxa de juros', + 'interest_date' => 'Data de juros', 'book_date' => 'Data de registo', 'process_date' => 'Data de processamento', 'due_date' => 'Data de vencimento', 'payment_date' => 'Data de pagamento', - 'invoice_date' => 'Data da factura', + 'invoice_date' => 'Data da fatura', 'internal_reference' => 'Referência interna', 'notes' => 'Notas', 'from' => 'De', @@ -109,24 +109,24 @@ return [ 'paid_current_period' => 'Pago este periodo', 'email' => 'Email', 'registered_at' => 'Registado em', - 'is_blocked' => 'Esta bloqueado', + 'is_blocked' => 'Está bloqueado', 'is_admin' => 'Administrador', - 'has_two_factor' => 'Tem autenticacao 2 passos', - 'blocked_code' => 'Codigo de bloqueio', + 'has_two_factor' => 'Tem 2FA', + 'blocked_code' => 'Código de bloqueio', 'source_account' => 'Conta de origem', 'destination_account' => 'Conta de destino', - 'accounts_count' => 'Numero de contas', - 'journals_count' => 'Numero de transações', - 'attachments_count' => 'Numero de anexos', - 'bills_count' => 'Numero de facturas', - 'categories_count' => 'Numero de categorias', - 'budget_count' => 'Numero de orçamentos', - 'rule_and_groups_count' => 'Numero de regras e grupos de regras', - 'tags_count' => 'Numero de etiquetas', + 'accounts_count' => 'Número de contas', + 'journals_count' => 'Número de transações', + 'attachments_count' => 'Número de anexos', + 'bills_count' => 'Número de faturas', + 'categories_count' => 'Número de categorias', + 'budget_count' => 'Número de orçamentos', + 'rule_and_groups_count' => 'Número de regras e grupos de regras', + 'tags_count' => 'Número de etiquetas', 'tags' => 'Etiquetas', - 'inward' => 'Descricao interna', - 'outward' => 'Descricao externa', - 'number_of_transactions' => 'Numero de transações', + 'inward' => 'Descrição interna', + 'outward' => 'Descrição externa', + 'number_of_transactions' => 'Número de transações', 'total_amount' => 'Montante total', 'sum' => 'Soma', 'sum_excluding_transfers' => 'Soma (excluindo transferências)', @@ -134,17 +134,17 @@ return [ 'sum_deposits' => 'Soma dos depósitos', 'sum_transfers' => 'Soma das transferências', 'sum_reconciliations' => 'Soma das reconciliações', - 'reconcile' => 'Reconciliacao', + 'reconcile' => 'Reconciliação', 'sepa_ct_id' => 'SEPA Identificador ponta-a-ponta', 'sepa_ct_op' => 'Identificador SEPA da conta oposta', 'sepa_db' => 'Mandato Identificador SEPA', 'sepa_country' => 'País SEPA', - 'sepa_cc' => 'Código SEPA de Área Única de Pagamento em Euros', + 'sepa_cc' => 'SEPA Clearing Code', 'sepa_ep' => 'Finalidade Externa SEPA', 'sepa_ci' => 'Identificador do credor SEPA', 'sepa_batch_id' => 'ID do Lote SEPA', 'external_id' => 'ID Externo', - 'account_at_bunq' => 'Conta com bunq', + 'account_at_bunq' => 'Conta no bunq', 'file_name' => 'Nome do ficheiro', /* @@ -164,12 +164,12 @@ return [ 'attached_to' => 'Anexado a', 'file_exists' => 'O ficheiro existe', 'spectre_bank' => 'Banco', - 'spectre_last_use' => 'Ultimo login', + 'spectre_last_use' => 'Último início de sessão', 'spectre_status' => 'Estado', 'bunq_payment_id' => 'ID de pagamento bunq', - 'repetitions' => 'Repeticoes', - 'title' => 'Titulo', - 'transaction_s' => 'Transaccao(oes)', + 'repetitions' => 'Repetições', + 'title' => 'Título', + 'transaction_s' => 'Transação(ões)', 'field' => 'Campo', 'value' => 'Valor', 'interest' => 'Juro', @@ -180,11 +180,11 @@ return [ 'payment_info' => 'Informações de pagamento', 'expected_info' => 'Próxima transação esperada', 'start_date' => 'Data de inicio', - 'trigger' => 'Trigger', - 'response' => 'Response', - 'delivery' => 'Delivery', + 'trigger' => 'Gatilho', + 'response' => 'Resposta', + 'delivery' => 'Entrega', 'url' => 'URL', - 'secret' => 'Secret', + 'secret' => 'Segredo', ]; /* diff --git a/resources/lang/pt_PT/validation.php b/resources/lang/pt_PT/validation.php index 39d3828515..73c8e8fabe 100644 --- a/resources/lang/pt_PT/validation.php +++ b/resources/lang/pt_PT/validation.php @@ -35,64 +35,64 @@ declare(strict_types=1); return [ - 'missing_where' => 'A matriz tem em falha a cláusula-"onde"', - 'missing_update' => 'A matriz tem em falha a cláusula-"atualizar"', + 'missing_where' => 'A matriz tem em falta a cláusula-"onde"', + 'missing_update' => 'A matriz tem em falta a cláusula-"atualizar"', 'invalid_where_key' => 'JSON contém uma chave inválida para a cláusula "onde"', 'invalid_update_key' => 'JSON contém uma chave inválida para a cláusula "atualizar"', 'invalid_query_data' => 'Existem dados inválidos no campo %s:%s do seu inquérito.', - 'invalid_query_account_type' => 'O seu inquérito contem contas de tipos diferentes, o que não é permitido.', - 'invalid_query_currency' => 'O seu inquérito contem contas com configurações de moeda diferentes, o que não é permitido.', + 'invalid_query_account_type' => 'O seu inquérito contém contas de tipos diferentes, o que não é permitido.', + 'invalid_query_currency' => 'O seu inquérito contém contas com configurações de moeda diferentes, o que não é permitido.', 'iban' => 'Este IBAN não é valido.', 'zero_or_more' => 'O valor não pode ser negativo.', - 'date_or_time' => 'O valor deve ser uma data válida ou hora (ISO 8601).', - 'source_equals_destination' => 'A conta de origem é igual a conta de destino.', - 'unique_account_number_for_user' => 'Este numero de conta já esta em uso.', - 'unique_iban_for_user' => 'Este IBAN já esta em uso.', - 'deleted_user' => 'Devido a motivos de segurança, não se pode registar com este e-mail.', - 'rule_trigger_value' => 'Este valor e invalido para o disparador seleccionado.', - 'rule_action_value' => 'Este valor e invalido para a accao seleccionada.', - 'file_already_attached' => 'O ficheiro ":name" carregado ja esta anexado a este objecto.', + 'date_or_time' => 'O valor deve ser uma data ou hora válida (ISO 8601).', + 'source_equals_destination' => 'A conta de origem é igual à conta de destino.', + 'unique_account_number_for_user' => 'Parece que este número de conta já está em uso.', + 'unique_iban_for_user' => 'Parece que este IBAN já está em uso.', + 'deleted_user' => 'Devido a motivos de segurança, não se pode registar com este email.', + 'rule_trigger_value' => 'Este valor é inválido para o gatilho selecionado.', + 'rule_action_value' => 'Este valor é inválido para a ação selecionada.', + 'file_already_attached' => 'O ficheiro ":name" carregado já está anexado a este objeto.', 'file_attached' => 'Ficheiro carregado com sucesso ":name".', - 'must_exist' => 'O ID no campo :attribute nao existe em base de dados.', - 'all_accounts_equal' => 'Todas as contas neste campo tem de ser iguais.', - 'group_title_mandatory' => 'Um título de grupo é obrigatório quando existe mais de uma transacção.', + 'must_exist' => 'O ID no campo :attribute não existe na base de dados.', + 'all_accounts_equal' => 'Todas as contas neste campo têm de ser iguais.', + 'group_title_mandatory' => 'Um título de grupo é obrigatório quando existe mais de uma transação.', 'transaction_types_equal' => 'Todas as divisões devem ser do mesmo tipo.', - 'invalid_transaction_type' => 'Tipo de transacção inválido.', - 'invalid_selection' => 'A tua seleccao e invalida.', - 'belongs_user' => 'O valor e invalido para este campo.', - 'at_least_one_transaction' => 'Necessita de, pelo menos, uma transaccao.', - 'at_least_one_repetition' => 'Necessita de, pelo menos, uma repeticao.', - 'require_repeat_until' => 'Preencher um numero de repeticoes, ou uma data de fim (repeat_until). Nao ambos.', - 'require_currency_info' => 'O conteudo deste campo e invalido sem as informacoes da divisa.', + 'invalid_transaction_type' => 'Tipo de transação inválido.', + 'invalid_selection' => 'A sua seleção é invalida.', + 'belongs_user' => 'Este valor é inválido para este campo.', + 'at_least_one_transaction' => 'Necessita de, pelo menos, uma transação.', + 'at_least_one_repetition' => 'Necessita de, pelo menos, uma repetição.', + 'require_repeat_until' => 'Preencher um número de repetições, ou uma data de fim (repeat_until). Não ambos.', + 'require_currency_info' => 'O conteúdo deste campo é inválido sem a informação da moeda.', 'not_transfer_account' => 'Esta conta não pode ser utilizada para transferências.', - 'require_currency_amount' => 'O conteúdo deste campo é inválido sem a informação da moeda estrangeira.', - 'require_foreign_currency' => 'This field requires a number', - 'require_foreign_dest' => 'This field value must match the currency of the destination account.', - 'require_foreign_src' => 'This field value must match the currency of the source account.', - 'equal_description' => 'A descricao da transaccao nao deve ser igual a descricao global.', - 'file_invalid_mime' => 'O ficheiro ":name" e do tipo ":mime" que nao e aceite como um novo upload.', - 'file_too_large' => 'O ficheiro ":name" e demasiado grande.', - 'belongs_to_user' => 'O valor de :attribute e desconhecido.', + 'require_currency_amount' => 'O conteúdo deste campo é inválido sem o montante da moeda estrangeira.', + 'require_foreign_currency' => 'Este campo requer um número', + 'require_foreign_dest' => 'O valor deste campo deve utilizar a mesma moeda da conta de destino.', + 'require_foreign_src' => 'O valor deste campo deve utilizar a mesma moeda da conta de origem.', + 'equal_description' => 'A descrição da transação não deve ser igual à descrição global.', + 'file_invalid_mime' => 'O ficheiro ":name" é do tipo ":mime" que não é aceite para carregamento.', + 'file_too_large' => 'O ficheiro ":name" é demasiado grande.', + 'belongs_to_user' => 'O valor de :attribute é desconhecido.', 'accepted' => 'O :attribute tem de ser aceite.', - 'bic' => 'Este BIC nao e valido.', - 'at_least_one_trigger' => 'A regra tem de ter, pelo menos, um disparador.', - 'at_least_one_active_trigger' => 'Rule must have at least one active trigger.', - 'at_least_one_action' => 'A regra tem de ter, pelo menos, uma accao.', - 'at_least_one_active_action' => 'Rule must have at least one active action.', - 'base64' => 'Estes dados nao sao validos na codificacao em base648.', - 'model_id_invalid' => 'O ID inserido e invalida para este modelo.', - 'less' => ':attribute tem de ser menor que 10,000,000', - 'active_url' => 'O :attribute nao e um URL valido.', - 'after' => 'I :attribute tem de ser uma data depois de :date.', + 'bic' => 'Este BIC não é válido.', + 'at_least_one_trigger' => 'A regra tem de ter, pelo menos, um gatilho.', + 'at_least_one_active_trigger' => 'A regra deve ter pelo menos um gatilho ativo.', + 'at_least_one_action' => 'A regra tem de ter, pelo menos, uma ação.', + 'at_least_one_active_action' => 'A regra deve ter pelo menos uma ação ativa.', + 'base64' => 'Estes dados não são válidos na codificação base64.', + 'model_id_invalid' => 'O ID inserido é inválido para este modelo.', + 'less' => ':attribute tem de ser menor que 10.000.000', + 'active_url' => 'O :attribute não é um URL válido.', + 'after' => 'I :attribute tem de ser uma data posterior a :date.', 'date_after' => 'A data de início deve ser anterior à data de fim.', 'alpha' => 'O :attribute apenas pode conter letras.', - 'alpha_dash' => 'O :attribute apenas pode conter letras, numero e tracos.', - 'alpha_num' => 'O :attribute apenas pode conter letras e numeros.', - 'array' => 'O :attribute tem de ser um array.', - 'unique_for_user' => 'Ja existe uma entrada com este :attribute.', - 'before' => 'O :attribute tem de ser uma data antes de :date.', - 'unique_object_for_user' => 'Este nome ja esta em uso.', - 'unique_account_for_user' => 'Este nome de conta ja esta em uso.', + 'alpha_dash' => 'O :attribute apenas pode conter letras, número e traços.', + 'alpha_num' => 'O :attribute apenas pode conter letras e números.', + 'array' => 'O :attribute tem de ser uma matriz.', + 'unique_for_user' => 'Já existe uma entrada com este :attribute.', + 'before' => 'O :attribute tem de ser uma data anterior a :date.', + 'unique_object_for_user' => 'Este nome já está em uso.', + 'unique_account_for_user' => 'Este nome de conta já está em uso.', /* * PLEASE DO NOT EDIT THIS FILE DIRECTLY. @@ -108,39 +108,39 @@ return [ 'between.numeric' => 'O :attribute tem de estar entre :min e :max.', 'between.file' => 'O :attribute tem de estar entre :min e :max kilobytes.', - 'between.string' => 'O :attribute tem de ter entre :min e :max caracteres.', + 'between.string' => 'O :attribute tem de ter entre :min e :max carateres.', 'between.array' => 'O :attribute tem de ter entre :min e :max itens.', 'boolean' => 'O campo :attribute tem de ser verdadeiro ou falso.', - 'confirmed' => 'A confirmacao de :attribute nao coincide.', - 'date' => 'A :attribute nao e uma data valida.', - 'date_format' => 'O :attribute nao corresponde ao formato :format.', - 'different' => 'O :attribute e :other tem de ser diferentes.', - 'digits' => 'O :attribute tem de ter :digits digitos.', - 'digits_between' => 'O :attribute tem de ter entre :min e :max digitos.', - 'email' => 'O :attribute tem de ser um email valido.', - 'filled' => 'O campo :attribute e obrigatorio.', - 'exists' => 'O :attribute seleccionado e invalido.', + 'confirmed' => 'A confirmação de :attribute não coincide.', + 'date' => ':attribute não é uma data válida.', + 'date_format' => 'O :attribute não corresponde ao formato :format.', + 'different' => 'O :attribute e :other têm de ser diferentes.', + 'digits' => 'O :attribute tem de ter :digits dígitos.', + 'digits_between' => 'O :attribute tem de ter entre :min e :max dígitos.', + 'email' => 'O :attribute tem de ser um endereço de email válido.', + 'filled' => 'O campo :attribute é obrigatório.', + 'exists' => 'O :attribute selecionado é inválido.', 'image' => 'O :attribute tem de ser uma imagem.', - 'in' => 'O :attribute seleccionado e invalido.', + 'in' => 'O :attribute selecionado é inválido.', 'integer' => 'O :attribute tem de ser um inteiro.', 'ip' => 'O :attribute tem de ser um endereco IP valido.', 'json' => 'O :attribute tem de ser uma string JSON valida.', 'max.numeric' => 'O :attribute nao pode ser maior que :max.', - 'max.file' => 'O :attribute nao pode ter mais que :max kilobytes.', - 'max.string' => 'O :attribute nao pode ter mais que :max caracteres.', - 'max.array' => 'O :attribute nao pode ter mais que :max itens.', + 'max.file' => 'O :attribute não pode ter mais que :max kilobytes.', + 'max.string' => 'O :attribute não pode ter mais que :max carateres.', + 'max.array' => 'O :attribute não pode ter mais que :max itens.', 'mimes' => 'O :attribute tem de ser um ficheiro do tipo :values.', - 'min.numeric' => 'O :attribute tem de ter pelo menos :min.', - 'lte.numeric' => 'O :attribute tem de ser menor ou igual que :value.', + 'min.numeric' => 'O :attribute tem de ser pelo menos :min.', + 'lte.numeric' => 'O :attribute tem de ser menor ou igual a :value.', 'min.file' => 'O :attribute tem de ter, pelo menos, :min kilobytes.', - 'min.string' => 'O :attribute tem de ter, pelo menos, :min caracteres.', + 'min.string' => 'O :attribute tem de ter, pelo menos, :min carateres.', 'min.array' => 'O :attribute tem de ter, pelo menos, :min itens.', - 'not_in' => 'O :attribute seleccionado e invalido.', - 'numeric' => 'O :attribute tem de ser um numero.', - 'numeric_native' => 'O montante nativo tem de ser um numero.', - 'numeric_destination' => 'O montante de destino tem de ser um numero.', - 'numeric_source' => 'O montante de origem tem de ser um numero.', - 'regex' => 'O formato do :attribute e invalido.', + 'not_in' => 'O :attribute selecionado é inválido.', + 'numeric' => 'O :attribute tem de ser um número.', + 'numeric_native' => 'O montante nativo tem de ser um número.', + 'numeric_destination' => 'O montante de destino tem de ser um número.', + 'numeric_source' => 'O montante de origem tem de ser um número.', + 'regex' => 'O formato do :attribute é inválido.', 'required' => 'O campo :attribute e obrigatorio.', 'required_if' => 'O campo :attribute e obrigatorio quando :other e :value.', 'required_unless' => 'O campo :attribute e obrigatorio, a menos que :other esteja em :values.', @@ -156,22 +156,22 @@ return [ 'size.array' => 'O :attribute tem de conter :size itens.', 'unique' => 'O :attribute ja foi usado.', 'string' => 'O :attribute tem de ser uma string.', - 'url' => 'O formato do :attribute e invalido.', - 'timezone' => 'O :attribute tem de ser uma zona valida.', - '2fa_code' => 'O campo :attribute e invalido.', - 'dimensions' => 'O :attribute tem dimensoes de imagens incorrectas.', + 'url' => 'O formato do :attribute é inválido.', + 'timezone' => 'O :attribute tem de ser uma zona válida.', + '2fa_code' => 'O campo :attribute é inválido.', + 'dimensions' => 'O :attribute tem dimensões de imagens incorretas.', 'distinct' => 'O campo :attribute tem um valor duplicado.', 'file' => 'O :attribute tem de ser um ficheiro.', - 'in_array' => 'O campo :attribute nao existe em :other.', + 'in_array' => 'O campo :attribute não existe em :other.', 'present' => 'O campo :attribute tem de estar presente.', - 'amount_zero' => 'O montante total nao pode ser 0.', - 'current_target_amount' => 'O valor actual deve ser menor que o valor pretendido.', - 'unique_piggy_bank_for_user' => 'O nome do mealheiro tem de ser unico.', - 'unique_object_group' => 'O nome do grupo tem que ser único', + 'amount_zero' => 'O montante total não pode ser 0.', + 'current_target_amount' => 'O valor atual deve ser inferior ao valor pretendido.', + 'unique_piggy_bank_for_user' => 'O nome do mealheiro tem de ser único.', + 'unique_object_group' => 'O nome do grupo tem de ser único', 'starts_with' => 'O valor deve começar com :values.', - 'unique_webhook' => 'You already have a webhook with this combination of URL, trigger, response and delivery.', - 'unique_existing_webhook' => 'You already have another webhook with this combination of URL, trigger, response and delivery.', - 'same_account_type' => 'Ambas as contas devem ser do mesmo tipo de conta', + 'unique_webhook' => 'Já existe um webhook com esta combinação de URL, gatilho, resposta e entrega.', + 'unique_existing_webhook' => 'Já existe outro webhook com esta combinação de URL, gatilho, resposta e entrega.', + 'same_account_type' => 'Ambas as contas devem ser do mesmo tipo', 'same_account_currency' => 'Ambas as contas devem ter a mesma moeda configurada', /* @@ -186,40 +186,40 @@ return [ */ - 'secure_password' => 'Esta nao e uma password segura. Tenta de novo por favor. Para mais informacoes visita https://bit.ly/FF3-password-security', + 'secure_password' => 'Esta não é uma palavra-passe segura. Tente de novo por favor. Para mais informações visite https://bit.ly/FF3-password-security', 'valid_recurrence_rep_type' => 'Tipo de repetição inválido para transações recorrentes.', - 'valid_recurrence_rep_moment' => 'Dia invalido para este tipo de repeticao.', - 'invalid_account_info' => 'Informação de conta invalida.', + 'valid_recurrence_rep_moment' => 'Momento inválido para este tipo de repetição.', + 'invalid_account_info' => 'Informação de conta inválida.', 'attributes' => [ - 'email' => 'endereco de email', - 'description' => 'descricao', + 'email' => 'endereço de email', + 'description' => 'descrição', 'amount' => 'montante', - 'transactions.*.amount' => 'valor de transação', + 'transactions.*.amount' => 'montante da transação', 'name' => 'nome', 'piggy_bank_id' => 'ID do mealheiro', 'targetamount' => 'montante alvo', 'opening_balance_date' => 'data do saldo inicial', 'opening_balance' => 'saldo inicial', 'match' => 'corresponder', - 'amount_min' => 'montante minimo', - 'amount_max' => 'montante maximo', - 'title' => 'titulo', + 'amount_min' => 'montante mínimo', + 'amount_max' => 'montante máximo', + 'title' => 'título', 'tag' => 'etiqueta', - 'transaction_description' => 'descricao da transaccao', - 'rule-action-value.1' => 'valor da accao da regra #1', - 'rule-action-value.2' => 'valor da accao da regra #2', - 'rule-action-value.3' => 'valor da accao da regra #3', - 'rule-action-value.4' => 'valor da accao da regra #4', - 'rule-action-value.5' => 'valor da accao da regra #5', - 'rule-action.1' => 'accao da regra #1', - 'rule-action.2' => 'accao da regra #2', - 'rule-action.3' => 'accao da regra #3', - 'rule-action.4' => 'accao da regra #4', - 'rule-action.5' => 'accao da regra #5', - 'rule-trigger-value.1' => 'valor de disparo da regra #1', - 'rule-trigger-value.2' => 'valor de disparo da regra #2', - 'rule-trigger-value.3' => 'valor de disparo da regra #3', - 'rule-trigger-value.4' => 'valor de disparo da regra #4', + 'transaction_description' => 'descrição da transação', + 'rule-action-value.1' => 'valor da ação da regra #1', + 'rule-action-value.2' => 'valor da ação da regra #2', + 'rule-action-value.3' => 'valor da ação da regra #3', + 'rule-action-value.4' => 'valor da ação da regra #4', + 'rule-action-value.5' => 'valor da ação da regra #5', + 'rule-action.1' => 'ação da regra #1', + 'rule-action.2' => 'ação da regra #2', + 'rule-action.3' => 'ação da regra #3', + 'rule-action.4' => 'ação da regra #4', + 'rule-action.5' => 'ação da regra #5', + 'rule-trigger-value.1' => 'valor do gatilho da regra #1', + 'rule-trigger-value.2' => 'valor do gatilho da regra #2', + 'rule-trigger-value.3' => 'valor do gatilho da regra #3', + 'rule-trigger-value.4' => 'valor do gatilho da regra #4', 'rule-trigger-value.5' => 'valor de disparo da regra #5', 'rule-trigger.1' => 'disparo da regra #1', 'rule-trigger.2' => 'disparo da regra #2', @@ -234,7 +234,7 @@ return [ 'withdrawal_dest_need_data' => 'É necessário ter um ID de conta de destino válido e/ou um nome de conta de destino válido para continuar.', 'withdrawal_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar pelo ID ":id" ou nome ":name".', - 'reconciliation_source_bad_data' => 'Could not find a valid reconciliation account when searching for ID ":id" or name ":name".', + 'reconciliation_source_bad_data' => 'Não foi possível encontrar uma conta de reconciliação válida ao procurar pela ID ":id" ou pelo nome ":name".', 'generic_source_bad_data' => 'Não foi possível encontrar uma conta de origem válida ao pesquisar pelo ID ":id" ou nome ":name".', @@ -266,16 +266,16 @@ return [ 'lc_source_need_data' => 'É necessário obter um ID de uma conta de origem válida para continuar.', 'ob_dest_need_data' => 'É necessário ter um ID de conta de destino válido e/ou um nome de conta de destino válido para continuar.', 'ob_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar pelo ID ":id" ou nome ":name".', - 'reconciliation_either_account' => 'To submit a reconciliation, you must submit either a source or a destination account. Not both, not neither.', + 'reconciliation_either_account' => 'Ao submeter a reconciliação, tem de submeter a conta de origem ou a conta de destino. Não ambas ou nenhuma.', 'generic_invalid_source' => 'Não pode utilizar esta conta como conta de origem.', 'generic_invalid_destination' => 'Não pode utilizar esta conta como conta de destino.', - 'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.', - 'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.', + 'generic_no_source' => 'Tem de submeter a informação de uma conta de origem ou uma ID de diário de transações.', + 'generic_no_destination' => 'Tem de submeter a informação de uma conta de destino ou uma ID de diário de transações.', 'gte.numeric' => 'O :attribute deve ser maior ou igual a :value.', - 'gt.numeric' => 'O :attribute deve ser maior que :value.', + 'gt.numeric' => 'O :attribute deve ser superior a :value.', 'gte.file' => 'O :attribute deve ser maior ou igual a :value kilobytes.', 'gte.string' => 'O :attribute deve ser maior ou igual a :value caracteres.', 'gte.array' => 'O :attribute deve ter :value items ou mais.', @@ -285,7 +285,7 @@ return [ 'auto_budget_period_mandatory' => 'O período de orçamento automático é um campo obrigatório.', // no access to administration: - 'no_access_user_group' => 'You do not have the correct access rights for this administration.', + 'no_access_user_group' => 'Não tem as permissões de acesso necessárias para esta administração.', ]; /* diff --git a/resources/lang/uk_UA/firefly.php b/resources/lang/uk_UA/firefly.php index ca9c98af52..8b3e3cf759 100644 --- a/resources/lang/uk_UA/firefly.php +++ b/resources/lang/uk_UA/firefly.php @@ -361,7 +361,7 @@ return [ 'search_modifier_external_id_is' => 'Зовнішній ID - ":value"', 'search_modifier_not_external_id_is' => 'Зовнішній ідентифікатор не ":value"', 'search_modifier_no_external_url' => 'Операція не має зовнішнього URL', - 'search_modifier_no_external_id' => 'The transaction has no external ID', + 'search_modifier_no_external_id' => 'Операція не має зовнішнього ID', 'search_modifier_not_any_external_url' => 'Операція не має зовнішнього URL', 'search_modifier_not_any_external_id' => 'The transaction has no external ID', 'search_modifier_any_external_url' => 'Операція повинна мати зовнішні URL-адреси (будь-який)', @@ -1239,7 +1239,7 @@ return [ 'rule_action_remove_all_tags_choice' => 'Видалити усі теги', 'rule_action_set_description_choice' => 'Set description to ..', 'rule_action_update_piggy_choice' => 'Add / remove transaction amount in piggy bank ..', - 'rule_action_update_piggy' => 'Add / remove transaction amount in piggy bank ":action_value"', + 'rule_action_update_piggy' => 'Додати/видалити суму транзакції в скарбничці ":action_value"', 'rule_action_append_description_choice' => 'Append description with ..', 'rule_action_prepend_description_choice' => 'Prepend description with ..', 'rule_action_set_source_account_choice' => 'Set source account to ..', @@ -1306,7 +1306,7 @@ return [ 'dark_mode_option_light' => 'Always light', 'dark_mode_option_dark' => 'Always dark', 'equal_to_language' => '(прирівнюється до мови)', - 'dark_mode_preference' => 'Dark mode', + 'dark_mode_preference' => 'Темний режим', 'dark_mode_preference_help' => 'Tell Firefly III when to use dark mode.', 'pref_home_screen_accounts' => 'Головна сторінка рахунків', 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', @@ -1318,10 +1318,10 @@ return [ 'pref_3M' => 'Три місяці (квартал)', 'pref_6M' => 'Шість місяців', 'pref_1Y' => 'Один рік', - 'pref_last365' => 'Last year', - 'pref_last90' => 'Last 90 days', - 'pref_last30' => 'Last 30 days', - 'pref_last7' => 'Last 7 days', + 'pref_last365' => 'Минулий рік', + 'pref_last90' => 'Останні 90 днів', + 'pref_last30' => 'Останні 30 днів', + 'pref_last7' => 'Останні 7 днів', 'pref_YTD' => 'Year to date', 'pref_QTD' => 'Quarter to date', 'pref_MTD' => 'Month to date', @@ -1332,7 +1332,7 @@ return [ 'pref_locale_no_demo' => 'This feature won\'t work for the demo user.', 'pref_custom_fiscal_year' => 'Налаштування фінансового року', 'pref_custom_fiscal_year_label' => 'Увiмкнено', - 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', + 'pref_custom_fiscal_year_help' => 'У країнах, які використовують фінансовий рік, крім 1 січня до 31 грудня, ви можете увімкнути це і вказати початкові/кінцеві дні фіскального року', 'pref_fiscal_year_start_label' => 'Дата початку фіскального року', 'pref_two_factor_auth' => 'Двоетапна перевірка', 'pref_two_factor_auth_help' => 'При включенні двохетапної перевірки (також відомої як двофакторна автентифікація), ви додаєте додатковий шар безпеки для вашого облікового запису. Ви здійснюєте вхід за допомогою того, що знаєте (свій пароль) і того, що ви маєте (код підтвердження). Коди для підтвердження генерується додатком на вашому телефоні, таким як Authy або Google Authenticator.', @@ -1346,46 +1346,46 @@ return [ '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', '2fa_already_enabled' => '2-step verification is already enabled.', - 'wrong_mfa_code' => 'This MFA code is not valid.', + 'wrong_mfa_code' => 'Цей MFA код не дійсний.', 'pref_save_settings' => 'Зберегти налаштування', - 'saved_preferences' => 'Preferences saved!', + 'saved_preferences' => 'Налаштування збережено!', 'preferences_general' => 'Загальні', 'preferences_frontpage' => 'Головний екран', 'preferences_security' => 'Безпека', 'preferences_layout' => 'Зовнішній вигляд', - 'preferences_notifications' => 'Notifications', + 'preferences_notifications' => 'Сповіщення', 'pref_home_show_deposits' => 'Показувати надходження на головному екрані', 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', + 'pref_home_do_show_deposits' => 'Так, показати їх', 'successful_count' => 'of which :count successful', 'list_page_size_title' => 'Розмір сторінки', 'list_page_size_help' => 'Any list of things (accounts, transactions, etc) shows at most this many per page.', 'list_page_size_label' => 'Розмір сторінки', 'between_dates' => '(:start і :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', + 'pref_optional_fields_transaction' => 'Необов\'язкові поля для операцій', 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', - 'optional_tj_date_fields' => 'Date fields', - 'optional_tj_other_fields' => 'Other fields', - 'optional_tj_attachment_fields' => 'Attachment fields', - 'pref_optional_tj_interest_date' => 'Interest date', - 'pref_optional_tj_book_date' => 'Book date', - 'pref_optional_tj_process_date' => 'Processing date', - 'pref_optional_tj_due_date' => 'Due date', + 'optional_tj_date_fields' => 'Поле дати', + 'optional_tj_other_fields' => 'Інші поля', + 'optional_tj_attachment_fields' => 'Поля вкладення', + 'pref_optional_tj_interest_date' => 'Дата нарахування відсотку', + 'pref_optional_tj_book_date' => 'Дата обліку', + 'pref_optional_tj_process_date' => 'Дата опрацювання', + 'pref_optional_tj_due_date' => 'Дата закінчення', 'pref_optional_tj_payment_date' => 'Дата оплати', - 'pref_optional_tj_invoice_date' => 'Invoice date', - 'pref_optional_tj_internal_reference' => 'Internal reference', + 'pref_optional_tj_invoice_date' => 'Дата рахунку', + 'pref_optional_tj_internal_reference' => 'Внутрішнє посилання', 'pref_optional_tj_notes' => 'Нотатки', - 'pref_optional_tj_attachments' => 'Attachments', - 'pref_optional_tj_external_url' => 'External URL', - 'pref_optional_tj_location' => 'Location', + 'pref_optional_tj_attachments' => 'Вкладення', + 'pref_optional_tj_external_url' => 'Зовнішній URL', + 'pref_optional_tj_location' => 'Розташування', 'pref_optional_tj_links' => 'Transaction links', - 'optional_field_meta_dates' => 'Dates', + 'optional_field_meta_dates' => 'Дати', 'optional_field_meta_business' => 'Business', 'optional_field_attachments' => 'Вкладення', 'optional_field_meta_data' => 'Optional meta data', 'external_url' => 'Зовнішній URL', - 'pref_notification_bill_reminder' => 'Reminder about expiring bills', - 'pref_notification_new_access_token' => 'Alert when a new API access token is created', + 'pref_notification_bill_reminder' => 'Нагадування про термін дії рахунків', + 'pref_notification_new_access_token' => 'Попереджати, коли створюється новий токен доступу до API', 'pref_notification_transaction_creation' => 'Alert when a transaction is created automatically', 'pref_notification_user_login' => 'Alert when you login from a new location', 'pref_notifications' => 'Сповіщення', @@ -1395,7 +1395,7 @@ return [ 'slack_url_label' => 'URL-адреса Slack "вхідного вебхуку"', // Financial administrations - 'administration_index' => 'Financial administration', + 'administration_index' => 'Фінансове управління', // profile: 'purge_data_title' => 'Очистити дані з Firefly III', @@ -1512,11 +1512,11 @@ return [ 'profile_create_new_token' => 'Створити новий токен', 'profile_create_token' => 'Створити токен', 'profile_create' => 'Створити', - 'profile_save_changes' => 'Save changes', - 'profile_whoops' => 'Whoops!', - 'profile_something_wrong' => 'Something went wrong!', - 'profile_try_again' => 'Something went wrong. Please try again.', - 'amounts' => 'Amounts', + 'profile_save_changes' => 'Зберегти зміни', + 'profile_whoops' => 'Лишенько!', + 'profile_something_wrong' => 'Щось пішло не так!', + 'profile_try_again' => 'Щось пішло не так. Будь ласка, спробуйте ще раз.', + 'amounts' => 'Суми', 'multi_account_warning_unknown' => 'Depending on the type of transaction you create, the source and/or destination account of subsequent splits may be overruled by whatever is defined in the first split of the transaction.', 'multi_account_warning_withdrawal' => 'Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.', 'multi_account_warning_deposit' => 'Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.', @@ -1535,25 +1535,25 @@ return [ // export data: - 'export_data_title' => 'Export data from Firefly III', - 'export_data_menu' => 'Export data', - 'export_data_bc' => 'Export data from Firefly III', - 'export_data_main_title' => 'Export data from Firefly III', + 'export_data_title' => 'Експорт даних з Firefly III', + 'export_data_menu' => 'Експорт даних', + 'export_data_bc' => 'Експорт даних з Firefly III', + 'export_data_main_title' => 'Експорт даних з Firefly III', 'export_data_expl' => 'This link allows you to export all transactions + meta data from Firefly III. Please refer to the help (top right (?)-icon) for more information about the process.', - 'export_data_all_transactions' => 'Export all transactions', + 'export_data_all_transactions' => 'Експорт всіх транзакцій', 'export_data_advanced_expl' => 'If you need a more advanced or specific type of export, read the help on how to use the console command php artisan help firefly-iii:export-data.', // attachments 'nr_of_attachments' => 'One attachment|:count attachments', - 'attachments' => 'Attachments', - 'edit_attachment' => 'Edit attachment ":name"', - 'update_attachment' => 'Update attachment', - 'delete_attachment' => 'Delete attachment ":name"', - 'attachment_deleted' => 'Deleted attachment ":name"', + 'attachments' => 'Вкладення', + 'edit_attachment' => 'Редагувати вкладення ":name"', + 'update_attachment' => 'Оновлення вкладення', + 'delete_attachment' => 'Видалити вкладення":name"', + 'attachment_deleted' => 'Видалено вкладення":name"', 'liabilities_deleted' => 'Deleted liability ":name"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - 'list_all_attachments' => 'List of all attachments', + 'attachment_updated' => 'Оновлено вкладення":name"', + 'upload_max_file_size' => 'Максимальний розмір файлу: :size', + 'list_all_attachments' => 'Список всіх вкладень', // transaction index 'title_expenses' => 'Витрати', @@ -1563,7 +1563,7 @@ return [ 'title_transfer' => 'Переказ', 'title_transfers' => 'Перекази', 'submission_options' => 'Submission options', - 'apply_rules_checkbox' => 'Apply rules', + 'apply_rules_checkbox' => 'Застосувати правила', 'fire_webhooks_checkbox' => 'Fire webhooks', // convert stuff: @@ -1599,7 +1599,7 @@ return [ 'convert_select_destinations' => 'To complete the conversion, please select the new destination account below.|To complete the conversion, please select the new destination accounts below.', 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', 'converted_to_Deposit' => 'The transaction has been converted to a deposit', - 'converted_to_Transfer' => 'The transaction has been converted to a transfer', + 'converted_to_Transfer' => 'Транзакцію перетворено на переказ', 'invalid_convert_selection' => 'The account you have selected is already used in this transaction or does not exist.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', 'convert_to_withdrawal' => 'Перетворити на витрату', @@ -1607,12 +1607,12 @@ return [ 'convert_to_transfer' => 'Перетворити на переказ', // create new stuff: - 'create_new_withdrawal' => 'Create new withdrawal', - 'create_new_deposit' => 'Create new deposit', - 'create_new_transfer' => 'Create new transfer', - 'create_new_asset' => 'Create new asset account', - 'create_new_liabilities' => 'Create new liability', - 'create_new_expense' => 'Create new expense account', + 'create_new_withdrawal' => 'Створити нову витрату', + 'create_new_deposit' => 'Створити новий дохід', + 'create_new_transfer' => 'Створити новий переказ', + 'create_new_asset' => 'Зберегти новий рахунок активів', + 'create_new_liabilities' => 'Створити нове зобов\'язання', + 'create_new_expense' => 'Створити новий рахунок витрат', 'create_new_revenue' => 'Створити нове джерело доходу', 'create_new_piggy_bank' => 'Створити нову скарбничку', 'create_new_bill' => 'Створити новий рахунок', @@ -1685,34 +1685,34 @@ return [ 'deleted_bl' => 'Зазначену в бюджеті суму видалено', 'alt_currency_ab_create' => 'Встановіть доступний бюджет в іншій валюті', 'bl_create_btn' => 'Вказати бюджет в іншій валюті', - 'inactiveBudgets' => 'Inactive budgets', + 'inactiveBudgets' => 'Неактивні бюджети', 'without_budget_between' => 'Transactions without a budget between :start and :end', - 'delete_budget' => 'Delete budget ":name"', - 'deleted_budget' => 'Deleted budget ":name"', - 'edit_budget' => 'Edit budget ":name"', - 'updated_budget' => 'Updated budget ":name"', - 'update_amount' => 'Update amount', - 'update_budget' => 'Update budget', + 'delete_budget' => 'Видалити бюджет ":name"', + 'deleted_budget' => 'Видалений бюджет ":name"', + 'edit_budget' => 'Редагувати бюджет ":name"', + 'updated_budget' => 'Оновлений бюджет ":name"', + 'update_amount' => 'Оновити суму', + 'update_budget' => 'Оновити бюджет', 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', 'set_budget_limit_title' => 'Set budgeted amount for budget :budget between :start and :end', - 'set_budget_limit' => 'Set budgeted amount', + 'set_budget_limit' => 'Встановити суму в бюджеті', 'budget_period_navigator' => 'Огляд періоду', - 'info_on_available_amount' => 'What do I have available?', + 'info_on_available_amount' => 'Що я маю в наявності?', 'available_amount_indication' => 'Use these amounts to get an indication of what your total budget could be.', 'suggested' => 'Suggested', - 'average_between' => 'Average between :start and :end', - 'transferred_in' => 'Transferred (in)', + 'average_between' => 'В середньому між :start і :end', + 'transferred_in' => 'Переведено (у)', 'transferred_away' => 'Transferred (away)', - 'auto_budget_none' => 'No auto-budget', - 'auto_budget_reset' => 'Set a fixed amount every period', + 'auto_budget_none' => 'Без авто-бюджету', + 'auto_budget_reset' => 'Встановити фіксовану суму кожного періоду', 'auto_budget_rollover' => 'Add an amount every period', 'auto_budget_adjusted' => 'Add an amount every period and correct for overspending', - 'auto_budget_period_daily' => 'Daily', - 'auto_budget_period_weekly' => 'Weekly', - 'auto_budget_period_monthly' => 'Monthly', - 'auto_budget_period_quarterly' => 'Quarterly', - 'auto_budget_period_half_year' => 'Every half year', - 'auto_budget_period_yearly' => 'Yearly', + 'auto_budget_period_daily' => 'Щоденно', + 'auto_budget_period_weekly' => 'Щотижня', + 'auto_budget_period_monthly' => 'Щомісячно', + 'auto_budget_period_quarterly' => 'Щоквартально', + 'auto_budget_period_half_year' => 'Щопівроку', + 'auto_budget_period_yearly' => 'Щорічно', 'auto_budget_help' => 'You can read more about this feature in the help. Click the top-right (?) icon.', 'auto_budget_reset_icon' => 'This budget will be set periodically', 'auto_budget_rollover_icon' => 'The budget amount will increase periodically', @@ -1729,15 +1729,15 @@ return [ 'repeats' => 'Repeats', 'bill_end_date_help' => 'Optional field. The bill is expected to end on this date.', 'bill_extension_date_help' => 'Optional field. The bill must be extended (or cancelled) on or before this date.', - 'bill_end_index_line' => 'This bill ends on :date', + 'bill_end_index_line' => 'Цей рахунок закінчується :date', 'bill_extension_index_line' => 'This bill must be extended or cancelled on :date', - 'connected_journals' => 'Connected transactions', + 'connected_journals' => 'Підключені транзакції', 'auto_match_on' => 'Automatically matched by Firefly III', 'auto_match_off' => 'Not automatically matched by Firefly III', 'next_expected_match' => 'Next expected match', - 'delete_bill' => 'Delete bill ":name"', - 'deleted_bill' => 'Deleted bill ":name"', - 'edit_bill' => 'Edit bill ":name"', + 'delete_bill' => 'Видалити рахунок::name"', + 'deleted_bill' => 'Видалено рахунок ":name"', + 'edit_bill' => 'Редагувати рахунок:name"', 'more' => 'Докладніше', 'rescan_old' => 'Run rules again, on all transactions', 'update_bill' => 'Update bill', @@ -1805,16 +1805,16 @@ return [ 'revenue_deleted' => 'Успішно видалено джерело доходів ":name"', 'update_asset_account' => 'Update asset account', 'update_undefined_account' => 'Update account', - 'update_liabilities_account' => 'Update liability', + 'update_liabilities_account' => 'Оновити зобов\'язання', 'update_expense_account' => 'Update expense account', 'update_revenue_account' => 'Оновити джерело доходу', 'make_new_asset_account' => 'Create a new asset account', 'make_new_expense_account' => 'Create a new expense account', 'make_new_revenue_account' => 'Створити нове джерело доходу', - 'make_new_liabilities_account' => 'Create a new liability', - 'asset_accounts' => 'Asset accounts', + 'make_new_liabilities_account' => 'Створити нове зобов\'язання', + 'asset_accounts' => 'Основні рахунки', 'undefined_accounts' => 'Accounts', - 'asset_accounts_inactive' => 'Asset accounts (inactive)', + 'asset_accounts_inactive' => 'Основні рахунки (неактивні)', 'expense_accounts' => 'Рахунки витрат', 'expense_accounts_inactive' => 'Expense accounts (inactive)', 'revenue_accounts' => 'Джерела доходів', @@ -1830,7 +1830,7 @@ return [ 'amount_cannot_be_zero' => 'The amount cannot be zero', 'end_of_reconcile_period' => 'End of reconcile period: :period', 'start_of_reconcile_period' => 'Start of reconcile period: :period', - 'start_balance' => 'Start balance', + 'start_balance' => 'Початковий баланс', 'end_balance' => 'Кінцевий баланс', 'update_balance_dates_instruction' => 'Match the amounts and dates above to your bank statement, and press "Start reconciling"', 'select_transactions_instruction' => 'Select the transactions that appear on your bank statement.', @@ -1854,14 +1854,14 @@ return [ 'stored_new_account_js' => 'New account "{name}" stored!', 'updated_account' => 'Updated account ":name"', 'updated_account_js' => 'Updated account "{title}".', - 'credit_card_options' => 'Credit card options', + 'credit_card_options' => 'Налаштування кредитної картки', 'no_transactions_account' => 'Немає транзакцій (у цьому періоді) для рахунку активу «:name».', 'no_transactions_period' => 'There are no transactions (in this period).', 'no_data_for_chart' => 'Недостатньо інформації (поки що) для створення цього графіку.', 'select_at_least_one_account' => 'Please select at least one asset account', - 'select_at_least_one_category' => 'Please select at least one category', - 'select_at_least_one_budget' => 'Please select at least one budget', - 'select_at_least_one_tag' => 'Please select at least one tag', + 'select_at_least_one_category' => 'Будь ласка, оберіть хоча б одну категорію', + 'select_at_least_one_budget' => 'Будь ласка, оберіть хоча б один бюджет', + 'select_at_least_one_tag' => 'Будь ласка, виберіть принаймні один тег', 'select_at_least_one_expense' => 'Please select at least one combination of expense/revenue accounts. If you have none (the list is empty) this report is not available.', 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Your Firefly III ledger has more money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', @@ -1878,10 +1878,10 @@ return [ 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Reconcile this account', 'reconcile' => 'Reconcile', - 'show' => 'Show', + 'show' => 'Показати', 'confirm_reconciliation' => 'Confirm reconciliation', 'submitted_start_balance' => 'Submitted start balance', - 'selected_transactions' => 'Selected transactions (:count)', + 'selected_transactions' => 'Вибрані транзакції (:count)', 'already_cleared_transactions' => 'Already cleared transactions (:count)', 'submitted_end_balance' => 'Submitted end balance', 'initial_balance_description' => 'Initial balance for ":account"', @@ -1945,9 +1945,9 @@ return [ 'stored_journal' => 'Successfully created new transaction ":description"', 'stored_journal_no_descr' => 'Successfully created your new transaction', 'updated_journal_no_descr' => 'Successfully updated your transaction', - 'select_transactions' => 'Select transactions', - 'rule_group_select_transactions' => 'Apply ":title" to transactions', - 'rule_select_transactions' => 'Apply ":title" to transactions', + 'select_transactions' => 'Вибрати транзакції', + 'rule_group_select_transactions' => 'Застосувати ":title" до транзакцій', + 'rule_select_transactions' => 'Застосувати ":title" до транзакцій', 'stop_selection' => 'Stop selecting transactions', 'reconcile_selected' => 'Reconcile', 'mass_delete_journals' => 'Delete a number of transactions', @@ -1963,14 +1963,14 @@ return [ 'append_these_tags' => 'Add these tags', 'mass_edit' => 'Edit selected individually', 'bulk_edit' => 'Edit selected in bulk', - 'mass_delete' => 'Delete selected', + 'mass_delete' => 'Видалити вибране', 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', 'no_budget' => '(поза бюджетом)', 'no_bill' => '(no bill)', 'account_per_budget' => 'Account per budget', 'account_per_category' => 'Account per category', - 'create_new_object' => 'Create', + 'create_new_object' => 'Створити', 'empty' => '(порожньо)', 'all_other_budgets' => '(всі інші бюджети)', 'all_other_accounts' => '(all other accounts)', @@ -1993,20 +1993,20 @@ return [ 'tag' => 'Тег', 'no_budget_squared' => '(no budget)', 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', - 'mass_deleted_transactions_success' => 'Deleted :count transaction.|Deleted :count transactions.', - 'mass_edited_transactions_success' => 'Updated :count transaction.|Updated :count transactions.', + 'mass_deleted_transactions_success' => 'Видалено :count транзакцій.|Видалено транзакції :count.', + 'mass_edited_transactions_success' => 'Оновлено :count транзакцій.|Оновлено :count транзакцій.', 'opt_group_' => '(no account type)', 'opt_group_no_account_type' => '(no account type)', - 'opt_group_defaultAsset' => 'Default asset accounts', - 'opt_group_savingAsset' => 'Savings accounts', - 'opt_group_sharedAsset' => 'Shared asset accounts', + 'opt_group_defaultAsset' => 'Основний рахунок за замовчуванням', + 'opt_group_savingAsset' => 'Накопичувальні рахунки', + 'opt_group_sharedAsset' => 'Спільні рахунки', 'opt_group_ccAsset' => 'Credit cards', - 'opt_group_cashWalletAsset' => 'Cash wallets', + 'opt_group_cashWalletAsset' => 'Готівкові гаманці', 'opt_group_expense_account' => 'Рахунки витрат', 'opt_group_revenue_account' => 'Revenue accounts', - 'opt_group_l_Loan' => 'Liability: Loan', + 'opt_group_l_Loan' => 'Зобов\'язання: Позика', 'opt_group_cash_account' => 'Cash account', - 'opt_group_l_Debt' => 'Liability: Debt', + 'opt_group_l_Debt' => 'Зобов\'язання: Борг', 'opt_group_l_Mortgage' => 'Liability: Mortgage', 'opt_group_l_Credit card' => 'Liability: Credit card', 'notes' => 'Notes', @@ -2050,7 +2050,7 @@ return [ 'budgets_and_spending' => 'Budgets and spending', 'go_to_budget' => 'Go to budget "{budget}"', 'go_to_deposits' => 'Go to deposits', - 'go_to_expenses' => 'Go to expenses', + 'go_to_expenses' => 'Перейти до витрат', 'savings' => 'Заощадження', 'newWithdrawal' => 'Нові витрати', 'newDeposit' => 'Нові надходження', @@ -2100,46 +2100,46 @@ return [ 'budgets' => 'Бюджети', 'tags' => 'Теги', 'reports' => 'Звіти', - 'transactions' => 'Transactions', - 'expenses' => 'Expenses', + 'transactions' => 'Транзакції', + 'expenses' => 'Витрати', 'income' => 'Дохід / прихід', - 'transfers' => 'Transfers', + 'transfers' => 'Перекази', 'moneyManagement' => 'Money management', 'money_management' => 'Money management', 'tools' => 'Tools', - 'piggyBanks' => 'Piggy banks', - 'piggy_banks' => 'Piggy banks', + 'piggyBanks' => 'Скарбнички', + 'piggy_banks' => 'Скарбнички', 'amount_x_of_y' => '{current} of {total}', - 'bills' => 'Bills', - 'withdrawal' => 'Withdrawal', + 'bills' => 'Рахунки', + 'withdrawal' => 'Витрата', 'opening_balance' => 'Opening balance', 'deposit' => 'Deposit', 'account' => 'Account', 'transfer' => 'Transfer', - 'Withdrawal' => 'Withdrawal', + 'Withdrawal' => 'Витрата', 'Deposit' => 'Deposit', 'Transfer' => 'Переказ', - 'bill' => 'Bill', - 'yes' => 'Yes', - 'no' => 'No', - 'amount' => 'Amount', - 'overview' => 'Overview', + 'bill' => 'Рахунок', + 'yes' => 'Так', + 'no' => 'Ні', + 'amount' => 'Сума', + 'overview' => 'Огляд', 'saveOnAccount' => 'Save on account', 'unknown' => 'Unknown', 'monthly' => 'Monthly', 'profile' => 'Профіль', - 'errors' => 'Errors', + 'errors' => 'Помилки', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', 'debt_start_amount_help' => 'It\'s always best to set this value to a negative amount. Read the help pages (top right (?)-icon) for more information.', 'interest_period_help' => 'This field is purely cosmetic and won\'t be calculated for you. As it turns out banks are very sneaky so Firefly III never gets it right.', - 'store_new_liabilities_account' => 'Store new liability', - 'edit_liabilities_account' => 'Edit liability ":name"', - 'financial_control' => 'Financial control', + 'store_new_liabilities_account' => 'Зберегти нове зобов\'язання', + 'edit_liabilities_account' => 'Редагувати зобов\'язання ":name"', + 'financial_control' => 'Фінансовий контроль', 'accounting' => 'Accounting', - 'automation' => 'Automation', + 'automation' => 'Автоматизація', 'others' => 'Others', - 'classification' => 'Classification', + 'classification' => 'Класифікація', 'store_transaction' => 'Store transaction', /* @@ -2161,7 +2161,7 @@ return [ 'report_double' => 'Expense/revenue account report between :start and :end', 'report_budget' => 'Budget report between :start and :end', 'report_tag' => 'Tag report between :start and :end', - 'quick_link_reports' => 'Quick links', + 'quick_link_reports' => 'Швидкі посилання', 'quick_link_examples' => 'These are just some example links to get you started. Check out the help pages under the (?)-button for information on all reports and the magic words you can use.', 'quick_link_default_report' => 'Default financial report', 'quick_link_audit_report' => 'Transaction history overview', @@ -2171,14 +2171,14 @@ return [ 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', 'report_all_time_quick' => 'All-time, all accounts', 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', - 'incomeVsExpenses' => 'Income vs. expenses', + 'incomeVsExpenses' => 'Порівняння доходів та витрат', 'accountBalances' => 'Account balances', 'balanceStart' => 'Balance at start of period', 'balanceEnd' => 'Balance at end of period', 'splitByAccount' => 'Split by account', 'coveredWithTags' => 'Covered with tags', 'leftInBudget' => 'Left in budget', - 'left_in_debt' => 'Amount due', + 'left_in_debt' => 'Сума боргу', 'sumOfSums' => 'Sum of sums', 'noCategory' => '(no category)', 'notCharged' => 'Not charged (yet)', @@ -2206,13 +2206,13 @@ return [ 'income_entry' => 'Income from account ":name" between :start and :end', 'expense_entry' => 'Expenses to account ":name" between :start and :end', 'category_entry' => 'Expenses and income in category ":name" between :start and :end', - 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', + 'budget_spent_amount' => 'Витрати в бюджеті ":budget" між :start і :end', 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', 'reports_extra_options' => 'Extra options', 'report_has_no_extra_options' => 'This report has no extra options', - 'reports_submit' => 'View report', + 'reports_submit' => 'Переглянути звіт', 'end_after_start_date' => 'End date of report must be after start date.', 'select_category' => 'Select category(ies)', 'select_budget' => 'Select budget(s).', @@ -2230,27 +2230,27 @@ return [ 'include_income_not_in_category' => 'Included income not in the selected category(ies)', 'include_income_not_in_account' => 'Included income not in the selected account(s)', 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', - 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', + 'include_expense_not_in_tags' => 'Включені витрати не у вибраних тегах', 'everything_else' => 'Everything else', - 'income_and_expenses' => 'Income and expenses', + 'income_and_expenses' => 'Доходи і витрати', 'spent_average' => 'Spent (average)', 'income_average' => 'Income (average)', 'transaction_count' => 'Transaction count', 'average_spending_per_account' => 'Average spending per account', 'average_income_per_account' => 'Average income per account', - 'total' => 'Total', - 'description' => 'Description', + 'total' => 'Всього', + 'description' => 'Опис', 'sum_of_period' => 'Sum of period', 'average_in_period' => 'Average in period', 'account_role_defaultAsset' => 'Default asset account', - 'account_role_sharedAsset' => 'Shared asset account', - 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', - 'account_role_cashWalletAsset' => 'Cash wallet', - 'budget_chart_click' => 'Please click on a budget name in the table above to see a chart.', - 'category_chart_click' => 'Please click on a category name in the table above to see a chart.', + 'account_role_sharedAsset' => 'Спільний рахунок', + 'account_role_savingAsset' => 'Рахунок для накопичення', + 'account_role_ccAsset' => 'Кредитна картка', + 'account_role_cashWalletAsset' => 'Гаманець', + 'budget_chart_click' => 'Будь ласка, натисніть на назву бюджету в звітності, щоб побачити діаграму.', + 'category_chart_click' => 'Будь ласка, натисніть на назву категорії в наведеній таблиці, щоб побачити діаграму.', 'in_out_accounts' => 'Earned and spent per combination', - 'in_out_accounts_per_asset' => 'Earned and spent (per asset account)', + 'in_out_accounts_per_asset' => 'Зароблено і витрачено (на рахунок)', 'in_out_per_category' => 'Earned and spent per category', 'out_per_budget' => 'Spent per budget', 'select_expense_revenue' => 'Select expense/revenue account', @@ -2285,12 +2285,12 @@ return [ 'min-amount' => 'Minimum amount', 'journal-amount' => 'Запис поточного рахунку', 'name' => 'Name', - 'date' => 'Date', + 'date' => 'Дата', 'date_and_time' => 'Date and time', 'time' => 'Time', 'paid' => 'Paid', 'unpaid' => 'Unpaid', - 'day' => 'Day', + 'day' => 'День', 'budgeted' => 'Budgeted', 'period' => 'Period', 'balance' => 'Balance', @@ -2303,29 +2303,29 @@ return [ // piggy banks: 'event_history' => 'Event history', - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', - 'piggy_bank' => 'Piggy bank', - 'new_piggy_bank' => 'New piggy bank', - 'store_piggy_bank' => 'Store new piggy bank', - 'stored_piggy_bank' => 'Store new piggy bank ":name"', + 'add_money_to_piggy' => 'Додайте гроші в скарбничку ":name"', + 'piggy_bank' => 'Скарбничка', + 'new_piggy_bank' => 'Нова скарбничка', + 'store_piggy_bank' => 'Зберегти нову скарбничку', + 'stored_piggy_bank' => 'Зберегти нову скарбничку ":name"', 'account_status' => 'Account status', 'left_for_piggy_banks' => 'Left for piggy banks', - 'sum_of_piggy_banks' => 'Sum of piggy banks', + 'sum_of_piggy_banks' => 'Сума скарбничок', 'saved_so_far' => 'Saved so far', 'left_to_save' => 'Left to save', 'suggested_amount' => 'Suggested monthly amount to save', - 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', - 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', + 'add_money_to_piggy_title' => 'Додайте гроші в скарбничку ":name"', + 'remove_money_from_piggy_title' => 'Зніміть гроші зі скарбнички ":name"', 'add' => 'Add', - 'no_money_for_piggy' => 'You have no money to put in this piggy bank.', + 'no_money_for_piggy' => 'У вас немає грошей, щоб покласти в цю скарбничку.', 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Remove', 'max_amount_add' => 'The maximum amount you can add is', 'max_amount_remove' => 'The maximum amount you can remove is', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'updated_piggy_bank' => 'Updated piggy bank ":name"', + 'update_piggy_button' => 'Оновити скарбничку', + 'update_piggy_title' => 'Оновити скарбничку "":name"', + 'updated_piggy_bank' => 'Оновлено скарбничку ":name"', 'details' => 'Details', 'events' => 'Events', 'target_amount' => 'Target amount', @@ -2334,13 +2334,13 @@ return [ 'target_date' => 'Target date', 'no_target_date' => 'No target date', 'table' => 'Table', - 'delete_piggy_bank' => 'Delete piggy bank ":name"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'piggy_events' => 'Related piggy banks', + 'delete_piggy_bank' => 'Видалити скарбничку ":name"', + 'cannot_add_amount_piggy' => 'Не вдалося додати :amount до ":name.', + 'cannot_remove_from_piggy' => 'Не вдалося зняти :amount з :name".', + 'deleted_piggy_bank' => 'Видалено скарбничку ":name"', + 'added_amount_to_piggy' => 'Додано :amount до ":name"', + 'removed_amount_from_piggy' => 'Знято :amount з ":name"', + 'piggy_events' => 'Пов\'язані скарбнички', // tags 'delete_tag' => 'Delete tag ":tag"', @@ -2523,7 +2523,7 @@ return [ // empty lists? no objects? instructions: 'no_accounts_title_asset' => 'Let\'s create an asset account!', 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', - 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', + 'no_accounts_imperative_asset' => 'Щоб розпочати використання Firefly III, ви повинні створити принаймні один рахунок активів. Давайте зробимо це зараз:', 'no_accounts_create_asset' => 'Create an asset account', 'no_accounts_title_expense' => 'Let\'s create an expense account!', 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', @@ -2539,7 +2539,7 @@ return [ 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_rules_title_default' => 'Let\'s create a rule', - 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organize your expenses into logical groups, which you can give a soft-cap to limit your expenses.', + 'no_budgets_intro_default' => 'У вас поки що немає бюджетів. Бюджети використовуються для організації ваших витрат у логічні групи, яким ви можете надати м’яке обмеження, щоб обмежити свої витрати.', 'no_rules_intro_default' => 'You have no rules yet. Rules are powerful automations that can handle transactions for you.', 'no_rules_imperative_default' => 'Rules can be very useful when you\'re managing transactions. Let\'s create one now:', 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', @@ -2554,7 +2554,7 @@ return [ 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', 'no_tags_create_default' => 'Create a tag', 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', - 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', + 'no_transactions_intro_withdrawal' => 'У вас поки що немає витрат. Ви повинні створити витрати, щоб почати керувати своїми фінансами.', 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', 'no_transactions_create_withdrawal' => 'Створити витрату', 'no_transactions_title_deposit' => 'Let\'s create some income!', @@ -2567,12 +2567,12 @@ return [ 'no_transactions_create_transfers' => 'Create a transfer', 'no_piggies_title_default' => 'Давайте створимо скарбничку!', 'no_piggies_intro_default' => 'У вас поки що немає жодних скарбниць. Ви можете створити скарбниці, щоб розділити заощадження і відслідковувати ваші заощадження.', - 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', - 'no_piggies_create_default' => 'Create a new piggy bank', - 'no_bills_title_default' => 'Let\'s create a bill!', - 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent or insurance.', - 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', - 'no_bills_create_default' => 'Create a bill', + 'no_piggies_imperative_default' => 'У вас є речі, на які ви відкладаєте гроші? Створіть скарбничку і стежте за цим:', + 'no_piggies_create_default' => 'Створити нову скарбничку', + 'no_bills_title_default' => 'Давайте створимо рахунок!', + 'no_bills_intro_default' => 'Ви поки що не маєте рахунків. Ви можете створювати рахунки для відстеження регулярних витрат, таких як оренда або страхування.', + 'no_bills_imperative_default' => 'У вас регулярні рахунки? Створіть рахунок і відстежуйте свої платежі:', + 'no_bills_create_default' => 'Створити рахунок', // recurring transactions 'create_right_now' => 'Create right now', @@ -2595,7 +2595,7 @@ return [ 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', 'created_transactions' => 'Related transactions', - 'expected_withdrawals' => 'Expected withdrawals', + 'expected_withdrawals' => 'Очікувані витрати', 'expected_deposits' => 'Expected deposits', 'expected_transfers' => 'Expected transfers', 'created_withdrawals' => 'Created withdrawals', @@ -2692,8 +2692,8 @@ return [ // audit log entries 'audit_log_entries' => 'Audit log entries', - 'ale_action_log_add' => 'Added :amount to piggy bank ":name"', - 'ale_action_log_remove' => 'Removed :amount from piggy bank ":name"', + 'ale_action_log_add' => 'Додано :amount до скарбнички ":name"', + 'ale_action_log_remove' => 'Знято :amount зі скарбнички ":name"', 'ale_action_clear_budget' => 'Removed from budget', 'ale_action_clear_category' => 'Removed from category', 'ale_action_clear_notes' => 'Removed notes', @@ -2707,8 +2707,8 @@ return [ 'ale_action_update_transaction_type' => 'Changed transaction type', 'ale_action_update_notes' => 'Changed notes', 'ale_action_update_description' => 'Changed description', - 'ale_action_add_to_piggy' => 'Piggy bank', - 'ale_action_remove_from_piggy' => 'Piggy bank', + 'ale_action_add_to_piggy' => 'Скарбничка', + 'ale_action_remove_from_piggy' => 'Скарбничка', 'ale_action_add_tag' => 'Added tag', ]; From c764ddd3be0a787ac4a8e990d3a494ec5d646340 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 1 Jun 2023 19:49:28 +0200 Subject: [PATCH 05/30] Fix #7572 --- .../Controllers/Summary/BasicController.php | 23 +++++++++----- app/Repositories/Bill/BillRepository.php | 30 ++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php index ab7f56a8cb..48c919caf7 100644 --- a/app/Api/V1/Controllers/Summary/BasicController.php +++ b/app/Api/V1/Controllers/Summary/BasicController.php @@ -264,11 +264,16 @@ class BasicController extends Controller * Since both this method and the chart use the exact same data, we can suffice * with calling the one method in the bill repository that will get this amount. */ - $paidAmount = $this->billRepository->getBillsPaidInRangePerCurrency($start, $end); - $unpaidAmount = $this->billRepository->getBillsUnpaidInRangePerCurrency($start, $end); - $return = []; - foreach ($paidAmount as $currencyId => $amount) { - $amount = bcmul($amount, '-1'); + $paidAmount = $this->billRepository->sumPaidInRange($start, $end); + $unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end); + + $return = []; + /** + * @var int $currencyId + * @var array $info + */ + foreach ($paidAmount as $currencyId => $info) { + $amount = bcmul($info['sum'], '-1'); $currency = $this->currencyRepos->find((int)$currencyId); if (null === $currency) { continue; @@ -287,8 +292,12 @@ class BasicController extends Controller ]; } - foreach ($unpaidAmount as $currencyId => $amount) { - $amount = bcmul($amount, '-1'); + /** + * @var int $currencyId + * @var array $info + */ + foreach ($unpaidAmount as $currencyId => $info) { + $amount = bcmul($info['sum'], '-1'); $currency = $this->currencyRepos->find((int)$currencyId); if (null === $currency) { continue; diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 5ba53678ea..a3b5161726 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -851,17 +851,25 @@ class BillRepository implements BillRepositoryInterface /** @var Collection $set */ $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); $currency = $bill->transactionCurrency; - if ($set->count() > 0) { - $journalIds = $set->pluck('id')->toArray(); - $amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); - $return[$currency->id] = $return[$currency->id] ?? [ - 'id' => (string)$currency->id, - 'name' => $currency->name, - 'symbol' => $currency->symbol, - 'code' => $currency->code, - 'decimal_places' => $currency->decimal_places, - 'sum' => '0', - ]; + + $return[$currency->id] = $return[$currency->id] ?? [ + 'id' => (string)$currency->id, + 'name' => $currency->name, + 'symbol' => $currency->symbol, + 'code' => $currency->code, + 'decimal_places' => $currency->decimal_places, + 'sum' => '0', + ]; + + /** @var TransactionJournal $transactionJournal */ + foreach ($set as $transactionJournal) { + /** @var Transaction $sourceTransaction */ + $sourceTransaction = $transactionJournal->transactions()->where('amount', '<', 0)->first(); + $amount = (string)$sourceTransaction->amount; + if ((int)$sourceTransaction->foreign_currency_id === (int)$currency->id) { + // use foreign amount instead! + $amount = (string)$sourceTransaction->foreign_amount; + } $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount); } } From b72aa92e55a3db94ae69b2f8de4752d537b6b630 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2023 06:38:07 +0200 Subject: [PATCH 06/30] clean: remove deprecated methods and refactor as necessary. --- .../Controllers/Summary/BasicController.php | 42 ++--- app/Http/Controllers/Chart/BillController.php | 43 ++--- app/Repositories/Bill/BillRepository.php | 154 ------------------ .../Bill/BillRepositoryInterface.php | 51 ------ 4 files changed, 38 insertions(+), 252 deletions(-) diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php index 48c919caf7..bc0ba94eb5 100644 --- a/app/Api/V1/Controllers/Summary/BasicController.php +++ b/app/Api/V1/Controllers/Summary/BasicController.php @@ -269,48 +269,38 @@ class BasicController extends Controller $return = []; /** - * @var int $currencyId * @var array $info */ - foreach ($paidAmount as $currencyId => $info) { + foreach ($paidAmount as $info) { $amount = bcmul($info['sum'], '-1'); - $currency = $this->currencyRepos->find((int)$currencyId); - if (null === $currency) { - continue; - } $return[] = [ - 'key' => sprintf('bills-paid-in-%s', $currency->code), - 'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $currency->symbol]), + 'key' => sprintf('bills-paid-in-%s', $info['code']), + 'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]), 'monetary_value' => $amount, - 'currency_id' => $currency->id, - 'currency_code' => $currency->code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'value_parsed' => app('amount')->formatAnything($currency, $amount, false), + 'currency_id' => $info['id'], + 'currency_code' => $info['code'], + 'currency_symbol' => $info['symbol'], + 'currency_decimal_places' => $info['decimal_places'], + 'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false), 'local_icon' => 'check', 'sub_title' => '', ]; } /** - * @var int $currencyId * @var array $info */ - foreach ($unpaidAmount as $currencyId => $info) { + foreach ($unpaidAmount as $info) { $amount = bcmul($info['sum'], '-1'); - $currency = $this->currencyRepos->find((int)$currencyId); - if (null === $currency) { - continue; - } $return[] = [ - 'key' => sprintf('bills-unpaid-in-%s', $currency->code), - 'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $currency->symbol]), + 'key' => sprintf('bills-unpaid-in-%s', $info['code']), + 'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]), 'monetary_value' => $amount, - 'currency_id' => $currency->id, - 'currency_code' => $currency->code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'value_parsed' => app('amount')->formatAnything($currency, $amount, false), + 'currency_id' => $info['id'], + 'currency_code' => $info['code'], + 'currency_symbol' => $info['symbol'], + 'currency_decimal_places' => $info['decimal_places'], + 'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false), 'local_icon' => 'calendar-o', 'sub_title' => '', ]; diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index fa540c279c..edd98df59c 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -29,7 +29,6 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Bill; use FireflyIII\Repositories\Bill\BillRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\JsonResponse; @@ -38,8 +37,7 @@ use Illuminate\Http\JsonResponse; */ class BillController extends Controller { - /** @var GeneratorInterface Chart generation methods. */ - protected $generator; + protected GeneratorInterface $generator; /** * BillController constructor. @@ -70,30 +68,33 @@ class BillController extends Controller if ($cache->has()) { return response()->json($cache->get()); } - /** @var CurrencyRepositoryInterface $currencyRepository */ - $currencyRepository = app(CurrencyRepositoryInterface::class); - $chartData = []; - $currencies = []; - $paid = $repository->getBillsPaidInRangePerCurrency($start, $end); // will be a negative amount. - $unpaid = $repository->getBillsUnpaidInRangePerCurrency($start, $end); // will be a positive amount. + $chartData = []; + $paid = $repository->sumPaidInRange($start, $end); + $unpaid = $repository->sumUnpaidInRange($start, $end); - foreach ($paid as $currencyId => $amount) { - $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepository->find($currencyId); - $label = (string)trans('firefly.paid_in_currency', ['currency' => $currencies[$currencyId]->name]); - $chartData[$label] = [ + /** + * @var array $info + */ + foreach ($paid as $info) { + $amount = $info['sum']; + $label = (string)trans('firefly.paid_in_currency', ['currency' => $info['name']]); + $chartData[$label] = [ 'amount' => $amount, - 'currency_symbol' => $currencies[$currencyId]->symbol, - 'currency_code' => $currencies[$currencyId]->code, + 'currency_symbol' => $info['symbol'], + 'currency_code' => $info['code'], ]; } - foreach ($unpaid as $currencyId => $amount) { - $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepository->find($currencyId); - $label = (string)trans('firefly.unpaid_in_currency', ['currency' => $currencies[$currencyId]->name]); - $chartData[$label] = [ + /** + * @var array $info + */ + foreach ($unpaid as $info) { + $amount = $info['sum']; + $label = (string)trans('firefly.unpaid_in_currency', ['currency' => $info['name']]); + $chartData[$label] = [ 'amount' => $amount, - 'currency_symbol' => $currencies[$currencyId]->symbol, - 'currency_code' => $currencies[$currencyId]->code, + 'currency_symbol' => $info['symbol'], + 'currency_code' => $info['code'], ]; } diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index a3b5161726..a4496990fe 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -87,27 +87,6 @@ class BillRepository implements BillRepositoryInterface return $search->take($limit)->get(); } - /** - * @inheritDoc - * @deprecated - */ - public function collectBillsUnpaidInRange(Carbon $start, Carbon $end): Collection - { - $bills = $this->getActiveBills(); - $return = new Collection(); - /** @var Bill $bill */ - foreach ($bills as $bill) { - $dates = $this->getPayDatesInRange($bill, $start, $end); - $count = $bill->transactionJournals()->after($start)->before($end)->count(); - $total = $dates->count() - $count; - if ($total > 0) { - $return->push($bill); - } - } - - return $bills; - } - /** * Correct order of piggies in case of issues. */ @@ -296,139 +275,6 @@ class BillRepository implements BillRepositoryInterface ->get($fields); } - /** - * TODO unsure why this is deprecated. - * - * Get the total amount of money paid for the users active bills in the date range given. - * This amount will be negative (they're expenses). This method is equal to - * getBillsUnpaidInRange. So the debug comments are gone. - * - * @param Carbon $start - * @param Carbon $end - * @return string - * @deprecated - */ - public function getBillsPaidInRange(Carbon $start, Carbon $end): string - { - $bills = $this->getActiveBills(); - $sum = '0'; - /** @var Bill $bill */ - foreach ($bills as $bill) { - /** @var Collection $set */ - $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); - if ($set->count() > 0) { - $journalIds = $set->pluck('id')->toArray(); - $amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); - $sum = bcadd($sum, $amount); - //Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum)); - } - } - - return $sum; - } - - /** - * TODO unsure why this is deprecated. - * - * Get the total amount of money paid for the users active bills in the date range given, - * grouped per currency. - * @param Carbon $start - * @param Carbon $end - * - * @return array - * @deprecated - */ - public function getBillsPaidInRangePerCurrency(Carbon $start, Carbon $end): array - { - $bills = $this->getActiveBills(); - $return = []; - /** @var Bill $bill */ - foreach ($bills as $bill) { - /** @var Collection $set */ - $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); - $currencyId = (int)$bill->transaction_currency_id; - if ($set->count() > 0) { - $journalIds = $set->pluck('id')->toArray(); - $amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); - $return[$currencyId] = $return[$currencyId] ?? '0'; - $return[$currencyId] = bcadd($amount, $return[$currencyId]); - //Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId)); - } - } - - return $return; - } - - /** - * TODO unsure why this is deprecated. - * - * Get the total amount of money due for the users active bills in the date range given. This amount will be positive. - * - * @param Carbon $start - * @param Carbon $end - * @return string - * @deprecated - */ - public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string - { - $bills = $this->getActiveBills(); - $sum = '0'; - /** @var Bill $bill */ - foreach ($bills as $bill) { - //Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name)); - $dates = $this->getPayDatesInRange($bill, $start, $end); - $count = $bill->transactionJournals()->after($start)->before($end)->count(); - $total = $dates->count() - $count; - - //Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total)); - - if ($total > 0) { - $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); - $multi = bcmul($average, (string)$total); - $sum = bcadd($sum, $multi); - //Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum)); - } - } - - return $sum; - } - - /** - * TODO unsure why this is deprecated. - * - * Get the total amount of money due for the users active bills in the date range given. - * - * @param Carbon $start - * @param Carbon $end - * @return array - * @deprecated - */ - public function getBillsUnpaidInRangePerCurrency(Carbon $start, Carbon $end): array - { - $bills = $this->getActiveBills(); - $return = []; - /** @var Bill $bill */ - foreach ($bills as $bill) { - //Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name)); - $dates = $this->getPayDatesInRange($bill, $start, $end); - $count = $bill->transactionJournals()->after($start)->before($end)->count(); - $total = $dates->count() - $count; - $currencyId = (int)$bill->transaction_currency_id; - - //Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total)); - - if ($total > 0) { - $average = bcdiv(bcadd((string)$bill->amount_max, (string)$bill->amount_min), '2'); - $multi = bcmul($average, (string)$total); - $return[$currencyId] = $return[$currencyId] ?? '0'; - $return[$currencyId] = bcadd($return[$currencyId], $multi); - //Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId)); - } - } - - return $return; - } - /** * Get all bills with these ID's. * diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 735eae4af5..b9a67e73bd 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -52,16 +52,6 @@ interface BillRepositoryInterface */ public function billStartsWith(string $query, int $limit): Collection; - /** - * Get the total amount of money due for the users active bills in the date range given. - * - * @param Carbon $start - * @param Carbon $end - * @return Collection - * @deprecated - */ - public function collectBillsUnpaidInRange(Carbon $start, Carbon $end): Collection; - /** * Add correct order to bills. */ @@ -135,47 +125,6 @@ interface BillRepositoryInterface */ public function getBillsForAccounts(Collection $accounts): Collection; - /** - * Get the total amount of money paid for the users active bills in the date range given. - * @param Carbon $start - * @param Carbon $end - * - * @return string - * @deprecated - */ - public function getBillsPaidInRange(Carbon $start, Carbon $end): string; - - /** - * Get the total amount of money paid for the users active bills in the date range given, - * grouped per currency. - * @param Carbon $start - * @param Carbon $end - * - * @return array - * @deprecated - */ - public function getBillsPaidInRangePerCurrency(Carbon $start, Carbon $end): array; - - /** - * Get the total amount of money due for the users active bills in the date range given. - * @param Carbon $start - * @param Carbon $end - * - * @return string - * @deprecated - */ - public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string; - - /** - * Get the total amount of money due for the users active bills in the date range given. - * @param Carbon $start - * @param Carbon $end - * - * @return array - * @deprecated - */ - public function getBillsUnpaidInRangePerCurrency(Carbon $start, Carbon $end): array; - /** * Get all bills with these ID's. * From dcf71c6fdf1f1965adda20123e95286a292eb8ef Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2023 07:36:17 +0200 Subject: [PATCH 07/30] cleanup: Commands are a lot less verbal and report better on success / failue --- .../Commands/Correction/CorrectAmounts.php | 14 +- .../Commands/Correction/CorrectDatabase.php | 13 +- .../CorrectOpeningBalanceCurrencies.php | 62 +- .../Correction/CreateAccessTokens.php | 5 +- .../Commands/Correction/CreateLinkTypes.php | 6 +- .../Commands/Correction/DeleteEmptyGroups.php | 25 +- .../Correction/DeleteEmptyJournals.php | 7 +- .../Correction/DeleteOrphanedTransactions.php | 9 +- .../Commands/Correction/DeleteZeroAmount.php | 6 +- .../Commands/Correction/EnableCurrencies.php | 26 +- .../Commands/Correction/FixAccountOrder.php | 16 +- .../Commands/Correction/FixAccountTypes.php | 167 ++-- .../Correction/FixFrontpageAccounts.php | 17 +- .../Commands/Correction/FixGroupAccounts.php | 14 +- app/Console/Commands/Correction/FixIbans.php | 18 +- .../Correction/FixLongDescriptions.php | 22 +- .../Commands/Correction/FixPiggies.php | 18 +- .../Correction/FixRecurringTransactions.php | 28 +- .../Correction/FixTransactionTypes.php | 18 +- .../Commands/Correction/FixUnevenAmount.php | 23 +- .../Commands/Correction/RemoveBills.php | 19 +- .../Commands/Correction/RenameMetaFields.php | 21 +- .../Commands/Correction/TransferBudgets.php | 19 +- .../Correction/TriggerCreditCalculation.php | 13 +- .../Integrity/CreateGroupMemberships.php | 25 +- .../Commands/Integrity/ReportEmptyObjects.php | 3 - app/Console/Commands/Integrity/ReportSum.php | 17 +- .../Commands/Integrity/RestoreOAuthKeys.php | 28 +- .../Integrity/UpdateGroupInformation.php | 14 +- .../Commands/System/ForceDecimalSize.php | 1 - .../Commands/System/ForceMigration.php | 2 - .../Commands/Upgrade/AccountCurrencies.php | 52 +- .../Upgrade/AppendBudgetLimitPeriods.php | 7 +- .../Commands/Upgrade/BackToJournals.php | 11 +- .../Commands/Upgrade/BudgetLimitCurrency.php | 9 +- .../Commands/Upgrade/CCLiabilities.php | 20 +- .../Commands/Upgrade/DecryptDatabase.php | 19 +- .../Commands/Upgrade/FixPostgresSequences.php | 2 - .../Commands/Upgrade/MigrateAttachments.php | 4 +- .../Commands/Upgrade/MigrateJournalNotes.php | 4 +- .../Upgrade/MigrateRecurrenceMeta.php | 8 +- .../Upgrade/MigrateRecurrenceType.php | 4 - .../Commands/Upgrade/MigrateTagLocations.php | 6 +- .../Commands/Upgrade/MigrateToGroups.php | 329 ++++---- .../Commands/Upgrade/MigrateToRules.php | 4 +- .../Upgrade/OtherCurrenciesCorrections.php | 207 +++-- .../Commands/Upgrade/RenameAccountMeta.php | 9 +- .../Upgrade/TransactionIdentifier.php | 27 +- .../Upgrade/TransferCurrenciesCorrections.php | 740 +++++++++--------- .../Commands/Upgrade/UpgradeDatabase.php | 19 +- .../Commands/Upgrade/UpgradeLiabilities.php | 23 +- .../Upgrade/UpgradeLiabilitiesEight.php | 46 +- .../Commands/Upgrade/UpgradeSkeleton.php.stub | 2 +- app/Factory/AccountMetaFactory.php | 3 - app/Http/Controllers/DebugController.php | 1 - .../Account/AccountRepository.php | 1 - .../Support/CreditRecalculateService.php | 47 +- .../Internal/Support/JournalServiceTrait.php | 1 - composer.lock | 521 ++++++------ database/seeders/ConfigSeeder.php | 4 - 60 files changed, 1108 insertions(+), 1698 deletions(-) diff --git a/app/Console/Commands/Correction/CorrectAmounts.php b/app/Console/Commands/Correction/CorrectAmounts.php index 462c9847a8..c2fda9333f 100644 --- a/app/Console/Commands/Correction/CorrectAmounts.php +++ b/app/Console/Commands/Correction/CorrectAmounts.php @@ -39,22 +39,10 @@ use Illuminate\Console\Command; */ class CorrectAmounts extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'This command makes sure positive and negative amounts are recorded correctly.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-amount-pos-neg'; + protected $signature = 'firefly-iii:fix-amount-pos-neg'; /** - * Execute the console command. - * * @return int */ public function handle(): int diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php index 633fcd6df9..aed810e830 100644 --- a/app/Console/Commands/Correction/CorrectDatabase.php +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -34,25 +34,14 @@ use Schema; */ class CorrectDatabase extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Will correct the integrity of your database, if necessary.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:correct-database'; + protected $signature = 'firefly-iii:correct-database'; /** * Execute the console command. */ public function handle(): int { - $this->line('Handle Firefly III database correction commands.'); // if table does not exist, return false if (!Schema::hasTable('users')) { $this->error('No "users"-table, will not continue.'); diff --git a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php index f94ebdaefb..14d6b299a6 100644 --- a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php +++ b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace FireflyIII\Console\Commands\Correction; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; @@ -33,26 +32,15 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; -use JsonException; +use Illuminate\Support\Collection; /** * Class CorrectOpeningBalanceCurrencies */ class CorrectOpeningBalanceCurrencies extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Will make sure that opening balance transaction currencies match the account they\'re for.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-ob-currencies'; + protected $signature = 'firefly-iii:fix-ob-currencies'; /** * Execute the console command. @@ -61,32 +49,22 @@ class CorrectOpeningBalanceCurrencies extends Command */ public function handle(): int { - Log::debug(sprintf('Now in %s', __METHOD__)); - // get all OB journals: - $set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_types.type', TransactionType::OPENING_BALANCE)->get(['transaction_journals.*']); - - $this->line(sprintf('Going to verify %d opening balance transactions.', $set->count())); - $count = 0; + $journals = $this->getJournals(); + $count = 0; /** @var TransactionJournal $journal */ - foreach ($set as $journal) { + foreach ($journals as $journal) { $count += $this->correctJournal($journal); } if ($count > 0) { $message = sprintf('Corrected %d opening balance transaction(s).', $count); - Log::debug($message); $this->line($message); } if (0 === $count) { - $message = 'There was nothing to fix in the opening balance transactions.'; - Log::debug($message); + $message = 'Correct: There was nothing to fix in the opening balance transactions.'; $this->info($message); } - Log::debug(sprintf('Done with %s', __METHOD__)); - return 0; } @@ -94,24 +72,21 @@ class CorrectOpeningBalanceCurrencies extends Command * @param TransactionJournal $journal * * @return int - * @throws FireflyException - * @throws JsonException */ private function correctJournal(TransactionJournal $journal): int { // get the asset account for this opening balance: $account = $this->getAccount($journal); if (null === $account) { - $message = sprintf('Transaction journal #%d has no valid account. Cant fix this line.', $journal->id); + $message = sprintf('Transaction journal #%d has no valid account. Can\'t fix this line.', $journal->id); app('log')->warning($message); $this->warn($message); return 0; } - $currency = $this->getCurrency($account); // update journal and all transactions: - return $this->setCurrency($journal, $currency); + return $this->setCorrectCurrency($account, $journal); } /** @@ -138,8 +113,6 @@ class CorrectOpeningBalanceCurrencies extends Command * @param Account $account * * @return TransactionCurrency - * @throws JsonException - * @throws FireflyException */ private function getCurrency(Account $account): TransactionCurrency { @@ -151,14 +124,25 @@ class CorrectOpeningBalanceCurrencies extends Command } /** + * @return Collection + */ + private function getJournals(): Collection + { + /** @var Collection */ + return TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_types.type', TransactionType::OPENING_BALANCE)->get(['transaction_journals.*']); + } + + /** + * @param Account $account * @param TransactionJournal $journal - * @param TransactionCurrency $currency - * * @return int */ - private function setCurrency(TransactionJournal $journal, TransactionCurrency $currency): int + private function setCorrectCurrency(Account $account, TransactionJournal $journal): int { - $count = 0; + $currency = $this->getCurrency($account); + $count = 0; if ((int)$journal->transaction_currency_id !== (int)$currency->id) { $journal->transaction_currency_id = $currency->id; $journal->save(); diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php index 7933662fe7..a8a73d1a9b 100644 --- a/app/Console/Commands/Correction/CreateAccessTokens.php +++ b/app/Console/Commands/Correction/CreateAccessTokens.php @@ -58,7 +58,6 @@ class CreateAccessTokens extends Command /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); - $start = microtime(true); $count = 0; $users = $repository->all(); /** @var User $user */ @@ -72,10 +71,8 @@ class CreateAccessTokens extends Command } } if (0 === $count) { - $this->info('All access tokens OK!'); + $this->info('Correct: Verified access tokens.'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verify access tokens in %s seconds.', $end)); return 0; } diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php index bec99fd9d5..cde1dbbd47 100644 --- a/app/Console/Commands/Correction/CreateLinkTypes.php +++ b/app/Console/Commands/Correction/CreateLinkTypes.php @@ -51,7 +51,6 @@ class CreateLinkTypes extends Command */ public function handle(): int { - $start = microtime(true); $count = 0; $set = [ 'Related' => ['relates to', 'relates to'], @@ -74,11 +73,8 @@ class CreateLinkTypes extends Command $link->save(); } if (0 === $count) { - $this->info('All link types OK!'); + $this->info('Correct: all link types are OK'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified link types in %s seconds', $end)); - return 0; } } diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php index 6d16b1766b..27eaee4077 100644 --- a/app/Console/Commands/Correction/DeleteEmptyGroups.php +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -26,25 +26,14 @@ namespace FireflyIII\Console\Commands\Correction; use Exception; use FireflyIII\Models\TransactionGroup; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; /** * Class DeleteEmptyGroups */ class DeleteEmptyGroups extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Delete empty transaction groups.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:delete-empty-groups'; + protected $signature = 'firefly-iii:delete-empty-groups'; /** * Execute the console command. @@ -55,14 +44,11 @@ class DeleteEmptyGroups extends Command */ public function handle(): int { - Log::debug(sprintf('Now in %s', __METHOD__)); - $start = microtime(true); $groupIds - = TransactionGroup::leftJoin('transaction_journals', 'transaction_groups.id', '=', 'transaction_journals.transaction_group_id') - ->whereNull('transaction_journals.id')->get(['transaction_groups.id'])->pluck('id')->toArray(); + = TransactionGroup::leftJoin('transaction_journals', 'transaction_groups.id', '=', 'transaction_journals.transaction_group_id') + ->whereNull('transaction_journals.id')->get(['transaction_groups.id'])->pluck('id')->toArray(); $total = count($groupIds); - Log::debug(sprintf('Count is %d', $total)); if ($total > 0) { $this->info(sprintf('Deleted %d empty transaction group(s).', $total)); @@ -72,8 +58,9 @@ class DeleteEmptyGroups extends Command TransactionGroup::whereNull('deleted_at')->whereIn('id', $chunk)->delete(); } } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified empty groups in %s seconds', $end)); + if (0 === $total) { + $this->info('Correct: verified empty groups.'); + } return 0; } diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php index e62b6be7d6..e0a6184881 100644 --- a/app/Console/Commands/Correction/DeleteEmptyJournals.php +++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php @@ -63,7 +63,6 @@ class DeleteEmptyJournals extends Command private function deleteEmptyJournals(): void { - $start = microtime(true); $count = 0; $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->groupBy('transaction_journals.id') @@ -82,10 +81,8 @@ class DeleteEmptyJournals extends Command ++$count; } if (0 === $count) { - $this->info('No empty transaction journals.'); + $this->info('Correct: no empty transaction journals.'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified empty journals in %s seconds', $end)); } /** @@ -115,7 +112,7 @@ class DeleteEmptyJournals extends Command } } if (0 === $total) { - $this->info('No uneven transaction journals.'); + $this->info('Correct: no uneven transaction journals.'); } } } diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php index b559c43101..3d2dc9b753 100644 --- a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php +++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php @@ -55,12 +55,9 @@ class DeleteOrphanedTransactions extends Command */ public function handle(): int { - $start = microtime(true); $this->deleteOrphanedJournals(); $this->deleteOrphanedTransactions(); $this->deleteFromOrphanedAccounts(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified orphans in %s seconds', $end)); return 0; } @@ -93,7 +90,7 @@ class DeleteOrphanedTransactions extends Command $count++; } if (0 === $count) { - $this->info('No orphaned accounts.'); + $this->info('Correct: no orphaned accounts.'); } } @@ -105,7 +102,7 @@ class DeleteOrphanedTransactions extends Command ->get(['transaction_journals.id', 'transaction_journals.transaction_group_id']); $count = $set->count(); if (0 === $count) { - $this->info('No orphaned journals.'); + $this->info('Correct: no orphaned journals.'); } if ($count > 0) { $this->info(sprintf('Found %d orphaned journal(s).', $count)); @@ -157,7 +154,7 @@ class DeleteOrphanedTransactions extends Command } } if (0 === $count) { - $this->info('No orphaned transactions.'); + $this->info('Correct: no orphaned transactions.'); } } } diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php index 12d5ebf02b..b7bf6e2c8e 100644 --- a/app/Console/Commands/Correction/DeleteZeroAmount.php +++ b/app/Console/Commands/Correction/DeleteZeroAmount.php @@ -52,7 +52,6 @@ class DeleteZeroAmount extends Command */ public function handle(): int { - $start = microtime(true); $set = Transaction::where('amount', 0)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); $set = array_unique($set); $journals = TransactionJournal::whereIn('id', $set)->get(); @@ -64,12 +63,9 @@ class DeleteZeroAmount extends Command Transaction::where('transaction_journal_id', $journal->id)->delete(); } if (0 === $journals->count()) { - $this->info('No zero-amount transaction journals.'); + $this->info('Correct: no zero-amount transaction journals.'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified zero-amount integrity in %s seconds', $end)); - return 0; } } diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php index b037435a81..0901c4a565 100644 --- a/app/Console/Commands/Correction/EnableCurrencies.php +++ b/app/Console/Commands/Correction/EnableCurrencies.php @@ -37,18 +37,8 @@ use Illuminate\Support\Facades\Log; */ class EnableCurrencies extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Enables all currencies in use.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:enable-currencies'; + protected $signature = 'firefly-iii:enable-currencies'; /** * Execute the console command. @@ -57,7 +47,6 @@ class EnableCurrencies extends Command */ public function handle(): int { - $start = microtime(true); $found = []; // get all meta entries /** @var Collection $meta */ @@ -85,8 +74,8 @@ class EnableCurrencies extends Command $found[] = (int)$entry->transaction_currency_id; } - $found = array_values(array_unique($found)); - $found = array_values( + $found = array_values(array_unique($found)); + $found = array_values( array_filter( $found, function (int $currencyId) { @@ -94,22 +83,15 @@ class EnableCurrencies extends Command } ) ); - $message = sprintf('%d different currencies are currently in use.', count($found)); - $this->info($message); - Log::debug($message, $found); - $disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count(); if ($disabled > 0) { $this->info(sprintf('%d were (was) still disabled. This has been corrected.', $disabled)); } if (0 === $disabled) { - $this->info('All currencies are correctly enabled or disabled.'); + $this->info('Correct: All currencies are correctly enabled or disabled.'); } TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified currencies in %s seconds.', $end)); - return 0; } } diff --git a/app/Console/Commands/Correction/FixAccountOrder.php b/app/Console/Commands/Correction/FixAccountOrder.php index b2b214e7f8..86f9052fbb 100644 --- a/app/Console/Commands/Correction/FixAccountOrder.php +++ b/app/Console/Commands/Correction/FixAccountOrder.php @@ -32,18 +32,8 @@ use Illuminate\Console\Command; */ class FixAccountOrder extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Make sure account order is correct.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-account-order'; + protected $signature = 'firefly-iii:fix-account-order'; private AccountRepositoryInterface $repository; @@ -55,7 +45,6 @@ class FixAccountOrder extends Command public function handle(): int { $this->stupidLaravel(); - $start = microtime(true); $users = User::get(); foreach ($users as $user) { @@ -63,8 +52,7 @@ class FixAccountOrder extends Command $this->repository->resetAccountOrder(); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verifying account order took %s seconds', $end)); + $this->info('Correct: All accounts are ordered correctly'); return 0; } diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php index 1dac4a558d..53706dbd99 100644 --- a/app/Console/Commands/Correction/FixAccountTypes.php +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -38,18 +38,8 @@ use Illuminate\Support\Facades\Log; */ class FixAccountTypes extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Make sure all journals have the correct from/to account types.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-account-types'; + protected $signature = 'firefly-iii:fix-account-types'; private int $count; private array $expected; private AccountFactory $factory; @@ -63,103 +53,23 @@ class FixAccountTypes extends Command public function handle(): int { $this->stupidLaravel(); - Log::debug('Now in fix-account-types'); - $start = microtime(true); $this->factory = app(AccountFactory::class); $this->expected = config('firefly.source_dests'); $journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get(); - Log::debug(sprintf('Found %d journals to inspect.', $journals->count())); foreach ($journals as $journal) { $this->inspectJournal($journal); } if (0 === $this->count) { - Log::debug('No journals had to be fixed.'); - $this->info('All account types are OK!'); + $this->info('Correct: all account types are OK'); } if (0 !== $this->count) { Log::debug(sprintf('%d journals had to be fixed.', $this->count)); $this->info(sprintf('Acted on %d transaction(s)!', $this->count)); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verifying account types took %s seconds', $end)); - return 0; } - /** - * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is - * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should - * be called from the handle method instead of using the constructor to initialize the command. - * - - */ - private function stupidLaravel(): void - { - $this->count = 0; - } - - /** - * @param TransactionJournal $journal - * - * @throws FireflyException - */ - private function inspectJournal(TransactionJournal $journal): void - { - $transactions = $journal->transactions()->count(); - if (2 !== $transactions) { - Log::debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions)); - $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $transactions)); - - return; - } - $type = $journal->transactionType->type; - $sourceTransaction = $this->getSourceTransaction($journal); - $destTransaction = $this->getDestinationTransaction($journal); - $sourceAccount = $sourceTransaction->account; - $sourceAccountType = $sourceAccount->accountType->type; - $destAccount = $destTransaction->account; - $destAccountType = $destAccount->accountType->type; - - if (!array_key_exists($type, $this->expected)) { - Log::info(sprintf('No source/destination info for transaction type %s.', $type)); - $this->info(sprintf('No source/destination info for transaction type %s.', $type)); - - return; - } - if (!array_key_exists($sourceAccountType, $this->expected[$type])) { - Log::debug(sprintf('Going to fix journal #%d', $journal->id)); - $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); - - return; - } - $expectedTypes = $this->expected[$type][$sourceAccountType]; - if (!in_array($destAccountType, $expectedTypes, true)) { - Log::debug(sprintf('Going to fix journal #%d', $journal->id)); - $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); - } - } - - /** - * @param TransactionJournal $journal - * - * @return Transaction - */ - private function getSourceTransaction(TransactionJournal $journal): Transaction - { - return $journal->transactions->firstWhere('amount', '<', 0); - } - - /** - * @param TransactionJournal $journal - * - * @return Transaction - */ - private function getDestinationTransaction(TransactionJournal $journal): Transaction - { - return $journal->transactions->firstWhere('amount', '>', 0); - } - /** * @param TransactionJournal $journal * @param string $type @@ -254,4 +164,77 @@ class FixAccountTypes extends Command break; } } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getDestinationTransaction(TransactionJournal $journal): Transaction + { + return $journal->transactions->firstWhere('amount', '>', 0); + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + return $journal->transactions->firstWhere('amount', '<', 0); + } + + /** + * @param TransactionJournal $journal + * + * @throws FireflyException + */ + private function inspectJournal(TransactionJournal $journal): void + { + $transactions = $journal->transactions()->count(); + if (2 !== $transactions) { + Log::debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions)); + $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $transactions)); + + return; + } + $type = $journal->transactionType->type; + $sourceTransaction = $this->getSourceTransaction($journal); + $destTransaction = $this->getDestinationTransaction($journal); + $sourceAccount = $sourceTransaction->account; + $sourceAccountType = $sourceAccount->accountType->type; + $destAccount = $destTransaction->account; + $destAccountType = $destAccount->accountType->type; + + if (!array_key_exists($type, $this->expected)) { + Log::info(sprintf('No source/destination info for transaction type %s.', $type)); + $this->info(sprintf('No source/destination info for transaction type %s.', $type)); + + return; + } + if (!array_key_exists($sourceAccountType, $this->expected[$type])) { + Log::debug(sprintf('Going to fix journal #%d', $journal->id)); + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); + + return; + } + $expectedTypes = $this->expected[$type][$sourceAccountType]; + if (!in_array($destAccountType, $expectedTypes, true)) { + Log::debug(sprintf('Going to fix journal #%d', $journal->id)); + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); + } + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + + */ + private function stupidLaravel(): void + { + $this->count = 0; + } } diff --git a/app/Console/Commands/Correction/FixFrontpageAccounts.php b/app/Console/Commands/Correction/FixFrontpageAccounts.php index 3175bc0e3d..970d570acb 100644 --- a/app/Console/Commands/Correction/FixFrontpageAccounts.php +++ b/app/Console/Commands/Correction/FixFrontpageAccounts.php @@ -36,18 +36,8 @@ use Illuminate\Console\Command; */ class FixFrontpageAccounts extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Fixes a preference that may include deleted accounts or accounts of another type.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-frontpage-accounts'; + protected $signature = 'firefly-iii:fix-frontpage-accounts'; /** * Execute the console command. @@ -56,8 +46,6 @@ class FixFrontpageAccounts extends Command */ public function handle(): int { - $start = microtime(true); - $users = User::get(); /** @var User $user */ foreach ($users as $user) { @@ -66,8 +54,7 @@ class FixFrontpageAccounts extends Command $this->fixPreference($preference); } } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verifying account preferences took %s seconds', $end)); + $this->info('Correct: account preferences are OK'); return 0; } diff --git a/app/Console/Commands/Correction/FixGroupAccounts.php b/app/Console/Commands/Correction/FixGroupAccounts.php index 31fa005c63..d2634990e0 100644 --- a/app/Console/Commands/Correction/FixGroupAccounts.php +++ b/app/Console/Commands/Correction/FixGroupAccounts.php @@ -36,18 +36,8 @@ use Illuminate\Console\Command; */ class FixGroupAccounts extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Unify the source / destination accounts of split groups.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:unify-group-accounts'; + protected $signature = 'firefly-iii:unify-group-accounts'; /** * Execute the console command. @@ -72,7 +62,7 @@ class FixGroupAccounts extends Command $handler->unifyAccounts($event); } - $this->line('Updated inconsistent transaction groups.'); + $this->info('Correct: updated possible inconsistent transaction groups.'); return 0; } diff --git a/app/Console/Commands/Correction/FixIbans.php b/app/Console/Commands/Correction/FixIbans.php index 4b3e70a289..ea8fb18154 100644 --- a/app/Console/Commands/Correction/FixIbans.php +++ b/app/Console/Commands/Correction/FixIbans.php @@ -34,18 +34,9 @@ use Illuminate\Support\Collection; */ class FixIbans extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Removes spaces from IBANs'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-ibans'; + protected $signature = 'firefly-iii:fix-ibans'; + private int $count = 0; /** * Execute the console command. @@ -57,6 +48,9 @@ class FixIbans extends Command $accounts = Account::whereNotNull('iban')->get(); $this->filterIbans($accounts); $this->countAndCorrectIbans($accounts); + if (0 === $this->count) { + $this->info('Correct: All IBANs are valid.'); + } return 0; } @@ -99,6 +93,7 @@ class FixIbans extends Command ); $account->iban = null; $account->save(); + $this->count++; } } @@ -124,6 +119,7 @@ class FixIbans extends Command $account->iban = $iban; $account->save(); $this->line(sprintf('Removed spaces from IBAN of account #%d', $account->id)); + $this->count++; } } } diff --git a/app/Console/Commands/Correction/FixLongDescriptions.php b/app/Console/Commands/Correction/FixLongDescriptions.php index 32fa810ec7..e08b78531f 100644 --- a/app/Console/Commands/Correction/FixLongDescriptions.php +++ b/app/Console/Commands/Correction/FixLongDescriptions.php @@ -34,18 +34,8 @@ use Illuminate\Console\Command; class FixLongDescriptions extends Command { private const MAX_LENGTH = 1000; - /** - * The console command description. - * - * @var string - */ protected $description = 'Fixes long descriptions in journals and groups.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-long-descriptions'; + protected $signature = 'firefly-iii:fix-long-descriptions'; /** * Execute the console command. @@ -54,14 +44,15 @@ class FixLongDescriptions extends Command */ public function handle(): int { - $start = microtime(true); $journals = TransactionJournal::get(['id', 'description']); + $count = 0; /** @var TransactionJournal $journal */ foreach ($journals as $journal) { if (strlen($journal->description) > self::MAX_LENGTH) { $journal->description = substr($journal->description, 0, self::MAX_LENGTH); $journal->save(); $this->line(sprintf('Truncated description of transaction journal #%d', $journal->id)); + $count++; } } @@ -72,11 +63,12 @@ class FixLongDescriptions extends Command $group->title = substr($group->title, 0, self::MAX_LENGTH); $group->save(); $this->line(sprintf('Truncated description of transaction group #%d', $group->id)); + $count++; } } - $end = round(microtime(true) - $start, 2); - $this->info('Verified all transaction group and journal title lengths.'); - $this->info(sprintf('Took %s seconds.', $end)); + if (0 === $count) { + $this->info('Correct: all transaction group and journal title lengths are within bounds.'); + } return 0; } diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php index a7f98d9fb9..d1f871f040 100644 --- a/app/Console/Commands/Correction/FixPiggies.php +++ b/app/Console/Commands/Correction/FixPiggies.php @@ -34,18 +34,8 @@ use Illuminate\Console\Command; */ class FixPiggies extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Fixes common issues with piggy banks.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-piggies'; + protected $signature = 'firefly-iii:fix-piggies'; /** * Execute the console command. @@ -55,7 +45,6 @@ class FixPiggies extends Command public function handle(): int { $count = 0; - $start = microtime(true); $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal'])->get(); /** @var PiggyBankEvent $event */ @@ -74,15 +63,12 @@ class FixPiggies extends Command } } if (0 === $count) { - $this->line('All piggy bank events are correct.'); + $this->info('Correct: all piggy bank events are OK.'); } if (0 !== $count) { $this->line(sprintf('Fixed %d piggy bank event(s).', $count)); } - $end = round(microtime(true) - $start, 2); - $this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end)); - return 0; } } diff --git a/app/Console/Commands/Correction/FixRecurringTransactions.php b/app/Console/Commands/Correction/FixRecurringTransactions.php index 05d182dfc8..b6599a6149 100644 --- a/app/Console/Commands/Correction/FixRecurringTransactions.php +++ b/app/Console/Commands/Correction/FixRecurringTransactions.php @@ -37,22 +37,11 @@ use Illuminate\Console\Command; */ class FixRecurringTransactions extends Command { - /** - * The console command description. - * - * @var string - */ - protected $description = 'Fixes recurring transactions with the wrong transaction type.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-recurring-transactions'; - /** @var RecurringRepositoryInterface */ - private $recurringRepos; - /** @var UserRepositoryInterface */ - private $userRepos; + protected $description = 'Fixes recurring transactions with the wrong transaction type.'; + protected $signature = 'firefly-iii:fix-recurring-transactions'; + private int $count = 0; + private RecurringRepositoryInterface $recurringRepos; + private UserRepositoryInterface $userRepos; /** * Execute the console command. @@ -61,11 +50,11 @@ class FixRecurringTransactions extends Command */ public function handle(): int { - $start = microtime(true); $this->stupidLaravel(); $this->correctTransactions(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Corrected recurring transactions in %s seconds.', $end)); + if (0 === $this->count) { + $this->info('Correct: all recurring transactions are OK.'); + } return 0; } @@ -111,6 +100,7 @@ class FixRecurringTransactions extends Command if (null !== $transactionType) { $recurrence->transaction_type_id = $transactionType->id; $recurrence->save(); + $this->count++; } } } diff --git a/app/Console/Commands/Correction/FixTransactionTypes.php b/app/Console/Commands/Correction/FixTransactionTypes.php index c818bd6ca1..3fb5fa4f6e 100644 --- a/app/Console/Commands/Correction/FixTransactionTypes.php +++ b/app/Console/Commands/Correction/FixTransactionTypes.php @@ -37,18 +37,8 @@ use Illuminate\Support\Collection; */ class FixTransactionTypes extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Make sure all transactions are of the correct type, based on source + dest.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-transaction-types'; + protected $signature = 'firefly-iii:fix-transaction-types'; /** * Execute the console command. @@ -57,7 +47,6 @@ class FixTransactionTypes extends Command */ public function handle(): int { - $start = microtime(true); $count = 0; $journals = $this->collectJournals(); /** @var TransactionJournal $journal */ @@ -67,13 +56,12 @@ class FixTransactionTypes extends Command $count++; } } - $end = round(microtime(true) - $start, 2); if ($count > 0) { - $this->info(sprintf('Corrected transaction type of %d transaction journals in %s seconds.', $count, $end)); + $this->info('Corrected transaction type of %d transaction journals.', $count); return 0; } - $this->line(sprintf('All transaction journals are of the correct transaction type (in %s seconds).', $end)); + $this->info('Correct: all transaction journals are of the correct transaction type'); return 0; } diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php index fdfc75ad8e..e947c7fd76 100644 --- a/app/Console/Commands/Correction/FixUnevenAmount.php +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -27,7 +27,6 @@ use DB; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; use stdClass; /** @@ -35,18 +34,8 @@ use stdClass; */ class FixUnevenAmount extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Fix journals with uneven amounts.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-uneven-amount'; + protected $signature = 'firefly-iii:fix-uneven-amount'; /** * Execute the console command. @@ -55,10 +44,7 @@ class FixUnevenAmount extends Command */ public function handle(): int { - Log::debug(sprintf('Now in %s', __METHOD__)); - $start = microtime(true); - $count = 0; - // get invalid journals + $count = 0; $journals = DB::table('transactions') ->groupBy('transaction_journal_id') ->whereNull('deleted_at') @@ -74,12 +60,9 @@ class FixUnevenAmount extends Command } } if (0 === $count) { - $this->info('Amount integrity OK!'); + $this->info('Correct: Database amount integrity is OK'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified amount integrity in %s seconds', $end)); - return 0; } diff --git a/app/Console/Commands/Correction/RemoveBills.php b/app/Console/Commands/Correction/RemoveBills.php index 4e4d6fc12c..227d0367b1 100644 --- a/app/Console/Commands/Correction/RemoveBills.php +++ b/app/Console/Commands/Correction/RemoveBills.php @@ -32,18 +32,8 @@ use Illuminate\Console\Command; */ class RemoveBills extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Remove bills from transactions that shouldn\'t have one.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:remove-bills'; + protected $signature = 'firefly-iii:remove-bills'; /** * Execute the console command. @@ -52,7 +42,6 @@ class RemoveBills extends Command */ public function handle(): int { - $start = microtime(true); /** @var TransactionType|null $withdrawal */ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); if (null === $withdrawal) { @@ -65,14 +54,10 @@ class RemoveBills extends Command $journal->bill_id = null; $journal->save(); } - if (0 === $journals->count()) { - $this->info('All transaction journals have correct bill information.'); - } if ($journals->count() > 0) { $this->info('Fixed all transaction journals so they have correct bill information.'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified bills / journals in %s seconds', $end)); + $this->info('Correct: verified bills / journals in %s seconds'); return 0; } diff --git a/app/Console/Commands/Correction/RenameMetaFields.php b/app/Console/Commands/Correction/RenameMetaFields.php index ff070f3707..d8d4c09dc6 100644 --- a/app/Console/Commands/Correction/RenameMetaFields.php +++ b/app/Console/Commands/Correction/RenameMetaFields.php @@ -31,18 +31,8 @@ use Illuminate\Console\Command; */ class RenameMetaFields extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Rename changed meta fields.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:rename-meta-fields'; + protected $signature = 'firefly-iii:rename-meta-fields'; private int $count; @@ -54,7 +44,6 @@ class RenameMetaFields extends Command public function handle(): int { $this->count = 0; - $start = microtime(true); $changes = [ 'original-source' => 'original_source', @@ -74,15 +63,11 @@ class RenameMetaFields extends Command $this->rename($original, $update); } if (0 === $this->count) { - $this->line('All meta fields are correct.'); + $this->info('Correct: all meta fields are correct.'); } if (0 !== $this->count) { - $this->line(sprintf('Renamed %d meta field(s).', $this->count)); + $this->info(sprintf('Renamed %d meta field(s).', $this->count)); } - - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Renamed meta fields in %s seconds', $end)); - return 0; } diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php index 99848de290..b6ad50ae35 100644 --- a/app/Console/Commands/Correction/TransferBudgets.php +++ b/app/Console/Commands/Correction/TransferBudgets.php @@ -33,18 +33,8 @@ use Illuminate\Support\Facades\Log; */ class TransferBudgets extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Removes budgets from transfers.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-transfer-budgets'; + protected $signature = 'firefly-iii:fix-transfer-budgets'; /** * Execute the console command. @@ -53,7 +43,6 @@ class TransferBudgets extends Command */ public function handle(): int { - $start = microtime(true); $set = TransactionJournal::distinct() ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') @@ -69,8 +58,7 @@ class TransferBudgets extends Command $count++; } if (0 === $count) { - $message = 'No invalid budget/journal entries.'; - Log::debug($message); + $message = 'Correct: no invalid budget/journal entries.'; $this->info($message); } if (0 !== $count) { @@ -78,9 +66,6 @@ class TransferBudgets extends Command Log::debug($message); $this->line($message); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified budget/journals in %s seconds.', $end)); - return 0; } } diff --git a/app/Console/Commands/Correction/TriggerCreditCalculation.php b/app/Console/Commands/Correction/TriggerCreditCalculation.php index 0b781f3ad5..bda570cf42 100644 --- a/app/Console/Commands/Correction/TriggerCreditCalculation.php +++ b/app/Console/Commands/Correction/TriggerCreditCalculation.php @@ -15,18 +15,8 @@ use Illuminate\Support\Facades\Log; */ class TriggerCreditCalculation extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Triggers the credit recalculation service for liabilities.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:trigger-credit-recalculation'; + protected $signature = 'firefly-iii:trigger-credit-recalculation'; /** * Execute the console command. @@ -59,7 +49,6 @@ class TriggerCreditCalculation extends Command ->whereIn('account_types.type', config('firefly.valid_liabilities')) ->get(['accounts.*']); foreach ($accounts as $account) { - Log::debug(sprintf('Processing account #%d ("%s")', $account->id, $account->name)); $this->processAccount($account); } } diff --git a/app/Console/Commands/Integrity/CreateGroupMemberships.php b/app/Console/Commands/Integrity/CreateGroupMemberships.php index 336d194152..9850e37d3f 100644 --- a/app/Console/Commands/Integrity/CreateGroupMemberships.php +++ b/app/Console/Commands/Integrity/CreateGroupMemberships.php @@ -38,18 +38,8 @@ use Illuminate\Support\Facades\Log; class CreateGroupMemberships extends Command { public const CONFIG_NAME = '560_create_group_memberships'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Update group memberships'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:create-group-memberships'; + protected $signature = 'firefly-iii:create-group-memberships'; /** * TODO move to helper. @@ -64,7 +54,6 @@ class CreateGroupMemberships extends Command $userGroup = UserGroup::where('title', $user->email)->first(); if (null === $userGroup) { $userGroup = UserGroup::create(['title' => $user->email]); - Log::debug(sprintf('Created new user group #%d ("%s")', $userGroup->id, $userGroup->title)); } $userRole = UserRole::where('title', UserRole::OWNER)->first(); @@ -83,15 +72,11 @@ class CreateGroupMemberships extends Command 'user_group_id' => $userGroup->id, ] ); - Log::debug('Created new membership.'); } if (null === $user->user_group_id) { $user->user_group_id = $userGroup->id; $user->save(); - Log::debug('Put user in default group.'); } - - Log::debug(sprintf('User #%d now has main group.', $user->id)); } /** @@ -102,12 +87,8 @@ class CreateGroupMemberships extends Command */ public function handle(): int { - $start = microtime(true); - $this->createGroupMemberships(); - - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Validated group memberships in %s seconds.', $end)); + $this->info('Correct: validated group memberships'); return 0; } @@ -121,9 +102,7 @@ class CreateGroupMemberships extends Command $users = User::get(); /** @var User $user */ foreach ($users as $user) { - Log::debug(sprintf('Manage group memberships for user #%d', $user->id)); self::createGroupMembership($user); - Log::debug(sprintf('Done with user #%d', $user->id)); } } } diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php index ea3e2e342f..18424eb281 100644 --- a/app/Console/Commands/Integrity/ReportEmptyObjects.php +++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php @@ -55,14 +55,11 @@ class ReportEmptyObjects extends Command */ public function handle(): int { - $start = microtime(true); $this->reportEmptyBudgets(); $this->reportEmptyCategories(); $this->reportEmptyTags(); $this->reportAccounts(); $this->reportBudgetLimits(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Report on empty objects finished in %s seconds', $end)); return 0; } diff --git a/app/Console/Commands/Integrity/ReportSum.php b/app/Console/Commands/Integrity/ReportSum.php index 5b5bcb8dc1..01422536d4 100644 --- a/app/Console/Commands/Integrity/ReportSum.php +++ b/app/Console/Commands/Integrity/ReportSum.php @@ -32,18 +32,8 @@ use Illuminate\Console\Command; */ class ReportSum extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Report on the total sum of transactions. Must be 0.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:report-sum'; + protected $signature = 'firefly-iii:report-sum'; /** * Execute the console command. @@ -62,7 +52,6 @@ class ReportSum extends Command */ private function reportSum(): void { - $start = microtime(true); /** @var UserRepositoryInterface $userRepository */ $userRepository = app(UserRepositoryInterface::class); @@ -74,10 +63,8 @@ class ReportSum extends Command $this->error($message); } if (0 === bccomp($sum, '0')) { - $this->info(sprintf('Amount integrity OK for user #%d', $user->id)); + $this->info(sprintf('Correct: Amount integrity OK for user #%d', $user->id)); } } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Report on total sum finished in %s seconds', $end)); } } diff --git a/app/Console/Commands/Integrity/RestoreOAuthKeys.php b/app/Console/Commands/Integrity/RestoreOAuthKeys.php index bbee5e20b7..49f0f915d7 100644 --- a/app/Console/Commands/Integrity/RestoreOAuthKeys.php +++ b/app/Console/Commands/Integrity/RestoreOAuthKeys.php @@ -26,25 +26,14 @@ namespace FireflyIII\Console\Commands\Integrity; use FireflyIII\Support\System\OAuthKeys; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; /** * Class RestoreOAuthKeys */ class RestoreOAuthKeys extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Will restore the OAuth keys generated for the system.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:restore-oauth-keys'; + protected $signature = 'firefly-iii:restore-oauth-keys'; /** * Execute the console command. @@ -95,38 +84,33 @@ class RestoreOAuthKeys extends Command */ private function restoreOAuthKeys(): void { - Log::debug('Going to restoreOAuthKeys()'); if (!$this->keysInDatabase() && !$this->keysOnDrive()) { - Log::debug('Keys are not in DB and keys are not on the drive.'); $this->generateKeys(); $this->storeKeysInDB(); - $this->line('Generated and stored new keys.'); + $this->line('Correct: generated and stored new keys.'); return; } if ($this->keysInDatabase() && !$this->keysOnDrive()) { - Log::debug('Keys are in DB and keys are not on the drive. Restore.'); $result = $this->restoreKeysFromDB(); if (true === $result) { - $this->line('Restored OAuth keys from database.'); + $this->line('Correct: restored OAuth keys from database.'); return; } - app('log')->warning('Could not restore keys. Will create new ones.'); $this->generateKeys(); $this->storeKeysInDB(); - $this->line('Generated and stored new keys.'); + $this->line('Correct: generated and stored new keys.'); return; } if (!$this->keysInDatabase() && $this->keysOnDrive()) { - Log::debug('Keys are not in DB and keys are on the drive. Save in DB.'); $this->storeKeysInDB(); - $this->line('Stored OAuth keys in database.'); + $this->line('Correct: stored OAuth keys in database.'); return; } - $this->line('OAuth keys are OK'); + $this->line('Correct: OAuth keys are OK'); } /** diff --git a/app/Console/Commands/Integrity/UpdateGroupInformation.php b/app/Console/Commands/Integrity/UpdateGroupInformation.php index 1fc377d7f8..f2ad93f02c 100644 --- a/app/Console/Commands/Integrity/UpdateGroupInformation.php +++ b/app/Console/Commands/Integrity/UpdateGroupInformation.php @@ -48,18 +48,8 @@ use Illuminate\Database\QueryException; */ class UpdateGroupInformation extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Makes sure that every object is linked to a group'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:upgrade-group-information'; + protected $signature = 'firefly-iii:upgrade-group-information'; /** * Execute the console command. @@ -126,7 +116,7 @@ class UpdateGroupInformation extends Command return; } if (0 !== $result) { - $this->line(sprintf('Moved %d %s objects to the correct group.', $result, str_replace('FireflyIII\\Models\\', '', $className))); + $this->info(sprintf('Correct: Moved %d %s objects to the correct group.', $result, str_replace('FireflyIII\\Models\\', '', $className))); } } } diff --git a/app/Console/Commands/System/ForceDecimalSize.php b/app/Console/Commands/System/ForceDecimalSize.php index 41bf0c7928..211251c743 100644 --- a/app/Console/Commands/System/ForceDecimalSize.php +++ b/app/Console/Commands/System/ForceDecimalSize.php @@ -103,7 +103,6 @@ class ForceDecimalSize extends Command $this->correctAmounts(); $this->updateDecimals(); } - $this->line('Done!'); return 0; } diff --git a/app/Console/Commands/System/ForceMigration.php b/app/Console/Commands/System/ForceMigration.php index 7abd2f2da7..4677996c7c 100644 --- a/app/Console/Commands/System/ForceMigration.php +++ b/app/Console/Commands/System/ForceMigration.php @@ -83,12 +83,10 @@ class ForceMigration extends Command $this->line('Dropping "migrations" table...'); sleep(2); Schema::dropIfExists('migrations'); - $this->line('Done!'); $this->line('Re-run all migrations...'); Artisan::call('migrate', ['--seed' => true]); sleep(2); $this->line(''); - $this->info('Done!'); $this->line('There is a good chance you just saw a lot of error messages.'); $this->line('No need to panic yet. First try to access Firefly III (again).'); $this->line('The issue, whatever it was, may have been solved now.'); diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php index 276e3e7668..512ffafd59 100644 --- a/app/Console/Commands/Upgrade/AccountCurrencies.php +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -43,54 +43,35 @@ use Psr\Container\NotFoundExceptionInterface; class AccountCurrencies extends Command { public const CONFIG_NAME = '480_account_currencies'; - /** - * The console command description. - * - * @var string - */ + protected $description = 'Give all accounts proper currency info.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}'; - /** @var AccountRepositoryInterface */ - private $accountRepos; - /** @var int */ - private $count; - /** @var UserRepositoryInterface */ - private $userRepos; + protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}'; + private AccountRepositoryInterface $accountRepos; + private int $count; + private UserRepositoryInterface $userRepos; /** * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. * * @return int - * @throws ContainerExceptionInterface - * @throws FireflyException - * @throws NotFoundExceptionInterface */ public function handle(): int { - Log::debug('Now in handle()'); $this->stupidLaravel(); - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } $this->updateAccountCurrencies(); if (0 === $this->count) { - $this->line('All accounts are OK.'); + $this->info('Correct: all account currencies are OK.'); } if (0 !== $this->count) { $this->line(sprintf('Corrected %d account(s).', $this->count)); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end)); $this->markAsExecuted(); return 0; @@ -124,7 +105,6 @@ class AccountCurrencies extends Command * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should * be called from the handle method instead of using the constructor to initialize the command. * - */ private function stupidLaravel(): void { @@ -139,23 +119,16 @@ class AccountCurrencies extends Command */ private function updateAccount(Account $account, TransactionCurrency $currency): void { - Log::debug(sprintf('Now in updateAccount(%d, %s)', $account->id, $currency->code)); $this->accountRepos->setUser($account->user); - $accountCurrency = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); - Log::debug(sprintf('Account currency is #%d', $accountCurrency)); - - $openingBalance = $this->accountRepos->getOpeningBalance($account); - $obCurrency = 0; + $openingBalance = $this->accountRepos->getOpeningBalance($account); + $obCurrency = 0; if (null !== $openingBalance) { $obCurrency = (int)$openingBalance->transaction_currency_id; - Log::debug('Account has opening balance.'); } - Log::debug(sprintf('Account OB currency is #%d.', $obCurrency)); // both 0? set to default currency: if (0 === $accountCurrency && 0 === $obCurrency) { - Log::debug(sprintf('Both currencies are 0, so reset to #%d (%s)', $currency->id, $currency->code)); AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $currency->id]); $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code)); @@ -166,7 +139,6 @@ class AccountCurrencies extends Command // account is set to 0, opening balance is not? if (0 === $accountCurrency && $obCurrency > 0) { - Log::debug(sprintf('Account is #0, OB is #%d, so set account to OB as well', $obCurrency)); AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); $this->line(sprintf('Account #%d ("%s") now has a currency setting (#%d).', $account->id, $account->name, $obCurrency)); $this->count++; @@ -175,7 +147,6 @@ class AccountCurrencies extends Command } // do not match and opening balance id is not null. if ($accountCurrency !== $obCurrency && null !== $openingBalance) { - Log::debug(sprintf('Account (#%d) and OB currency (#%d) are different. Overrule OB, set to account currency.', $accountCurrency, $obCurrency)); // update opening balance: $openingBalance->transaction_currency_id = $accountCurrency; $openingBalance->save(); @@ -190,7 +161,6 @@ class AccountCurrencies extends Command return; } - Log::debug('No changes necessary for this account.'); } /** @@ -198,10 +168,8 @@ class AccountCurrencies extends Command */ private function updateAccountCurrencies(): void { - Log::debug('Now in updateAccountCurrencies()'); $users = $this->userRepos->all(); $defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR'); - Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode)); foreach ($users as $user) { $this->updateCurrenciesForUser($user, $defaultCurrencyCode); } @@ -215,7 +183,6 @@ class AccountCurrencies extends Command */ private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void { - Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode)); $this->accountRepos->setUser($user); $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); @@ -224,7 +191,6 @@ class AccountCurrencies extends Command if (!is_string($defaultCurrencyCode)) { $defaultCurrencyCode = $systemCurrencyCode; } - Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode)); /** @var TransactionCurrency|null $defaultCurrency */ $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); diff --git a/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php b/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php index a468b6f9e9..7ccfa3fe00 100644 --- a/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php +++ b/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php @@ -56,20 +56,15 @@ class AppendBudgetLimitPeriods extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } $this->theresNoLimit(); - $this->markAsExecuted(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Fixed budget limits in %s seconds.', $end)); - return 0; } diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php index cf3ddadad9..c5bcba2a64 100644 --- a/app/Console/Commands/Upgrade/BackToJournals.php +++ b/app/Console/Commands/Upgrade/BackToJournals.php @@ -64,12 +64,11 @@ class BackToJournals extends Command */ public function handle(): int { - $start = microtime(true); if (!$this->isMigrated()) { $this->error('Please run firefly-iii:migrate-to-groups first.'); } if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -79,8 +78,7 @@ class BackToJournals extends Command $this->migrateAll(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Updated category and budget info for all transaction journals in %s seconds.', $end)); + $this->info('Correct: updated category and budget info for all transaction journals'); $this->markAsExecuted(); return 0; @@ -159,7 +157,6 @@ class BackToJournals extends Command */ private function migrateAll(): void { - Log::debug('Now in migrateAll()'); $this->migrateBudgets(); $this->migrateCategories(); @@ -225,19 +222,15 @@ class BackToJournals extends Command */ private function migrateCategories(): void { - Log::debug('Now in migrateCategories()'); $journals = new Collection(); $allIds = $this->getIdsForCategories(); - Log::debug(sprintf('Total: %d', count($allIds))); $chunks = array_chunk($allIds, 500); foreach ($chunks as $chunk) { - Log::debug('Now doing a chunk.'); $collected = TransactionJournal::whereIn('id', $chunk)->with(['transactions', 'categories', 'transactions.categories'])->get(); $journals = $journals->merge($collected); } - $this->line(sprintf('Check %d transaction journal(s) for category info.', count($journals))); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { $this->migrateCategoriesForJournal($journal); diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php index 6586001495..3203af7a39 100644 --- a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php +++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php @@ -58,10 +58,8 @@ class BudgetLimitCurrency extends Command */ public function handle(): int { - $start = microtime(true); - if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -88,11 +86,8 @@ class BudgetLimitCurrency extends Command } } if (0 === $count) { - $this->info('All budget limits are correct.'); + $this->info('Correct: all budget limits are OK.'); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified budget limits in %s seconds.', $end)); - $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php index a2b5d0e9ba..9b9a3b1aed 100644 --- a/app/Console/Commands/Upgrade/CCLiabilities.php +++ b/app/Console/Commands/Upgrade/CCLiabilities.php @@ -37,18 +37,8 @@ use Psr\Container\NotFoundExceptionInterface; class CCLiabilities extends Command { public const CONFIG_NAME = '480_cc_liabilities'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Convert old credit card liabilities.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:cc-liabilities {--F|force : Force the execution of this command.}'; + protected $signature = 'firefly-iii:cc-liabilities {--F|force : Force the execution of this command.}'; /** * Execute the console command. @@ -62,9 +52,8 @@ class CCLiabilities extends Command { $start = microtime(true); - if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -73,7 +62,8 @@ class CCLiabilities extends Command $ccType = AccountType::where('type', AccountType::CREDITCARD)->first(); $debtType = AccountType::where('type', AccountType::DEBT)->first(); if (null === $ccType || null === $debtType) { - $this->info('No incorrectly stored credit card liabilities.'); + $this->info('Correct: no incorrectly stored credit card liabilities.'); + $this->markAsExecuted(); return 0; } @@ -88,7 +78,7 @@ class CCLiabilities extends Command $this->info('Credit card liability types are no longer supported and have been converted to generic debts. See: https://bit.ly/FF3-credit-cards'); } if (0 === $accounts->count()) { - $this->info('No incorrectly stored credit card liabilities.'); + $this->info('Correct: no incorrectly stored credit card liabilities.'); } $end = round(microtime(true) - $start, 2); $this->info(sprintf('Verified credit card liabilities in %s seconds', $end)); diff --git a/app/Console/Commands/Upgrade/DecryptDatabase.php b/app/Console/Commands/Upgrade/DecryptDatabase.php index 443300be09..831b1d6576 100644 --- a/app/Console/Commands/Upgrade/DecryptDatabase.php +++ b/app/Console/Commands/Upgrade/DecryptDatabase.php @@ -41,18 +41,8 @@ use stdClass; */ class DecryptDatabase extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Decrypts the database.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:decrypt-all'; + protected $signature = 'firefly-iii:decrypt-all'; /** * Execute the console command. @@ -61,7 +51,6 @@ class DecryptDatabase extends Command */ public function handle(): int { - $this->line('Going to decrypt the database.'); $tables = [ 'accounts' => ['name', 'iban'], 'attachments' => ['filename', 'mime', 'title', 'description'], @@ -82,8 +71,6 @@ class DecryptDatabase extends Command foreach ($tables as $table => $fields) { $this->decryptTable($table, $fields); } - $this->info('Done!'); - return 0; } @@ -172,14 +159,14 @@ class DecryptDatabase extends Command private function decryptTable(string $table, array $fields): void { if ($this->isDecrypted($table)) { - $this->info(sprintf('No decryption required for table "%s".', $table)); + $this->info(sprintf('Correct: no decryption required for table "%s".', $table)); return; } foreach ($fields as $field) { $this->decryptField($table, $field); } - $this->line(sprintf('Decrypted the data in table "%s".', $table)); + $this->line(sprintf('Correct: decrypted the data in table "%s".', $table)); // mark as decrypted: $configName = sprintf('is_decrypted_%s', $table); app('fireflyconfig')->set($configName, true); diff --git a/app/Console/Commands/Upgrade/FixPostgresSequences.php b/app/Console/Commands/Upgrade/FixPostgresSequences.php index 95f7ea4127..b3f0fe08a4 100644 --- a/app/Console/Commands/Upgrade/FixPostgresSequences.php +++ b/app/Console/Commands/Upgrade/FixPostgresSequences.php @@ -53,8 +53,6 @@ class FixPostgresSequences extends Command public function handle(): int { if (DB::connection()->getName() !== 'pgsql') { - $this->info('Command executed successfully.'); - return 0; } $this->line('Going to verify PostgreSQL table sequences.'); diff --git a/app/Console/Commands/Upgrade/MigrateAttachments.php b/app/Console/Commands/Upgrade/MigrateAttachments.php index 7bcbc90d92..4ab0eb43d3 100644 --- a/app/Console/Commands/Upgrade/MigrateAttachments.php +++ b/app/Console/Commands/Upgrade/MigrateAttachments.php @@ -62,7 +62,7 @@ class MigrateAttachments extends Command { $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -94,7 +94,7 @@ class MigrateAttachments extends Command } } if (0 === $count) { - $this->line('All attachments are OK.'); + $this->info('Correct: all attachments are OK.'); } if (0 !== $count) { $this->line(sprintf('Updated %d attachment(s).', $count)); diff --git a/app/Console/Commands/Upgrade/MigrateJournalNotes.php b/app/Console/Commands/Upgrade/MigrateJournalNotes.php index 125a06fd36..72c91d4cbc 100644 --- a/app/Console/Commands/Upgrade/MigrateJournalNotes.php +++ b/app/Console/Commands/Upgrade/MigrateJournalNotes.php @@ -63,7 +63,7 @@ class MigrateJournalNotes extends Command $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -88,7 +88,7 @@ class MigrateJournalNotes extends Command } if (0 === $count) { - $this->line('No notes to migrate.'); + $this->info('Correct: No notes to migrate.'); } if (0 !== $count) { $this->line(sprintf('Migrated %d note(s).', $count)); diff --git a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php index da09efbfcb..3e16eb7e78 100644 --- a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php +++ b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php @@ -63,16 +63,15 @@ class MigrateRecurrenceMeta extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } $count = $this->migrateMetaData(); if (0 === $count) { - $this->line('No recurrence meta data migrated.'); + $this->info('Correct: no recurrence meta data migrated.'); } if ($count > 0) { $this->line(sprintf('Migrated %d meta data entries', $count)); @@ -80,9 +79,6 @@ class MigrateRecurrenceMeta extends Command $this->markAsExecuted(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Migrated recurrence meta data in %s seconds.', $end)); - return 0; } diff --git a/app/Console/Commands/Upgrade/MigrateRecurrenceType.php b/app/Console/Commands/Upgrade/MigrateRecurrenceType.php index fadff18491..29ee8dcfff 100644 --- a/app/Console/Commands/Upgrade/MigrateRecurrenceType.php +++ b/app/Console/Commands/Upgrade/MigrateRecurrenceType.php @@ -61,7 +61,6 @@ class MigrateRecurrenceType extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -69,11 +68,8 @@ class MigrateRecurrenceType extends Command } $this->migrateTypes(); - $this->markAsExecuted(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Update recurring transaction types in %s seconds.', $end)); return 0; } diff --git a/app/Console/Commands/Upgrade/MigrateTagLocations.php b/app/Console/Commands/Upgrade/MigrateTagLocations.php index 8e19076565..4b4b1d9917 100644 --- a/app/Console/Commands/Upgrade/MigrateTagLocations.php +++ b/app/Console/Commands/Upgrade/MigrateTagLocations.php @@ -60,18 +60,14 @@ class MigrateTagLocations extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } $this->migrateTagLocations(); $this->markAsExecuted(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Migrated tag locations in %s seconds.', $end)); - return 0; } diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php index e6ae2caf03..437a28ac81 100644 --- a/app/Console/Commands/Upgrade/MigrateToGroups.php +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -50,18 +50,8 @@ use Psr\Container\NotFoundExceptionInterface; class MigrateToGroups extends Command { public const CONFIG_NAME = '480_migrated_to_groups'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Migrates a pre-4.7.8 transaction structure to the 4.7.8+ transaction structure.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}'; + protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}'; private JournalCLIRepositoryInterface $cliRepository; private int $count; private TransactionGroupFactory $groupFactory; @@ -72,17 +62,13 @@ class MigrateToGroups extends Command * Execute the console command. * * @return int - * @throws ContainerExceptionInterface - * @throws FireflyException - * @throws NotFoundExceptionInterface */ public function handle(): int { $this->stupidLaravel(); - $start = microtime(true); if ($this->isMigrated() && true !== $this->option('force')) { - $this->info('Database already seems to be migrated.'); + $this->info('Correct: database is already migrated.'); return 0; } @@ -92,22 +78,14 @@ class MigrateToGroups extends Command } - Log::debug('---- start group migration ----'); $this->makeGroupsFromSplitJournals(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Migrate split journals to groups in %s seconds.', $end)); - - $start = microtime(true); $this->makeGroupsFromAll(); - Log::debug('---- end group migration ----'); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Migrate all journals to groups in %s seconds.', $end)); if (0 !== $this->count) { $this->line(sprintf('Migrated %d transaction journal(s).', $this->count)); } if (0 === $this->count) { - $this->line('No journals to migrate to groups.'); + $this->info('Correct: no journals to migrate to groups.'); } $this->markAsMigrated(); @@ -115,19 +93,122 @@ class MigrateToGroups extends Command } /** - * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is - * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should - * be called from the handle method instead of using the constructor to initialize the command. + * @param TransactionJournal $journal + * @param Transaction $transaction * - + * @return Transaction|null */ - private function stupidLaravel(): void + private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction { - $this->count = 0; - $this->journalRepository = app(JournalRepositoryInterface::class); - $this->service = app(JournalDestroyService::class); - $this->groupFactory = app(TransactionGroupFactory::class); - $this->cliRepository = app(JournalCLIRepositoryInterface::class); + $set = $journal->transactions->filter( + static function (Transaction $subject) use ($transaction) { + $amount = (float)$transaction->amount * -1 === (float)$subject->amount; // intentional float + $identifier = $transaction->identifier === $subject->identifier; + Log::debug(sprintf('Amount the same? %s', var_export($amount, true))); + Log::debug(sprintf('ID the same? %s', var_export($identifier, true))); + + return $amount && $identifier; + } + ); + + return $set->first(); + } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + private function getDestinationTransactions(TransactionJournal $journal): Collection + { + return $journal->transactions->filter( + static function (Transaction $transaction) { + return $transaction->amount > 0; + } + ); + } + + /** + * @param Transaction $left + * @param Transaction $right + * + * @return int|null + */ + private function getTransactionBudget(Transaction $left, Transaction $right): ?int + { + Log::debug('Now in getTransactionBudget()'); + + // try to get a budget ID from the left transaction: + /** @var Budget|null $budget */ + $budget = $left->budgets()->first(); + if (null !== $budget) { + Log::debug(sprintf('Return budget #%d, from transaction #%d', $budget->id, $left->id)); + + return (int)$budget->id; + } + + // try to get a budget ID from the right transaction: + /** @var Budget|null $budget */ + $budget = $right->budgets()->first(); + if (null !== $budget) { + Log::debug(sprintf('Return budget #%d, from transaction #%d', $budget->id, $right->id)); + + return (int)$budget->id; + } + Log::debug('Neither left or right have a budget, return NULL'); + + // if all fails, return NULL. + return null; + } + + /** + * @param Transaction $left + * @param Transaction $right + * + * @return int|null + */ + private function getTransactionCategory(Transaction $left, Transaction $right): ?int + { + Log::debug('Now in getTransactionCategory()'); + + // try to get a category ID from the left transaction: + /** @var Category|null $category */ + $category = $left->categories()->first(); + if (null !== $category) { + Log::debug(sprintf('Return category #%d, from transaction #%d', $category->id, $left->id)); + + return (int)$category->id; + } + + // try to get a category ID from the left transaction: + /** @var Category|null $category */ + $category = $right->categories()->first(); + if (null !== $category) { + Log::debug(sprintf('Return category #%d, from transaction #%d', $category->id, $category->id)); + + return (int)$category->id; + } + Log::debug('Neither left or right have a category, return NULL'); + + // if all fails, return NULL. + return null; + } + + /** + * @param array $array + */ + private function giveGroup(array $array): void + { + $groupId = DB::table('transaction_groups')->insertGetId( + [ + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'title' => null, + 'user_id' => $array['user_id'], + ] + ); + DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]); + $this->count++; } /** @@ -145,6 +226,26 @@ class MigrateToGroups extends Command return false; } + /** + * Gives all journals without a group a group. + */ + private function makeGroupsFromAll(): void + { + $orphanedJournals = $this->cliRepository->getJournalsWithoutGroup(); + $total = count($orphanedJournals); + if ($total > 0) { + Log::debug(sprintf('Going to convert %d transaction journals. Please hold..', $total)); + $this->line(sprintf('Going to convert %d transaction journals. Please hold..', $total)); + /** @var array $array */ + foreach ($orphanedJournals as $array) { + $this->giveGroup($array); + } + } + if (0 === $total) { + $this->info('Correct: no need to convert transaction journals.'); + } + } + /** * @throws Exception */ @@ -158,9 +259,6 @@ class MigrateToGroups extends Command $this->makeMultiGroup($journal); } } - if (0 === $splitJournals->count()) { - $this->info('Found no split transaction journals. Nothing to do.'); - } } /** @@ -305,145 +403,6 @@ class MigrateToGroups extends Command ); } - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - private function getDestinationTransactions(TransactionJournal $journal): Collection - { - return $journal->transactions->filter( - static function (Transaction $transaction) { - return $transaction->amount > 0; - } - ); - } - - /** - * @param TransactionJournal $journal - * @param Transaction $transaction - * - * @return Transaction|null - */ - private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction - { - $set = $journal->transactions->filter( - static function (Transaction $subject) use ($transaction) { - $amount = (float)$transaction->amount * -1 === (float)$subject->amount; // intentional float - $identifier = $transaction->identifier === $subject->identifier; - Log::debug(sprintf('Amount the same? %s', var_export($amount, true))); - Log::debug(sprintf('ID the same? %s', var_export($identifier, true))); - - return $amount && $identifier; - } - ); - - return $set->first(); - } - - /** - * @param Transaction $left - * @param Transaction $right - * - * @return int|null - */ - private function getTransactionBudget(Transaction $left, Transaction $right): ?int - { - Log::debug('Now in getTransactionBudget()'); - - // try to get a budget ID from the left transaction: - /** @var Budget|null $budget */ - $budget = $left->budgets()->first(); - if (null !== $budget) { - Log::debug(sprintf('Return budget #%d, from transaction #%d', $budget->id, $left->id)); - - return (int)$budget->id; - } - - // try to get a budget ID from the right transaction: - /** @var Budget|null $budget */ - $budget = $right->budgets()->first(); - if (null !== $budget) { - Log::debug(sprintf('Return budget #%d, from transaction #%d', $budget->id, $right->id)); - - return (int)$budget->id; - } - Log::debug('Neither left or right have a budget, return NULL'); - - // if all fails, return NULL. - return null; - } - - /** - * @param Transaction $left - * @param Transaction $right - * - * @return int|null - */ - private function getTransactionCategory(Transaction $left, Transaction $right): ?int - { - Log::debug('Now in getTransactionCategory()'); - - // try to get a category ID from the left transaction: - /** @var Category|null $category */ - $category = $left->categories()->first(); - if (null !== $category) { - Log::debug(sprintf('Return category #%d, from transaction #%d', $category->id, $left->id)); - - return (int)$category->id; - } - - // try to get a category ID from the left transaction: - /** @var Category|null $category */ - $category = $right->categories()->first(); - if (null !== $category) { - Log::debug(sprintf('Return category #%d, from transaction #%d', $category->id, $category->id)); - - return (int)$category->id; - } - Log::debug('Neither left or right have a category, return NULL'); - - // if all fails, return NULL. - return null; - } - - /** - * Gives all journals without a group a group. - */ - private function makeGroupsFromAll(): void - { - $orphanedJournals = $this->cliRepository->getJournalsWithoutGroup(); - $total = count($orphanedJournals); - if ($total > 0) { - Log::debug(sprintf('Going to convert %d transaction journals. Please hold..', $total)); - $this->line(sprintf('Going to convert %d transaction journals. Please hold..', $total)); - /** @var array $array */ - foreach ($orphanedJournals as $array) { - $this->giveGroup($array); - } - } - if (0 === $total) { - $this->info('No need to convert transaction journals.'); - } - } - - /** - * @param array $array - */ - private function giveGroup(array $array): void - { - $groupId = DB::table('transaction_groups')->insertGetId( - [ - 'created_at' => date('Y-m-d H:i:s'), - 'updated_at' => date('Y-m-d H:i:s'), - 'title' => null, - 'user_id' => $array['user_id'], - ] - ); - DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]); - $this->count++; - } - /** * */ @@ -451,4 +410,20 @@ class MigrateToGroups extends Command { app('fireflyconfig')->set(self::CONFIG_NAME, true); } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->journalRepository = app(JournalRepositoryInterface::class); + $this->service = app(JournalDestroyService::class); + $this->groupFactory = app(TransactionGroupFactory::class); + $this->cliRepository = app(JournalCLIRepositoryInterface::class); + } } diff --git a/app/Console/Commands/Upgrade/MigrateToRules.php b/app/Console/Commands/Upgrade/MigrateToRules.php index 79a28c0d03..dbf73a3661 100644 --- a/app/Console/Commands/Upgrade/MigrateToRules.php +++ b/app/Console/Commands/Upgrade/MigrateToRules.php @@ -79,7 +79,7 @@ class MigrateToRules extends Command if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -92,7 +92,7 @@ class MigrateToRules extends Command } if (0 === $this->count) { - $this->line('All bills are OK.'); + $this->info('Correct: all bills are OK.'); } if (0 !== $this->count) { $this->line(sprintf('Verified and fixed %d bill(s).', $this->count)); diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php index faeb76a83f..77ecf18e2d 100644 --- a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -44,18 +44,8 @@ use Psr\Container\NotFoundExceptionInterface; class OtherCurrenciesCorrections extends Command { public const CONFIG_NAME = '480_other_currencies'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Update all journal currency information.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; + protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; private array $accountCurrencies; private AccountRepositoryInterface $accountRepos; private JournalCLIRepositoryInterface $cliRepos; @@ -74,10 +64,9 @@ class OtherCurrenciesCorrections extends Command public function handle(): int { $this->stupidLaravel(); - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -86,28 +75,78 @@ class OtherCurrenciesCorrections extends Command $this->updateOtherJournalsCurrencies(); $this->markAsExecuted(); - $this->line(sprintf('Verified %d transaction(s) and journal(s).', $this->count)); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end)); + $this->info('Correct: verified and fixed transaction currencies.'); return 0; } /** - * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is - * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should - * be called from the handle method instead of using the constructor to initialize the command. + * @param Account $account * - + * @return TransactionCurrency|null */ - private function stupidLaravel(): void + private function getCurrency(Account $account): ?TransactionCurrency { - $this->count = 0; - $this->accountCurrencies = []; - $this->accountRepos = app(AccountRepositoryInterface::class); - $this->currencyRepos = app(CurrencyRepositoryInterface::class); - $this->journalRepos = app(JournalRepositoryInterface::class); - $this->cliRepos = app(JournalCLIRepositoryInterface::class); + $accountId = $account->id; + if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { + return null; + } + if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + return $this->accountCurrencies[$accountId]; + } + $currency = $this->accountRepos->getAccountCurrency($account); + if (null === $currency) { + $this->accountCurrencies[$accountId] = 0; + + return null; + } + $this->accountCurrencies[$accountId] = $currency; + + return $currency; + } + + /** + * Gets the transaction that determines the transaction that "leads" and will determine + * the currency to be used by all transactions, and the journal itself. + * + * @param TransactionJournal $journal + * + * @return Transaction|null + */ + private function getLeadTransaction(TransactionJournal $journal): ?Transaction + { + /** @var Transaction $lead */ + $lead = null; + switch ($journal->transactionType->type) { + default: + break; + case TransactionType::WITHDRAWAL: + $lead = $journal->transactions()->where('amount', '<', 0)->first(); + break; + case TransactionType::DEPOSIT: + $lead = $journal->transactions()->where('amount', '>', 0)->first(); + break; + case TransactionType::OPENING_BALANCE: + // whichever isn't an initial balance account: + $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin( + 'account_types', + 'accounts.account_type_id', + '=', + 'account_types.id' + )->where('account_types.type', '!=', AccountType::INITIAL_BALANCE)->first(['transactions.*']); + break; + case TransactionType::RECONCILIATION: + // whichever isn't the reconciliation account: + $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin( + 'account_types', + 'accounts.account_type_id', + '=', + 'account_types.id' + )->where('account_types.type', '!=', AccountType::RECONCILIATION)->first(['transactions.*']); + break; + } + + return $lead; } /** @@ -126,21 +165,28 @@ class OtherCurrenciesCorrections extends Command } /** - * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for - * the accounts they are linked to. - * Both source and destination must match the respective currency preference of the related asset account. - * So FF3 must verify all transactions. + * */ - private function updateOtherJournalsCurrencies(): void + private function markAsExecuted(): void { - $set = $this->cliRepos->getAllJournals( - [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,] - ); + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $this->updateJournalCurrency($journal); - } + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->accountCurrencies = []; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->currencyRepos = app(CurrencyRepositoryInterface::class); + $this->journalRepos = app(JournalRepositoryInterface::class); + $this->cliRepos = app(JournalCLIRepositoryInterface::class); } /** @@ -200,79 +246,20 @@ class OtherCurrenciesCorrections extends Command } /** - * Gets the transaction that determines the transaction that "leads" and will determine - * the currency to be used by all transactions, and the journal itself. - * - * @param TransactionJournal $journal - * - * @return Transaction|null + * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for + * the accounts they are linked to. + * Both source and destination must match the respective currency preference of the related asset account. + * So FF3 must verify all transactions. */ - private function getLeadTransaction(TransactionJournal $journal): ?Transaction + private function updateOtherJournalsCurrencies(): void { - /** @var Transaction $lead */ - $lead = null; - switch ($journal->transactionType->type) { - default: - break; - case TransactionType::WITHDRAWAL: - $lead = $journal->transactions()->where('amount', '<', 0)->first(); - break; - case TransactionType::DEPOSIT: - $lead = $journal->transactions()->where('amount', '>', 0)->first(); - break; - case TransactionType::OPENING_BALANCE: - // whichever isn't an initial balance account: - $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin( - 'account_types', - 'accounts.account_type_id', - '=', - 'account_types.id' - )->where('account_types.type', '!=', AccountType::INITIAL_BALANCE)->first(['transactions.*']); - break; - case TransactionType::RECONCILIATION: - // whichever isn't the reconciliation account: - $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin( - 'account_types', - 'accounts.account_type_id', - '=', - 'account_types.id' - )->where('account_types.type', '!=', AccountType::RECONCILIATION)->first(['transactions.*']); - break; + $set = $this->cliRepos->getAllJournals( + [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,] + ); + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $this->updateJournalCurrency($journal); } - - return $lead; - } - - /** - * @param Account $account - * - * @return TransactionCurrency|null - */ - private function getCurrency(Account $account): ?TransactionCurrency - { - $accountId = $account->id; - if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { - return null; - } - if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { - return $this->accountCurrencies[$accountId]; - } - $currency = $this->accountRepos->getAccountCurrency($account); - if (null === $currency) { - $this->accountCurrencies[$accountId] = 0; - - return null; - } - $this->accountCurrencies[$accountId] = $currency; - - return $currency; - } - - /** - * - */ - private function markAsExecuted(): void - { - app('fireflyconfig')->set(self::CONFIG_NAME, true); } } diff --git a/app/Console/Commands/Upgrade/RenameAccountMeta.php b/app/Console/Commands/Upgrade/RenameAccountMeta.php index 0d9c5d5e82..b2151f729c 100644 --- a/app/Console/Commands/Upgrade/RenameAccountMeta.php +++ b/app/Console/Commands/Upgrade/RenameAccountMeta.php @@ -58,10 +58,8 @@ class RenameAccountMeta extends Command */ public function handle(): int { - $start = microtime(true); - if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -88,15 +86,12 @@ class RenameAccountMeta extends Command $this->markAsExecuted(); if (0 === $count) { - $this->line('All account meta is OK.'); + $this->info('Correct: all account meta is OK.'); } if (0 !== $count) { $this->line(sprintf('Renamed %d account meta entries (entry).', $count)); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Fixed account meta data in %s seconds.', $end)); - return 0; } diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index 8eca9098f3..ccebe548bd 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -40,22 +40,10 @@ use Schema; class TransactionIdentifier extends Command { public const CONFIG_NAME = '480_transaction_identifier'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Fixes transaction identifiers.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:transaction-identifiers {--F|force : Force the execution of this command.}'; - /** @var JournalCLIRepositoryInterface */ - private $cliRepository; - /** @var int */ - private $count; + protected $signature = 'firefly-iii:transaction-identifiers {--F|force : Force the execution of this command.}'; + private JournalCLIRepositoryInterface $cliRepository; + private int $count; /** * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier @@ -74,10 +62,9 @@ class TransactionIdentifier extends Command public function handle(): int { $this->stupidLaravel(); - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -94,14 +81,12 @@ class TransactionIdentifier extends Command } if (0 === $this->count) { - $this->line('All split journal transaction identifiers are correct.'); + $this->line('Correct: all split journal transaction identifiers are OK.'); } if (0 !== $this->count) { - $this->line(sprintf('Fixed %d split journal transaction identifier(s).', $this->count)); + $this->line(sprintf('Correct: fixed %d split journal transaction identifier(s).', $this->count)); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php index 99331d9159..23395e7540 100644 --- a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php @@ -42,18 +42,8 @@ use Psr\Container\NotFoundExceptionInterface; class TransferCurrenciesCorrections extends Command { public const CONFIG_NAME = '480_transfer_currencies'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Updates transfer currency information.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:transfer-currencies {--F|force : Force the execution of this command.}'; + protected $signature = 'firefly-iii:transfer-currencies {--F|force : Force the execution of this command.}'; private array $accountCurrencies; private AccountRepositoryInterface $accountRepos; private JournalCLIRepositoryInterface $cliRepos; @@ -70,17 +60,13 @@ class TransferCurrenciesCorrections extends Command * Execute the console command. * * @return int - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface */ public function handle(): int { $this->stupidLaravel(); - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } @@ -90,50 +76,292 @@ class TransferCurrenciesCorrections extends Command $this->markAsExecuted(); if (0 === $this->count) { - $message = 'All transfers have correct currency information.'; - $this->line($message); - Log::debug($message); + $message = 'Correct: all transfers have correct currency information.'; + $this->info($message); } if (0 !== $this->count) { $message = sprintf('Verified currency information of %d transfer(s).', $this->count); $this->line($message); - Log::debug($message); } - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed currency information for transfers in %s seconds.', $end)); - return 0; } /** - * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is - * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should - * be called from the handle method instead of using the constructor to initialize the command. - * - + * The destination transaction must have a currency. If not, it will be added by + * taking it from the destination account's preference. */ - private function stupidLaravel(): void + private function fixDestNoCurrency(): void { - $this->count = 0; - $this->accountRepos = app(AccountRepositoryInterface::class); - $this->cliRepos = app(JournalCLIRepositoryInterface::class); - $this->accountCurrencies = []; - $this->resetInformation(); + if (null === $this->destinationTransaction->transaction_currency_id && null !== $this->destinationCurrency) { + $this->destinationTransaction + ->transaction_currency_id + = (int)$this->destinationCurrency->id; + $message = sprintf( + 'Transaction #%d has no currency setting, now set to %s.', + $this->destinationTransaction->id, + $this->destinationCurrency->code + ); + $this->line($message); + $this->count++; + $this->destinationTransaction->save(); + } } /** - * Reset all the class fields for the current transfer. + * If the foreign amount of the destination transaction is null, but that of the other isn't, use this piece of code + * to restore it. + */ + private function fixDestNullForeignAmount(): void + { + if (null === $this->destinationTransaction->foreign_amount && null !== $this->sourceTransaction->foreign_amount) { + $this->destinationTransaction->foreign_amount = bcmul((string)$this->sourceTransaction->foreign_amount, '-1'); + $this->destinationTransaction->save(); + $this->count++; + $this->line( + sprintf( + 'Restored foreign amount of destination transaction #%d to %s', + $this->destinationTransaction->id, + $this->destinationTransaction->foreign_amount + ) + ); + } + } + + /** + * The destination transaction must have the correct currency. If not, it will be set by + * taking it from the destination account's preference. + */ + private function fixDestinationUnmatchedCurrency(): void + { + if (null !== $this->destinationCurrency + && null === $this->destinationTransaction->foreign_amount + && (int)$this->destinationTransaction->transaction_currency_id !== (int)$this->destinationCurrency->id + ) { + $message = sprintf( + 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', + $this->destinationTransaction->id, + $this->destinationTransaction->transaction_currency_id, + $this->destinationAccount->id, + $this->destinationTransaction->amount + ); + $this->line($message); + $this->count++; + $this->destinationTransaction->transaction_currency_id = (int)$this->destinationCurrency->id; + $this->destinationTransaction->save(); + } + } + + /** + * If the destination account currency is the same as the source currency, + * both foreign_amount and foreign_currency_id fields must be NULL + * for both transactions (because foreign currency info would not make sense) + */ + private function fixInvalidForeignCurrency(): void + { + if ((int)$this->destinationCurrency->id === (int)$this->sourceCurrency->id) { + // update both transactions to match: + $this->sourceTransaction->foreign_amount = null; + $this->sourceTransaction->foreign_currency_id = null; + + $this->destinationTransaction->foreign_amount = null; + $this->destinationTransaction->foreign_currency_id = null; + + $this->sourceTransaction->save(); + $this->destinationTransaction->save(); + } + } + + /** + * If destination account currency is different from source account currency, + * then both transactions must get the source account's currency as normal currency + * and the opposing account's currency as foreign currency. + */ + private function fixMismatchedForeignCurrency(): void + { + if ((int)$this->sourceCurrency->id !== (int)$this->destinationCurrency->id) { + $this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id; + $this->sourceTransaction->foreign_currency_id = $this->destinationCurrency->id; + $this->destinationTransaction->transaction_currency_id = $this->sourceCurrency->id; + $this->destinationTransaction->foreign_currency_id = $this->destinationCurrency->id; + + $this->sourceTransaction->save(); + $this->destinationTransaction->save(); + $this->count++; + $this->line(sprintf('Verified foreign currency ID of transaction #%d and #%d', $this->sourceTransaction->id, $this->destinationTransaction->id)); + } + } + + /** + * The source transaction must have a currency. If not, it will be added by + * taking it from the source account's preference. + */ + private function fixSourceNoCurrency(): void + { + if (null === $this->sourceTransaction->transaction_currency_id && null !== $this->sourceCurrency) { + $this->sourceTransaction + ->transaction_currency_id + = (int)$this->sourceCurrency->id; + $message = sprintf( + 'Transaction #%d has no currency setting, now set to %s.', + $this->sourceTransaction->id, + $this->sourceCurrency->code + ); + $this->line($message); + $this->count++; + $this->sourceTransaction->save(); + } + } + + /** + * If the foreign amount of the source transaction is null, but that of the other isn't, use this piece of code + * to restore it. + */ + private function fixSourceNullForeignAmount(): void + { + if (null === $this->sourceTransaction->foreign_amount && null !== $this->destinationTransaction->foreign_amount) { + $this->sourceTransaction->foreign_amount = bcmul((string)$this->destinationTransaction->foreign_amount, '-1'); + $this->sourceTransaction->save(); + $this->count++; + $this->line( + sprintf( + 'Restored foreign amount of source transaction #%d to %s', + $this->sourceTransaction->id, + $this->sourceTransaction->foreign_amount + ) + ); + } + } + + /** + * The source transaction must have the correct currency. If not, it will be set by + * taking it from the source account's preference. + */ + private function fixSourceUnmatchedCurrency(): void + { + if (null !== $this->sourceCurrency + && null === $this->sourceTransaction->foreign_amount + && (int)$this->sourceTransaction->transaction_currency_id !== (int)$this->sourceCurrency->id + ) { + $message = sprintf( + 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', + $this->sourceTransaction->id, + $this->sourceTransaction->transaction_currency_id, + $this->sourceAccount->id, + $this->sourceTransaction->amount + ); + $this->line($message); + $this->count++; + $this->sourceTransaction->transaction_currency_id = (int)$this->sourceCurrency->id; + $this->sourceTransaction->save(); + } + } + + /** + * This method makes sure that the transaction journal uses the currency given in the source transaction. + * + * @param TransactionJournal $journal + */ + private function fixTransactionJournalCurrency(TransactionJournal $journal): void + { + if ((int)$journal->transaction_currency_id !== (int)$this->sourceCurrency->id) { + $oldCurrencyCode = $journal->transactionCurrency->code ?? '(nothing)'; + $journal->transaction_currency_id = $this->sourceCurrency->id; + $message = sprintf( + 'Transfer #%d ("%s") has been updated to use %s instead of %s.', + $journal->id, + $journal->description, + $this->sourceCurrency->code, + $oldCurrencyCode + ); + $this->count++; + $this->line($message); + $journal->save(); + } + } + + /** + * @param Account $account + * + * @return TransactionCurrency|null + */ + private function getCurrency(Account $account): ?TransactionCurrency + { + $accountId = $account->id; + if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { + return null; + } + if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + return $this->accountCurrencies[$accountId]; + } + $currency = $this->accountRepos->getAccountCurrency($account); + if (null === $currency) { + $this->accountCurrencies[$accountId] = 0; + + return null; + } + $this->accountCurrencies[$accountId] = $currency; + + return $currency; + } + + /** + * Extract destination transaction, destination account + destination account currency from the journal. + * + * @param TransactionJournal $journal * */ - private function resetInformation(): void + private function getDestinationInformation(TransactionJournal $journal): void { - $this->sourceTransaction = null; - $this->sourceAccount = null; - $this->sourceCurrency = null; - $this->destinationTransaction = null; - $this->destinationAccount = null; - $this->destinationCurrency = null; + $this->destinationTransaction = $this->getDestinationTransaction($journal); + $this->destinationAccount = $this->destinationTransaction?->account; + $this->destinationCurrency = null === $this->destinationAccount ? null : $this->getCurrency($this->destinationAccount); + } + + /** + * @param TransactionJournal $transfer + * + * @return Transaction|null + */ + private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction + { + return $transfer->transactions()->where('amount', '>', 0)->first(); + } + + /** + * Extract source transaction, source account + source account currency from the journal. + * + * @param TransactionJournal $journal + * + + */ + private function getSourceInformation(TransactionJournal $journal): void + { + $this->sourceTransaction = $this->getSourceTransaction($journal); + $this->sourceAccount = $this->sourceTransaction?->account; + $this->sourceCurrency = null === $this->sourceAccount ? null : $this->getCurrency($this->sourceAccount); + } + + /** + * @param TransactionJournal $transfer + * + * @return Transaction|null + */ + private function getSourceTransaction(TransactionJournal $transfer): ?Transaction + { + return $transfer->transactions()->where('amount', '<', 0)->first(); + } + + /** + * Is either the source or destination transaction NULL? + * + * @return bool + */ + private function isEmptyTransactions(): bool + { + return null === $this->sourceTransaction || null === $this->destinationTransaction + || null === $this->sourceAccount + || null === $this->destinationAccount; } /** @@ -151,6 +379,71 @@ class TransferCurrenciesCorrections extends Command return false; } + /** + * @return bool + */ + private function isNoCurrencyPresent(): bool + { + // source account must have a currency preference. + if (null === $this->sourceCurrency) { + $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $this->sourceAccount->id, $this->sourceAccount->name); + Log::error($message); + $this->error($message); + + return true; + } + + // destination account must have a currency preference. + if (null === $this->destinationCurrency) { + $message = sprintf( + 'Account #%d ("%s") must have currency preference but has none.', + $this->destinationAccount->id, + $this->destinationAccount->name + ); + Log::error($message); + $this->error($message); + + return true; + } + + return false; + } + + /** + * Is this a split transaction journal? + * + * @param TransactionJournal $transfer + * + * @return bool + */ + private function isSplitJournal(TransactionJournal $transfer): bool + { + return $transfer->transactions->count() > 2; + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * Reset all the class fields for the current transfer. + * + + */ + private function resetInformation(): void + { + $this->sourceTransaction = null; + $this->sourceAccount = null; + $this->sourceCurrency = null; + $this->destinationTransaction = null; + $this->destinationAccount = null; + $this->destinationCurrency = null; + } + /** * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they @@ -168,6 +461,22 @@ class TransferCurrenciesCorrections extends Command } } + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->cliRepos = app(JournalCLIRepositoryInterface::class); + $this->accountCurrencies = []; + $this->resetInformation(); + } + /** * @param TransactionJournal $transfer */ @@ -230,351 +539,4 @@ class TransferCurrenciesCorrections extends Command // fix journal itself: $this->fixTransactionJournalCurrency($transfer); } - - /** - * Is this a split transaction journal? - * - * @param TransactionJournal $transfer - * - * @return bool - */ - private function isSplitJournal(TransactionJournal $transfer): bool - { - return $transfer->transactions->count() > 2; - } - - /** - * Extract source transaction, source account + source account currency from the journal. - * - * @param TransactionJournal $journal - * - - */ - private function getSourceInformation(TransactionJournal $journal): void - { - $this->sourceTransaction = $this->getSourceTransaction($journal); - $this->sourceAccount = $this->sourceTransaction?->account; - $this->sourceCurrency = null === $this->sourceAccount ? null : $this->getCurrency($this->sourceAccount); - } - - /** - * @param TransactionJournal $transfer - * - * @return Transaction|null - */ - private function getSourceTransaction(TransactionJournal $transfer): ?Transaction - { - return $transfer->transactions()->where('amount', '<', 0)->first(); - } - - /** - * @param Account $account - * - * @return TransactionCurrency|null - */ - private function getCurrency(Account $account): ?TransactionCurrency - { - $accountId = $account->id; - if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { - return null; - } - if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { - return $this->accountCurrencies[$accountId]; - } - $currency = $this->accountRepos->getAccountCurrency($account); - if (null === $currency) { - $this->accountCurrencies[$accountId] = 0; - - return null; - } - $this->accountCurrencies[$accountId] = $currency; - - return $currency; - } - - /** - * Extract destination transaction, destination account + destination account currency from the journal. - * - * @param TransactionJournal $journal - * - - */ - private function getDestinationInformation(TransactionJournal $journal): void - { - $this->destinationTransaction = $this->getDestinationTransaction($journal); - $this->destinationAccount = $this->destinationTransaction?->account; - $this->destinationCurrency = null === $this->destinationAccount ? null : $this->getCurrency($this->destinationAccount); - } - - /** - * @param TransactionJournal $transfer - * - * @return Transaction|null - */ - private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction - { - return $transfer->transactions()->where('amount', '>', 0)->first(); - } - - /** - * Is either the source or destination transaction NULL? - * - * @return bool - */ - private function isEmptyTransactions(): bool - { - return null === $this->sourceTransaction || null === $this->destinationTransaction - || null === $this->sourceAccount - || null === $this->destinationAccount; - } - - /** - * @return bool - */ - private function isNoCurrencyPresent(): bool - { - // source account must have a currency preference. - if (null === $this->sourceCurrency) { - $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $this->sourceAccount->id, $this->sourceAccount->name); - Log::error($message); - $this->error($message); - - return true; - } - - // destination account must have a currency preference. - if (null === $this->destinationCurrency) { - $message = sprintf( - 'Account #%d ("%s") must have currency preference but has none.', - $this->destinationAccount->id, - $this->destinationAccount->name - ); - Log::error($message); - $this->error($message); - - return true; - } - - return false; - } - - /** - * The source transaction must have a currency. If not, it will be added by - * taking it from the source account's preference. - */ - private function fixSourceNoCurrency(): void - { - if (null === $this->sourceTransaction->transaction_currency_id && null !== $this->sourceCurrency) { - $this->sourceTransaction - ->transaction_currency_id - = (int)$this->sourceCurrency->id; - $message = sprintf( - 'Transaction #%d has no currency setting, now set to %s.', - $this->sourceTransaction->id, - $this->sourceCurrency->code - ); - Log::debug($message); - $this->line($message); - $this->count++; - $this->sourceTransaction->save(); - } - } - - /** - * The source transaction must have the correct currency. If not, it will be set by - * taking it from the source account's preference. - */ - private function fixSourceUnmatchedCurrency(): void - { - if (null !== $this->sourceCurrency - && null === $this->sourceTransaction->foreign_amount - && (int)$this->sourceTransaction->transaction_currency_id !== (int)$this->sourceCurrency->id - ) { - $message = sprintf( - 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', - $this->sourceTransaction->id, - $this->sourceTransaction->transaction_currency_id, - $this->sourceAccount->id, - $this->sourceTransaction->amount - ); - Log::debug($message); - $this->line($message); - $this->count++; - $this->sourceTransaction->transaction_currency_id = (int)$this->sourceCurrency->id; - $this->sourceTransaction->save(); - } - } - - /** - * The destination transaction must have a currency. If not, it will be added by - * taking it from the destination account's preference. - */ - private function fixDestNoCurrency(): void - { - if (null === $this->destinationTransaction->transaction_currency_id && null !== $this->destinationCurrency) { - $this->destinationTransaction - ->transaction_currency_id - = (int)$this->destinationCurrency->id; - $message = sprintf( - 'Transaction #%d has no currency setting, now set to %s.', - $this->destinationTransaction->id, - $this->destinationCurrency->code - ); - Log::debug($message); - $this->line($message); - $this->count++; - $this->destinationTransaction->save(); - } - } - - /** - * The destination transaction must have the correct currency. If not, it will be set by - * taking it from the destination account's preference. - */ - private function fixDestinationUnmatchedCurrency(): void - { - if (null !== $this->destinationCurrency - && null === $this->destinationTransaction->foreign_amount - && (int)$this->destinationTransaction->transaction_currency_id !== (int)$this->destinationCurrency->id - ) { - $message = sprintf( - 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', - $this->destinationTransaction->id, - $this->destinationTransaction->transaction_currency_id, - $this->destinationAccount->id, - $this->destinationTransaction->amount - ); - Log::debug($message); - $this->line($message); - $this->count++; - $this->destinationTransaction->transaction_currency_id = (int)$this->destinationCurrency->id; - $this->destinationTransaction->save(); - } - } - - /** - * If the destination account currency is the same as the source currency, - * both foreign_amount and foreign_currency_id fields must be NULL - * for both transactions (because foreign currency info would not make sense) - */ - private function fixInvalidForeignCurrency(): void - { - if ((int)$this->destinationCurrency->id === (int)$this->sourceCurrency->id) { - // update both transactions to match: - $this->sourceTransaction->foreign_amount = null; - $this->sourceTransaction->foreign_currency_id = null; - - $this->destinationTransaction->foreign_amount = null; - $this->destinationTransaction->foreign_currency_id = null; - - $this->sourceTransaction->save(); - $this->destinationTransaction->save(); - - Log::debug( - sprintf( - 'Currency for account "%s" is %s, and currency for account "%s" is also - %s, so transactions #%d and #%d has been verified to be to %s exclusively.', - $this->destinationAccount->name, - $this->destinationCurrency->code, - $this->sourceAccount->name, - $this->sourceCurrency->code, - $this->sourceTransaction->id, - $this->destinationTransaction->id, - $this->sourceCurrency->code - ) - ); - } - } - - /** - * If destination account currency is different from source account currency, - * then both transactions must get the source account's currency as normal currency - * and the opposing account's currency as foreign currency. - */ - private function fixMismatchedForeignCurrency(): void - { - if ((int)$this->sourceCurrency->id !== (int)$this->destinationCurrency->id) { - $this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id; - $this->sourceTransaction->foreign_currency_id = $this->destinationCurrency->id; - $this->destinationTransaction->transaction_currency_id = $this->sourceCurrency->id; - $this->destinationTransaction->foreign_currency_id = $this->destinationCurrency->id; - - $this->sourceTransaction->save(); - $this->destinationTransaction->save(); - $this->count++; - Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $this->sourceTransaction->id, $this->destinationTransaction->id)); - } - } - - /** - * If the foreign amount of the source transaction is null, but that of the other isn't, use this piece of code - * to restore it. - */ - private function fixSourceNullForeignAmount(): void - { - if (null === $this->sourceTransaction->foreign_amount && null !== $this->destinationTransaction->foreign_amount) { - $this->sourceTransaction->foreign_amount = bcmul((string)$this->destinationTransaction->foreign_amount, '-1'); - $this->sourceTransaction->save(); - $this->count++; - Log::debug( - sprintf( - 'Restored foreign amount of source transaction #%d to %s', - $this->sourceTransaction->id, - $this->sourceTransaction->foreign_amount - ) - ); - } - } - - /** - * If the foreign amount of the destination transaction is null, but that of the other isn't, use this piece of code - * to restore it. - */ - private function fixDestNullForeignAmount(): void - { - if (null === $this->destinationTransaction->foreign_amount && null !== $this->sourceTransaction->foreign_amount) { - $this->destinationTransaction->foreign_amount = bcmul((string)$this->sourceTransaction->foreign_amount, '-1'); - $this->destinationTransaction->save(); - $this->count++; - Log::debug( - sprintf( - 'Restored foreign amount of destination transaction #%d to %s', - $this->destinationTransaction->id, - $this->destinationTransaction->foreign_amount - ) - ); - } - } - - /** - * This method makes sure that the transaction journal uses the currency given in the source transaction. - * - * @param TransactionJournal $journal - */ - private function fixTransactionJournalCurrency(TransactionJournal $journal): void - { - if ((int)$journal->transaction_currency_id !== (int)$this->sourceCurrency->id) { - $oldCurrencyCode = $journal->transactionCurrency->code ?? '(nothing)'; - $journal->transaction_currency_id = $this->sourceCurrency->id; - $message = sprintf( - 'Transfer #%d ("%s") has been updated to use %s instead of %s.', - $journal->id, - $journal->description, - $this->sourceCurrency->code, - $oldCurrencyCode - ); - $this->count++; - $this->line($message); - Log::debug($message); - $journal->save(); - } - } - - /** - * - */ - private function markAsExecuted(): void - { - app('fireflyconfig')->set(self::CONFIG_NAME, true); - } } diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index 09ea26f05e..17ca07a0ed 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -35,18 +35,8 @@ use Illuminate\Console\Command; */ class UpgradeDatabase extends Command { - /** - * The console command description. - * - * @var string - */ protected $description = 'Upgrades the database to the latest version.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:upgrade-database {--F|force : Force all upgrades.}'; + protected $signature = 'firefly-iii:upgrade-database {--F|force : Force all upgrades.}'; /** * Execute the console command. @@ -97,15 +87,8 @@ class UpgradeDatabase extends Command */ private function callInitialCommands(): void { - $this->line('Now seeding the database...'); $this->call('migrate', ['--seed' => true, '--force' => true, '--no-interaction' => true]); - - $this->line('Fix PostgreSQL sequences.'); $this->call('firefly-iii:fix-pgsql-sequences'); - - $this->line('Now decrypting the database (if necessary)...'); $this->call('firefly-iii:decrypt-all'); - - $this->line('Done!'); } } diff --git a/app/Console/Commands/Upgrade/UpgradeLiabilities.php b/app/Console/Commands/Upgrade/UpgradeLiabilities.php index 7e93c7f44d..1d6b8f6134 100644 --- a/app/Console/Commands/Upgrade/UpgradeLiabilities.php +++ b/app/Console/Commands/Upgrade/UpgradeLiabilities.php @@ -43,18 +43,8 @@ use Psr\Container\NotFoundExceptionInterface; class UpgradeLiabilities extends Command { public const CONFIG_NAME = '560_upgrade_liabilities'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Upgrade liabilities to new 5.6.0 structure.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:upgrade-liabilities {--F|force : Force the execution of this command.}'; + protected $signature = 'firefly-iii:upgrade-liabilities {--F|force : Force the execution of this command.}'; /** * Execute the console command. @@ -66,7 +56,6 @@ class UpgradeLiabilities extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -75,10 +64,6 @@ class UpgradeLiabilities extends Command $this->upgradeLiabilities(); $this->markAsExecuted(); - - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Upgraded liabilities in %s seconds.', $end)); - return 0; } @@ -95,15 +80,12 @@ class UpgradeLiabilities extends Command } // source MUST be the liability. if ((int)$destination->account_id === (int)$account->id) { - Log::debug(sprintf('Must switch around, because account #%d is the destination.', $destination->account_id)); // so if not, switch things around: $sourceAccountId = (int)$source->account_id; $source->account_id = $destination->account_id; $destination->account_id = $sourceAccountId; $source->save(); $destination->save(); - Log::debug(sprintf('Source transaction #%d now has account #%d', $source->id, $source->account_id)); - Log::debug(sprintf('Dest transaction #%d now has account #%d', $destination->id, $destination->account_id)); } } @@ -155,7 +137,6 @@ class UpgradeLiabilities extends Command */ private function upgradeForUser(User $user): void { - Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id)); $accounts = $user->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('account_types.type', config('firefly.valid_liabilities')) @@ -174,7 +155,6 @@ class UpgradeLiabilities extends Command */ private function upgradeLiabilities(): void { - Log::debug('Upgrading liabilities.'); $users = User::get(); /** @var User $user */ foreach ($users as $user) { @@ -190,7 +170,6 @@ class UpgradeLiabilities extends Command /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); - Log::debug(sprintf('Upgrade liability #%d', $account->id)); // get opening balance, and correct if necessary. $openingBalance = $repository->getOpeningBalance($account); diff --git a/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php b/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php index 7acb7df97b..f1d1037458 100644 --- a/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php +++ b/app/Console/Commands/Upgrade/UpgradeLiabilitiesEight.php @@ -45,18 +45,8 @@ use Psr\Container\NotFoundExceptionInterface; class UpgradeLiabilitiesEight extends Command { public const CONFIG_NAME = '600_upgrade_liabilities'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Upgrade liabilities to new 6.0.0 structure.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:liabilities-600 {--F|force : Force the execution of this command.}'; + protected $signature = 'firefly-iii:liabilities-600 {--F|force : Force the execution of this command.}'; /** * Execute the console command. @@ -68,7 +58,6 @@ class UpgradeLiabilitiesEight extends Command */ public function handle(): int { - $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -77,9 +66,6 @@ class UpgradeLiabilitiesEight extends Command $this->upgradeLiabilities(); $this->markAsExecuted(); - $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Upgraded liabilities for 6.0.0 in %s seconds.', $end)); - return 0; } @@ -90,7 +76,6 @@ class UpgradeLiabilitiesEight extends Command */ private function deleteCreditTransaction(Account $account): void { - Log::debug('Will delete credit transaction.'); $liabilityType = TransactionType::whereType(TransactionType::LIABILITY_CREDIT)->first(); $liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id) @@ -100,11 +85,9 @@ class UpgradeLiabilitiesEight extends Command $group = $liabilityJournal->transactionGroup; $service = new TransactionGroupDestroyService(); $service->destroy($group); - Log::debug(sprintf('Deleted liability credit group #%d', $group->id)); return; } - Log::debug('No liability credit journal found.'); } /** @@ -117,7 +100,6 @@ class UpgradeLiabilitiesEight extends Command $count = 0; $journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->where('transactions.account_id', $account->id)->get(['transaction_journals.*']); - Log::debug(sprintf('Found %d journals to analyse.', $journals->count())); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { $delete = false; @@ -140,16 +122,6 @@ class UpgradeLiabilitiesEight extends Command $delete = false; if ($delete) { - Log::debug( - sprintf( - 'Deleted %s journal #%d ("%s") (%s %s).', - $journal->transactionType->type, - $journal->id, - $journal->description, - $journal->transactionCurrency->code, - $dest->amount - ) - ); $service = app(TransactionGroupDestroyService::class); $service->destroy($journal->transactionGroup); $count++; @@ -173,8 +145,6 @@ class UpgradeLiabilitiesEight extends Command ->where('transaction_journals.transaction_type_id', $openingBalanceType->id) ->first(['transaction_journals.*']); if (null === $openingJournal) { - Log::debug('Account has no opening balance and can be skipped.'); - return false; } $liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') @@ -182,16 +152,11 @@ class UpgradeLiabilitiesEight extends Command ->where('transaction_journals.transaction_type_id', $liabilityType->id) ->first(['transaction_journals.*']); if (null === $liabilityJournal) { - Log::debug('Account has no liability credit and can be skipped.'); - return false; } if (!$openingJournal->date->isSameDay($liabilityJournal->date)) { - Log::debug('Account has opening/credit not on the same day.'); - return false; } - Log::debug('Account has bad opening balance data.'); return true; } @@ -243,7 +208,6 @@ class UpgradeLiabilitiesEight extends Command $source->account_id = $destId; $source->save(); $dest->save(); - Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id)); return; } @@ -255,7 +219,6 @@ class UpgradeLiabilitiesEight extends Command */ private function upgradeForUser(User $user): void { - Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id)); $accounts = $user->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('account_types.type', config('firefly.valid_liabilities')) @@ -274,7 +237,6 @@ class UpgradeLiabilitiesEight extends Command */ private function upgradeLiabilities(): void { - Log::debug('Upgrading liabilities.'); $users = User::get(); /** @var User $user */ foreach ($users as $user) { @@ -287,16 +249,11 @@ class UpgradeLiabilitiesEight extends Command */ private function upgradeLiability(Account $account): void { - Log::debug(sprintf('Upgrade liability #%d ("%s")', $account->id, $account->name)); - /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); $direction = $repository->getMetaValue($account, 'liability_direction'); - if ('debit' === $direction) { - Log::debug('Direction is debit ("I owe this amount"), so no need to upgrade.'); - } if ('credit' === $direction && $this->hasBadOpening($account)) { $this->deleteCreditTransaction($account); $this->reverseOpeningBalance($account); @@ -308,6 +265,5 @@ class UpgradeLiabilitiesEight extends Command $this->line(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name)); } } - Log::debug(sprintf('Done upgrading liability #%d ("%s")', $account->id, $account->name)); } } diff --git a/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub index 90c0009222..c11ba2c00e 100644 --- a/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub +++ b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub @@ -33,7 +33,7 @@ class UpgradeSkeleton extends Command { $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { - $this->warn('This command has already been executed.'); + $this->warn('Correct: this command has already been executed.'); return 0; } diff --git a/app/Factory/AccountMetaFactory.php b/app/Factory/AccountMetaFactory.php index 3977153c89..a5d4031098 100644 --- a/app/Factory/AccountMetaFactory.php +++ b/app/Factory/AccountMetaFactory.php @@ -60,15 +60,12 @@ class AccountMetaFactory 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)); - return $this->create(['account_id' => $account->id, 'name' => $field, 'data' => $value]); } // if $data has field and $entry is not null, update $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) { $entry->delete(); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 995bc1fe1a..d52b983021 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -102,7 +102,6 @@ class DebugController extends Controller Log::debug('Call view:clear...'); Artisan::call('view:clear'); - Log::debug('Done! Redirecting...'); return redirect(route('index')); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 6853c9194c..92ad6f9aa5 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -667,7 +667,6 @@ class AccountRepository implements AccountRepositoryInterface //[AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION], ]; foreach ($sets as $set) { - Log::debug('Now in resetAccountOrder', $set); $list = $this->getAccountsByType($set); $index = 1; foreach ($list as $account) { diff --git a/app/Services/Internal/Support/CreditRecalculateService.php b/app/Services/Internal/Support/CreditRecalculateService.php index a22b3593c9..53a5360b8e 100644 --- a/app/Services/Internal/Support/CreditRecalculateService.php +++ b/app/Services/Internal/Support/CreditRecalculateService.php @@ -57,25 +57,18 @@ class CreditRecalculateService public function recalculate(): void { if (true !== config('firefly.feature_flags.handle_debts')) { - Log::debug('handle_debts is disabled.'); - return; } if (null !== $this->group && null === $this->account) { - Log::debug('Have to handle a group.'); $this->processGroup(); } if (null !== $this->account && null === $this->group) { - Log::debug('Have to handle an account.'); // work based on account. $this->processAccount(); } if (0 === count($this->work)) { - Log::debug('No work accounts, do not do CreditRecalculationService'); - return; } - Log::debug('Will now do CreditRecalculationService'); $this->processWork(); } @@ -108,11 +101,9 @@ class CreditRecalculateService // destination or source must be liability. $valid = config('firefly.valid_liabilities'); if (in_array($destination->accountType->type, $valid, true)) { - Log::debug(sprintf('Dest account type is "%s", include it.', $destination->accountType->type)); $this->work[] = $destination; } if (in_array($source->accountType->type, $valid, true)) { - Log::debug(sprintf('Src account type is "%s", include it.', $source->accountType->type)); $this->work[] = $source; } } @@ -168,7 +159,6 @@ class CreditRecalculateService { $valid = config('firefly.valid_liabilities'); if (in_array($this->account->accountType->type, $valid, true)) { - Log::debug(sprintf('Account type is "%s", include it.', $this->account->accountType->type)); $this->work[] = $this->account; } } @@ -187,7 +177,6 @@ class CreditRecalculateService Log::error(sprintf('Could not find work account for transaction group #%d.', $this->group->id)); } } - Log::debug(sprintf('Done with %s', __METHOD__)); } /** @@ -200,7 +189,6 @@ class CreditRecalculateService */ private function processTransaction(Account $account, string $direction, Transaction $transaction, string $leftOfDebt): string { - Log::debug(sprintf('Now in processTransaction(#%d, %s)', $transaction->id, $leftOfDebt)); $journal = $transaction->transactionJournal; $foreignCurrency = $transaction->foreignCurrency; $accountCurrency = $this->repository->getAccountCurrency($account); @@ -212,12 +200,9 @@ class CreditRecalculateService $sourceTransaction = $journal->transactions()->where('amount', '<', '0')->first(); if ('' === $direction) { - Log::debug('Since direction is "", do nothing.'); - return $leftOfDebt; } if (TransactionType::LIABILITY_CREDIT === $type || TransactionType::OPENING_BALANCE === $type) { - Log::debug(sprintf('Skip group #%d, journal #%d of type "%s"', $journal->id, $groupId, $type)); return $leftOfDebt; } @@ -225,11 +210,8 @@ class CreditRecalculateService $usedAmount = $transaction->amount; if (null !== $foreignCurrency && $foreignCurrency->id === $accountCurrency->id) { $usedAmount = $transaction->foreign_amount; - Log::debug('Will use foreign amount to match account currency.'); } - Log::debug(sprintf('Processing group #%d, journal #%d of type "%s"', $journal->id, $groupId, $type)); - // Case 1 // it's a withdrawal into this liability (from asset). // if it's a credit ("I am owed"), this increases the amount due, @@ -240,9 +222,7 @@ class CreditRecalculateService && 1 === bccomp($usedAmount, '0') && 'credit' === $direction ) { - $newLeftOfDebt = bcadd($leftOfDebt, app('steam')->positive($usedAmount)); - Log::debug(sprintf('[1] Is withdrawal (%s) into liability, left of debt = %s.', $usedAmount, $newLeftOfDebt)); - return $newLeftOfDebt; + return bcadd($leftOfDebt, app('steam')->positive($usedAmount)); } // Case 2 @@ -255,15 +235,7 @@ class CreditRecalculateService && -1 === bccomp($usedAmount, '0') && 'credit' === $direction ) { - $newLeftOfDebt = bcsub($leftOfDebt, app('steam')->positive($usedAmount)); - Log::debug( - sprintf( - '[2] Is withdrawal (%s) away from liability, left of debt = %s.', - $usedAmount, - $newLeftOfDebt - ) - ); - return $newLeftOfDebt; + return bcsub($leftOfDebt, app('steam')->positive($usedAmount)); } // case 3 @@ -276,9 +248,7 @@ class CreditRecalculateService && -1 === bccomp($usedAmount, '0') && 'credit' === $direction ) { - $newLeftOfDebt = bcsub($leftOfDebt, app('steam')->positive($usedAmount)); - Log::debug(sprintf('[3] Is deposit (%s) away from liability, left of debt = %s.', $usedAmount, $newLeftOfDebt)); - return $newLeftOfDebt; + return bcsub($leftOfDebt, app('steam')->positive($usedAmount)); } // case 4 @@ -292,16 +262,12 @@ class CreditRecalculateService && 'credit' === $direction ) { $newLeftOfDebt = bcadd($leftOfDebt, app('steam')->positive($usedAmount)); - Log::debug(sprintf('[4] Is deposit (%s) into liability, left of debt = %s.', $usedAmount, $newLeftOfDebt)); return $newLeftOfDebt; } // in any other case, remove amount from left of debt. if (in_array($type, [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER], true)) { $newLeftOfDebt = bcadd($leftOfDebt, bcmul($usedAmount, '-1')); - Log::debug( - sprintf('[5] Fallback: transaction is withdrawal/deposit/transfer, remove amount %s from left of debt, = %s.', $usedAmount, $newLeftOfDebt) - ); return $newLeftOfDebt; } @@ -319,7 +285,6 @@ class CreditRecalculateService foreach ($this->work as $account) { $this->processWorkAccount($account); } - Log::debug(sprintf('Done with %s', __METHOD__)); } /** @@ -327,8 +292,6 @@ class CreditRecalculateService */ private function processWorkAccount(Account $account): void { - Log::debug(sprintf('Now in %s(#%d)', __METHOD__, $account->id)); - // get opening balance (if present) $this->repository->setUser($account->user); $startOfDebt = $this->repository->getOpeningBalanceAmount($account) ?? '0'; @@ -345,14 +308,10 @@ class CreditRecalculateService // now loop all transactions (except opening balance and credit thing) $transactions = $account->transactions()->get(); - Log::debug(sprintf('Going to process %d transaction(s)', $transactions->count())); - Log::debug(sprintf('Account currency is #%d (%s)', $account->id, $this->repository->getAccountCurrency($account)?->code)); /** @var Transaction $transaction */ foreach ($transactions as $transaction) { $leftOfDebt = $this->processTransaction($account, $direction, $transaction, $leftOfDebt); } $factory->crud($account, 'current_debt', $leftOfDebt); - - Log::debug(sprintf('Done with %s(#%d)', __METHOD__, $account->id)); } } diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index 5cafba52cc..cedbbfcf93 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -265,7 +265,6 @@ trait JournalServiceTrait Log::debug('End of loop.'); Log::debug(sprintf('Total nr. of tags: %d', count($tags)), $tags); $journal->tags()->sync($set); - Log::debug('Done!'); } /** diff --git a/composer.lock b/composer.lock index ca3c37ebd1..8d56175ffd 100644 --- a/composer.lock +++ b/composer.lock @@ -584,16 +584,16 @@ }, { "name": "doctrine/deprecations", - "version": "v1.0.0", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + "reference": "8cffffb2218e01f3b370bf763e00e81697725259" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/8cffffb2218e01f3b370bf763e00e81697725259", + "reference": "8cffffb2218e01f3b370bf763e00e81697725259", "shasum": "" }, "require": { @@ -621,9 +621,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + "source": "https://github.com/doctrine/deprecations/tree/v1.1.0" }, - "time": "2022-05-02T15:47:09+00:00" + "time": "2023-05-29T18:55:17+00:00" }, { "name": "doctrine/event-manager", @@ -1937,16 +1937,16 @@ }, { "name": "laravel/framework", - "version": "v10.12.0", + "version": "v10.13.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "9e6dcff23ab1d4b522bef56074c31625cf077576" + "reference": "7322723585103082758d74917db62980684845cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/9e6dcff23ab1d4b522bef56074c31625cf077576", - "reference": "9e6dcff23ab1d4b522bef56074c31625cf077576", + "url": "https://api.github.com/repos/laravel/framework/zipball/7322723585103082758d74917db62980684845cb", + "reference": "7322723585103082758d74917db62980684845cb", "shasum": "" }, "require": { @@ -2133,7 +2133,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-05-23T18:04:16+00:00" + "time": "2023-05-30T14:46:25+00:00" }, { "name": "laravel/passport", @@ -5868,23 +5868,23 @@ }, { "name": "symfony/console", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77" + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77", - "reference": "5aa03db8ef0a5457c316ec580e69562d97734c77", + "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/string": "^5.4|^6.0" }, "conflict": { @@ -5906,12 +5906,6 @@ "symfony/process": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, "type": "library", "autoload": { "psr-4": { @@ -5944,7 +5938,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.11" + "source": "https://github.com/symfony/console/tree/v6.3.0" }, "funding": [ { @@ -5960,20 +5954,20 @@ "type": "tidelift" } ], - "time": "2023-05-26T08:16:21+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/css-selector", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0" + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/aedf3cb0f5b929ec255d96bbb4909e9932c769e0", - "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", "shasum": "" }, "require": { @@ -6009,7 +6003,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.7" + "source": "https://github.com/symfony/css-selector/tree/v6.3.0" }, "funding": [ { @@ -6025,20 +6019,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2023-03-20T16:43:42+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { @@ -6047,7 +6041,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -6076,7 +6070,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" }, "funding": [ { @@ -6092,20 +6086,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:25:55+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/error-handler", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "e847ba47e7a8f9708082990cb40ab4ff0440a11e" + "reference": "99d2d814a6351461af350ead4d963bd67451236f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/e847ba47e7a8f9708082990cb40ab4ff0440a11e", - "reference": "e847ba47e7a8f9708082990cb40ab4ff0440a11e", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99d2d814a6351461af350ead4d963bd67451236f", + "reference": "99d2d814a6351461af350ead4d963bd67451236f", "shasum": "" }, "require": { @@ -6113,8 +6107,11 @@ "psr/log": "^1|^2|^3", "symfony/var-dumper": "^5.4|^6.0" }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0" }, @@ -6147,7 +6144,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.11" + "source": "https://github.com/symfony/error-handler/tree/v6.3.0" }, "funding": [ { @@ -6163,28 +6160,29 @@ "type": "tidelift" } ], - "time": "2023-05-05T11:55:01+00:00" + "time": "2023-05-10T12:03:13+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -6197,13 +6195,9 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, "type": "library", "autoload": { "psr-4": { @@ -6230,7 +6224,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" }, "funding": [ { @@ -6246,33 +6240,30 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -6309,7 +6300,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" }, "funding": [ { @@ -6325,20 +6316,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/finder", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", + "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", "shasum": "" }, "require": { @@ -6373,7 +6364,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.7" + "source": "https://github.com/symfony/finder/tree/v6.3.0" }, "funding": [ { @@ -6389,28 +6380,32 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:57:23+00:00" + "time": "2023-04-02T01:25:41+00:00" }, { "name": "symfony/http-client", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "39f679c12648cc43bd9f0db12cc69b82041b91a1" + "reference": "b2f892c91e4e02a939edddeb7ef452522d10a424" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/39f679c12648cc43bd9f0db12cc69b82041b91a1", - "reference": "39f679c12648cc43bd9f0db12cc69b82041b91a1", + "url": "https://api.github.com/repos/symfony/http-client/zipball/b2f892c91e4e02a939edddeb7ef452522d10a424", + "reference": "b2f892c91e4e02a939edddeb7ef452522d10a424", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" }, "provide": { "php-http/async-client-implementation": "*", @@ -6426,7 +6421,6 @@ "guzzlehttp/promises": "^1.4", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", - "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", @@ -6462,7 +6456,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.2.11" + "source": "https://github.com/symfony/http-client/tree/v6.3.0" }, "funding": [ { @@ -6478,32 +6472,29 @@ "type": "tidelift" } ], - "time": "2023-05-12T08:48:34+00:00" + "time": "2023-05-12T08:49:48+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b" + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", - "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/http-client-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -6543,7 +6534,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" }, "funding": [ { @@ -6559,32 +6550,34 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "df27f4191a4292d01fd062296e09cbc8b657cb57" + "reference": "718a97ed430d34e5c568ea2c44eab708c6efbefb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/df27f4191a4292d01fd062296e09cbc8b657cb57", - "reference": "df27f4191a4292d01fd062296e09cbc8b657cb57", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/718a97ed430d34e5c568ea2c44eab708c6efbefb", + "reference": "718a97ed430d34e5c568ea2c44eab708c6efbefb", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "conflict": { "symfony/cache": "<6.2" }, "require-dev": { - "predis/predis": "~1.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", "symfony/cache": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", @@ -6592,9 +6585,6 @@ "symfony/mime": "^5.4|^6.0", "symfony/rate-limiter": "^5.2|^6.0" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" - }, "type": "library", "autoload": { "psr-4": { @@ -6621,7 +6611,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.2.11" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.0" }, "funding": [ { @@ -6637,29 +6627,29 @@ "type": "tidelift" } ], - "time": "2023-05-19T12:39:53+00:00" + "time": "2023-05-19T12:46:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "954a1a3b178309b216261eedc735c079709e4ab3" + "reference": "241973f3dd900620b1ca052fe409144f11aea748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/954a1a3b178309b216261eedc735c079709e4ab3", - "reference": "954a1a3b178309b216261eedc735c079709e4ab3", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/241973f3dd900620b1ca052fe409144f11aea748", + "reference": "241973f3dd900620b1ca052fe409144f11aea748", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^6.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^5.4.21|^6.2.7", + "symfony/http-foundation": "^6.2.7", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -6667,15 +6657,18 @@ "symfony/cache": "<5.4", "symfony/config": "<6.1", "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.2", + "symfony/dependency-injection": "<6.3", "symfony/doctrine-bridge": "<5.4", "symfony/form": "<5.4", "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/mailer": "<5.4", "symfony/messenger": "<5.4", "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4", "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { @@ -6684,29 +6677,27 @@ "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", "symfony/config": "^6.1", "symfony/console": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.2", + "symfony/dependency-injection": "^6.3", "symfony/dom-crawler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", "symfony/var-exporter": "^6.2", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, "type": "library", "autoload": { "psr-4": { @@ -6733,7 +6724,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.2.11" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.0" }, "funding": [ { @@ -6749,20 +6740,20 @@ "type": "tidelift" } ], - "time": "2023-05-27T21:12:52+00:00" + "time": "2023-05-30T19:03:32+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17" + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", - "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", "shasum": "" }, "require": { @@ -6772,9 +6763,10 @@ "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/mime": "^6.2", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", "symfony/messenger": "<6.2", "symfony/mime": "<6.2", @@ -6812,7 +6804,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.8" + "source": "https://github.com/symfony/mailer/tree/v6.3.0" }, "funding": [ { @@ -6828,28 +6820,32 @@ "type": "tidelift" } ], - "time": "2023-03-14T15:00:05+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.2.10", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8" + "reference": "2fafefe8683a93155aceb6cca622c7cee2e27174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/2c9d47b11cc154d2db3f571030cd965d128de1a8", - "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/2fafefe8683a93155aceb6cca622c7cee2e27174", + "reference": "2fafefe8683a93155aceb6cca622c7cee2e27174", "shasum": "" }, "require": { "php": ">=8.1", "symfony/mailer": "^5.4.21|^6.2.7" }, + "conflict": { + "symfony/http-foundation": "<6.2" + }, "require-dev": { - "symfony/http-client": "^5.4|^6.0" + "symfony/http-client": "^5.4|^6.0", + "symfony/webhook": "^6.3" }, "type": "symfony-mailer-bridge", "autoload": { @@ -6877,7 +6873,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.10" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.0" }, "funding": [ { @@ -6893,20 +6889,20 @@ "type": "tidelift" } ], - "time": "2023-04-14T16:23:31+00:00" + "time": "2023-05-02T16:15:19+00:00" }, { "name": "symfony/mime", - "version": "v6.2.10", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723" + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b6c137fc53a9f7c4c951cd3f362b3734c7a97723", - "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723", + "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", "shasum": "" }, "require": { @@ -6960,7 +6956,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.10" + "source": "https://github.com/symfony/mime/tree/v6.3.0" }, "funding": [ { @@ -6976,7 +6972,7 @@ "type": "tidelift" } ], - "time": "2023-04-19T09:54:16+00:00" + "time": "2023-04-28T15:57:00+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7554,6 +7550,83 @@ ], "time": "2022-11-03T14:55:06+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "symfony/polyfill-uuid", "version": "v1.27.0", @@ -7638,16 +7711,16 @@ }, { "name": "symfony/process", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c" + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/97ae9721bead9d1a39b5650e2f4b7834b93b539c", - "reference": "97ae9721bead9d1a39b5650e2f4b7834b93b539c", + "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", "shasum": "" }, "require": { @@ -7679,7 +7752,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.11" + "source": "https://github.com/symfony/process/tree/v6.3.0" }, "funding": [ { @@ -7695,7 +7768,7 @@ "type": "tidelift" } ], - "time": "2023-05-19T07:42:48+00:00" + "time": "2023-05-19T08:06:44+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -7787,16 +7860,16 @@ }, { "name": "symfony/routing", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "69062e2823f03b82265d73a966999660f0e1e404" + "reference": "827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/69062e2823f03b82265d73a966999660f0e1e404", - "reference": "69062e2823f03b82265d73a966999660f0e1e404", + "url": "https://api.github.com/repos/symfony/routing/zipball/827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b", + "reference": "827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b", "shasum": "" }, "require": { @@ -7817,12 +7890,6 @@ "symfony/http-foundation": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0" }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, "type": "library", "autoload": { "psr-4": { @@ -7855,7 +7922,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.8" + "source": "https://github.com/symfony/routing/tree/v6.3.0" }, "funding": [ { @@ -7871,20 +7938,20 @@ "type": "tidelift" } ], - "time": "2023-03-14T15:00:05+00:00" + "time": "2023-04-28T15:57:00+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a" + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "shasum": "" }, "require": { @@ -7894,13 +7961,10 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -7940,7 +8004,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" }, "funding": [ { @@ -7956,20 +8020,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/string", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", + "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", "shasum": "" }, "require": { @@ -7980,13 +8044,13 @@ "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", "symfony/intl": "^6.2", - "symfony/translation-contracts": "^2.0|^3.0", + "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", @@ -8026,7 +8090,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.8" + "source": "https://github.com/symfony/string/tree/v6.3.0" }, "funding": [ { @@ -8042,32 +8106,34 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2023-03-21T21:06:29+00:00" }, { "name": "symfony/translation", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "64113df3e8b009f92fad63014f4ec647e65bc927" + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/64113df3e8b009f92fad63014f4ec647e65bc927", - "reference": "64113df3e8b009f92fad63014f4ec647e65bc927", + "url": "https://api.github.com/repos/symfony/translation/zipball/f72b2cba8f79dd9d536f534f76874b58ad37876f", + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f", "shasum": "" }, "require": { "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.3|^3.0" + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { "symfony/config": "<5.4", "symfony/console": "<5.4", "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", "symfony/twig-bundle": "<5.4", "symfony/yaml": "<5.4" }, @@ -8081,20 +8147,14 @@ "symfony/console": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-client-contracts": "^2.5|^3.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", "symfony/routing": "^5.4|^6.0", - "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/yaml": "^5.4|^6.0" }, - "suggest": { - "nikic/php-parser": "To use PhpAstExtractor", - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, "type": "library", "autoload": { "files": [ @@ -8124,7 +8184,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.11" + "source": "https://github.com/symfony/translation/tree/v6.3.0" }, "funding": [ { @@ -8140,32 +8200,29 @@ "type": "tidelift" } ], - "time": "2023-05-19T12:37:14+00:00" + "time": "2023-05-19T12:46:45+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8" + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dfec258b9dd17a6b24420d464c43bffe347441c8", - "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/translation-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -8205,7 +8262,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" }, "funding": [ { @@ -8221,20 +8278,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-30T17:17:10+00:00" }, { "name": "symfony/uid", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0" + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", - "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", + "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", "shasum": "" }, "require": { @@ -8279,7 +8336,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.2.7" + "source": "https://github.com/symfony/uid/tree/v6.3.0" }, "funding": [ { @@ -8295,20 +8352,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2023-04-08T07:25:02+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.2.11", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "7d10f2a5a452bda385692fc7d38cd6eccfebe756" + "reference": "6acdcd5c122074ee9f7b051e4fb177025c277a0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7d10f2a5a452bda385692fc7d38cd6eccfebe756", - "reference": "7d10f2a5a452bda385692fc7d38cd6eccfebe756", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6acdcd5c122074ee9f7b051e4fb177025c277a0e", + "reference": "6acdcd5c122074ee9f7b051e4fb177025c277a0e", "shasum": "" }, "require": { @@ -8325,11 +8382,6 @@ "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "bin": [ "Resources/bin/var-dump-server" ], @@ -8366,7 +8418,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.11" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.0" }, "funding": [ { @@ -8382,7 +8434,7 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:08:43+00:00" + "time": "2023-05-25T13:09:35+00:00" }, { "name": "therobfonz/laravel-mandrill-driver", @@ -9717,16 +9769,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.1", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "dfc078e8af9c99210337325ff5aa152872c98714" + "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714", - "reference": "dfc078e8af9c99210337325ff5aa152872c98714", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d", + "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d", "shasum": "" }, "require": { @@ -9769,9 +9821,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2" }, - "time": "2023-03-27T19:02:04+00:00" + "time": "2023-05-30T18:13:47+00:00" }, { "name": "phpmyadmin/sql-parser", @@ -9862,22 +9914,23 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.21.0", + "version": "1.22.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c" + "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6df62b08faef4f899772bc7c3bbabb93d2b7a21c", - "reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", + "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { + "doctrine/annotations": "^2.0", "nikic/php-parser": "^4.15", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", @@ -9902,9 +9955,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0" }, - "time": "2023-05-17T13:13:44+00:00" + "time": "2023-06-01T12:35:21+00:00" }, { "name": "phpstan/phpstan", diff --git a/database/seeders/ConfigSeeder.php b/database/seeders/ConfigSeeder.php index 7005d71345..b1ade84b80 100644 --- a/database/seeders/ConfigSeeder.php +++ b/database/seeders/ConfigSeeder.php @@ -39,8 +39,6 @@ class ConfigSeeder extends Seeder { $entry = Configuration::where('name', 'db_version')->first(); if (null === $entry) { - Log::warning('No database version entry is present. Database is assumed to be OLD (version 1).'); - // FF old or no version present. Put at 1: Configuration::create( [ 'name' => 'db_version', @@ -52,8 +50,6 @@ class ConfigSeeder extends Seeder $version = (int)config('firefly.db_version'); $entry->data = $version; $entry->save(); - - Log::warning(sprintf('Database entry exists. Update to latest version (%d)', $version)); } } } From 1357074dcd72b1dc541cd060bfbeede4a3e711de Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 3 Jun 2023 17:16:28 +0200 Subject: [PATCH 08/30] Fix https://github.com/firefly-iii/firefly-iii/issues/7588 --- .../Support/RecurringTransactionTrait.php | 1 + app/Support/Request/GetRecurrenceData.php | 3 + app/Validation/RecurrenceValidation.php | 3 +- resources/views/recurring/show.twig | 71 +++++++++++-------- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index c3518e55b3..7ffb5922f1 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -281,6 +281,7 @@ trait RecurringTransactionTrait } } + /** * @param RecurrenceTransaction $transaction * @param array $tags diff --git a/app/Support/Request/GetRecurrenceData.php b/app/Support/Request/GetRecurrenceData.php index 661823ac60..6fc9a09e46 100644 --- a/app/Support/Request/GetRecurrenceData.php +++ b/app/Support/Request/GetRecurrenceData.php @@ -73,6 +73,9 @@ trait GetRecurrenceData if (array_key_exists('piggy_bank_id', $transaction)) { $return['piggy_bank_id'] = (int)$transaction['piggy_bank_id']; } + if (array_key_exists('bill_id', $transaction)) { + $return['bill_id'] = (int)$transaction['bill_id']; + } if (array_key_exists('tags', $transaction)) { $return['tags'] = $transaction['tags']; diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php index 0d5c8893fc..45696fcd65 100644 --- a/app/Validation/RecurrenceValidation.php +++ b/app/Validation/RecurrenceValidation.php @@ -203,12 +203,13 @@ trait RecurrenceValidation */ foreach ($repetitions as $index => $repetition) { if (!array_key_exists('moment', $repetition)) { - continue; + $repetition['moment'] = ''; } if (null === $repetition['moment']) { $repetition['moment'] = ''; } $repetition['moment'] = $repetition['moment'] ?? 'invalid'; + switch ($repetition['type'] ?? 'empty') { default: $validator->errors()->add(sprintf('repetitions.%d.type', $index), (string)trans('validation.valid_recurrence_rep_type')); diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 355fada105..2d4b181bae 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -59,37 +59,39 @@ {{ trans('firefly.repeat_until_in_past', {date: array.repeat_until.isoFormat(monthAndDayFormat) }) }} {% endif %} - {% for rep in array.repetitions %} -

- {{ rep.description }} - {% if rep.repetition_skip == 1 %} - ({{ trans('firefly.recurring_skips_one')|lower }}) - {% endif %} - {% if rep.repetition_skip > 1 %} - ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) - {% endif %} - -

- - - {% for occ in rep.occurrences %} - - - + + {% endfor %} + +
{{ occ.date.isoFormat(trans('config.month_and_date_day_js')) }} - {% if not occ.fired %} -
+ {% for rep in array.repetitions %} +

+ {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}) + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + {% endif %} + +

+ + + {% for occ in rep.occurrences %} + + + - - {% endfor %} - -
{{ occ.date.isoFormat(trans('config.month_and_date_day_js')) }} + {% if not occ.fired %} + - - + + - {% endif %} -
- {% endfor %} + {% endif %} +
+ {% endfor %}