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, - ] - ); - } }