diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php index c1cbea6229..141412f486 100644 --- a/app/Api/V1/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -77,7 +77,7 @@ class AccountController extends Controller $query = $data['query']; $date = $data['date'] ?? today(config('app.timezone')); $return = []; - $result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit')); + $result = $this->repository->searchAccount((string)$query, $types, $this->parameters->get('limit')); // TODO this code is duplicated in the V2 Autocomplete controller, which means this code is due to be deprecated. $defaultCurrency = app('amount')->getDefaultCurrency(); @@ -97,11 +97,11 @@ class AccountController extends Controller } $return[] = [ - 'id' => (string) $account->id, + 'id' => (string)$account->id, 'name' => $account->name, 'name_with_balance' => $nameWithBalance, 'type' => $account->accountType->type, - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_name' => $currency->name, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, @@ -114,8 +114,8 @@ class AccountController extends Controller $return, static function (array $left, array $right) { $order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE]; - $posA = (int) array_search($left['type'], $order, true); - $posB = (int) array_search($right['type'], $order, true); + $posA = (int)array_search($left['type'], $order, true); + $posB = (int)array_search($right['type'], $order, true); return $posA - $posB; } diff --git a/app/Api/V1/Controllers/Autocomplete/BillController.php b/app/Api/V1/Controllers/Autocomplete/BillController.php index 462761bb65..be64d6fd96 100644 --- a/app/Api/V1/Controllers/Autocomplete/BillController.php +++ b/app/Api/V1/Controllers/Autocomplete/BillController.php @@ -66,7 +66,7 @@ class BillController extends Controller $filtered = $result->map( static function (Bill $item) { return [ - 'id' => (string) $item->id, + 'id' => (string)$item->id, 'name' => $item->name, 'active' => $item->active, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/BudgetController.php b/app/Api/V1/Controllers/Autocomplete/BudgetController.php index 3aa3e96011..0674e2dd2e 100644 --- a/app/Api/V1/Controllers/Autocomplete/BudgetController.php +++ b/app/Api/V1/Controllers/Autocomplete/BudgetController.php @@ -66,7 +66,7 @@ class BudgetController extends Controller $filtered = $result->map( static function (Budget $item) { return [ - 'id' => (string) $item->id, + 'id' => (string)$item->id, 'name' => $item->name, ]; } diff --git a/app/Api/V1/Controllers/Autocomplete/CategoryController.php b/app/Api/V1/Controllers/Autocomplete/CategoryController.php index 1baea11df7..6a1b104aae 100644 --- a/app/Api/V1/Controllers/Autocomplete/CategoryController.php +++ b/app/Api/V1/Controllers/Autocomplete/CategoryController.php @@ -66,7 +66,7 @@ class CategoryController extends Controller $filtered = $result->map( static function (Category $item) { return [ - 'id' => (string) $item->id, + 'id' => (string)$item->id, 'name' => $item->name, ]; } diff --git a/app/Api/V1/Controllers/Autocomplete/CurrencyController.php b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php index 4b601cc02d..abf6b7b468 100644 --- a/app/Api/V1/Controllers/Autocomplete/CurrencyController.php +++ b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php @@ -68,7 +68,7 @@ class CurrencyController extends Controller /** @var TransactionCurrency $currency */ foreach ($collection as $currency) { $result[] = [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol, @@ -94,7 +94,7 @@ class CurrencyController extends Controller /** @var TransactionCurrency $currency */ foreach ($collection as $currency) { $result[] = [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => sprintf('%s (%s)', $currency->name, $currency->code), 'code' => $currency->code, 'symbol' => $currency->symbol, diff --git a/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php index 9d0d9595e7..e76794da2c 100644 --- a/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php @@ -68,7 +68,7 @@ class ObjectGroupController extends Controller /** @var ObjectGroup $objectGroup */ foreach ($result as $objectGroup) { $return[] = [ - 'id' => (string) $objectGroup->id, + 'id' => (string)$objectGroup->id, 'name' => $objectGroup->title, 'title' => $objectGroup->title, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php index ecab2caa43..3474edd3a1 100644 --- a/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php +++ b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php @@ -75,14 +75,14 @@ class PiggyBankController extends Controller $currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency; $objectGroup = $piggy->objectGroups()->first(); $response[] = [ - 'id' => (string) $piggy->id, + 'id' => (string)$piggy->id, 'name' => $piggy->name, - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_name' => $currency->name, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, - 'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id, + 'object_group_id' => null === $objectGroup ? null : (string)$objectGroup->id, 'object_group_title' => $objectGroup?->title, ]; } @@ -107,7 +107,7 @@ class PiggyBankController extends Controller $currentAmount = $this->piggyRepository->getRepetition($piggy)->currentamount ?? '0'; $objectGroup = $piggy->objectGroups()->first(); $response[] = [ - 'id' => (string) $piggy->id, + 'id' => (string)$piggy->id, 'name' => $piggy->name, 'name_with_balance' => sprintf( '%s (%s / %s)', @@ -115,12 +115,12 @@ class PiggyBankController extends Controller app('amount')->formatAnything($currency, $currentAmount, false), app('amount')->formatAnything($currency, $piggy->targetamount, false), ), - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_name' => $currency->name, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, - 'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id, + 'object_group_id' => null === $objectGroup ? null : (string)$objectGroup->id, 'object_group_title' => $objectGroup?->title, ]; } diff --git a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php index f0e13bff01..cd60887127 100644 --- a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php +++ b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php @@ -66,7 +66,7 @@ class RecurrenceController extends Controller /** @var Recurrence $recurrence */ foreach ($recurrences as $recurrence) { $response[] = [ - 'id' => (string) $recurrence->id, + 'id' => (string)$recurrence->id, 'name' => $recurrence->title, 'description' => $recurrence->description, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/RuleController.php b/app/Api/V1/Controllers/Autocomplete/RuleController.php index e00a97bae8..a396fd4d4f 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleController.php @@ -65,7 +65,7 @@ class RuleController extends Controller /** @var Rule $rule */ foreach ($rules as $rule) { $response[] = [ - 'id' => (string) $rule->id, + 'id' => (string)$rule->id, 'name' => $rule->title, 'description' => $rule->description, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php index ea84263593..7ea36c83dc 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php @@ -65,7 +65,7 @@ class RuleGroupController extends Controller /** @var RuleGroup $group */ foreach ($groups as $group) { $response[] = [ - 'id' => (string) $group->id, + 'id' => (string)$group->id, 'name' => $group->title, 'description' => $group->description, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/TagController.php b/app/Api/V1/Controllers/Autocomplete/TagController.php index 2bb1ad1578..c79e4feb4e 100644 --- a/app/Api/V1/Controllers/Autocomplete/TagController.php +++ b/app/Api/V1/Controllers/Autocomplete/TagController.php @@ -68,7 +68,7 @@ class TagController extends Controller /** @var Tag $tag */ foreach ($result as $tag) { $array[] = [ - 'id' => (string) $tag->id, + 'id' => (string)$tag->id, 'name' => $tag->tag, 'tag' => $tag->tag, ]; diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionController.php b/app/Api/V1/Controllers/Autocomplete/TransactionController.php index c43e0caa2d..0abe42e50d 100644 --- a/app/Api/V1/Controllers/Autocomplete/TransactionController.php +++ b/app/Api/V1/Controllers/Autocomplete/TransactionController.php @@ -76,8 +76,8 @@ class TransactionController extends Controller /** @var TransactionJournal $journal */ foreach ($filtered as $journal) { $array[] = [ - 'id' => (string) $journal->id, - 'transaction_group_id' => (string) $journal->transaction_group_id, + 'id' => (string)$journal->id, + 'transaction_group_id' => (string)$journal->transaction_group_id, 'name' => $journal->description, 'description' => $journal->description, ]; @@ -96,7 +96,7 @@ class TransactionController extends Controller $result = new Collection(); if (is_numeric($data['query'])) { // search for group, not journal. - $firstResult = $this->groupRepository->find((int) $data['query']); + $firstResult = $this->groupRepository->find((int)$data['query']); if (null !== $firstResult) { // group may contain multiple journals, each a result: foreach ($firstResult->transactionJournals as $journal) { @@ -114,8 +114,8 @@ class TransactionController extends Controller /** @var TransactionJournal $journal */ foreach ($result as $journal) { $array[] = [ - 'id' => (string) $journal->id, - 'transaction_group_id' => (string) $journal->transaction_group_id, + 'id' => (string)$journal->id, + 'transaction_group_id' => (string)$journal->transaction_group_id, 'name' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description), 'description' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description), ]; diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php index 852b91fa82..272a23afa2 100644 --- a/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php +++ b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php @@ -65,7 +65,7 @@ class TransactionTypeController extends Controller foreach ($types as $type) { // different key for consistency. $array[] = [ - 'id' => (string) $type->id, + 'id' => (string)$type->id, 'name' => $type->type, 'type' => $type->type, ]; diff --git a/app/Api/V1/Controllers/Chart/AccountController.php b/app/Api/V1/Controllers/Chart/AccountController.php index 2943243d07..de628ffcfb 100644 --- a/app/Api/V1/Controllers/Chart/AccountController.php +++ b/app/Api/V1/Controllers/Chart/AccountController.php @@ -104,7 +104,7 @@ class AccountController extends Controller } $currentSet = [ 'label' => $account->name, - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 2f3a41c8a6..74160af153 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -76,38 +76,6 @@ abstract class Controller extends BaseController ); } - /** - * Method to help build URL's. - */ - 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); - } - - 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. */ @@ -216,4 +184,36 @@ abstract class Controller extends BaseController return $bag; } + + /** + * Method to help build URL's. + */ + 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); + } + + 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/Bulk/TransactionController.php b/app/Api/V1/Controllers/Data/Bulk/TransactionController.php index f6ae26a5e2..869c17ae87 100644 --- a/app/Api/V1/Controllers/Data/Bulk/TransactionController.php +++ b/app/Api/V1/Controllers/Data/Bulk/TransactionController.php @@ -70,8 +70,8 @@ class TransactionController extends Controller // to respond to what is in the $query. // this is OK because only one thing can be in the query at the moment. if ($this->isUpdateTransactionAccount($params)) { - $original = $this->repository->find((int) $params['where']['account_id']); - $destination = $this->repository->find((int) $params['update']['account_id']); + $original = $this->repository->find((int)$params['where']['account_id']); + $destination = $this->repository->find((int)$params['update']['account_id']); /** @var AccountDestroyService $service */ $service = app(AccountDestroyService::class); diff --git a/app/Api/V1/Controllers/Data/Export/ExportController.php b/app/Api/V1/Controllers/Data/Export/ExportController.php index 9f75c3503b..8eda16cf8f 100644 --- a/app/Api/V1/Controllers/Data/Export/ExportController.php +++ b/app/Api/V1/Controllers/Data/Export/ExportController.php @@ -68,6 +68,32 @@ class ExportController extends Controller return $this->returnExport('accounts'); } + /** + * @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 @@ -189,30 +215,4 @@ class ExportController extends Controller return $this->returnExport('transactions'); } - - /** - * @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/Insight/Expense/TagController.php b/app/Api/V1/Controllers/Insight/Expense/TagController.php index de7c902816..b2b2b6b4ea 100644 --- a/app/Api/V1/Controllers/Insight/Expense/TagController.php +++ b/app/Api/V1/Controllers/Insight/Expense/TagController.php @@ -75,28 +75,28 @@ class TagController extends Controller $genericSet = $collector->getExtractedJournals(); foreach ($genericSet as $journal) { - $currencyId = (int) $journal['currency_id']; - $foreignCurrencyId = (int) $journal['foreign_currency_id']; + $currencyId = (int)$journal['currency_id']; + $foreignCurrencyId = (int)$journal['foreign_currency_id']; if (0 !== $currencyId) { $response[$currencyId] ??= [ 'difference' => '0', 'difference_float' => 0, - 'currency_id' => (string) $currencyId, + 'currency_id' => (string)$currencyId, 'currency_code' => $journal['currency_code'], ]; $response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], $journal['amount']); - $response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference']; // float but on purpose. + $response[$currencyId]['difference_float'] = (float)$response[$currencyId]['difference']; // float but on purpose. } if (0 !== $foreignCurrencyId) { $response[$foreignCurrencyId] ??= [ 'difference' => '0', 'difference_float' => 0, - 'currency_id' => (string) $foreignCurrencyId, + 'currency_id' => (string)$foreignCurrencyId, 'currency_code' => $journal['foreign_currency_code'], ]; $response[$foreignCurrencyId]['difference'] = bcadd($response[$foreignCurrencyId]['difference'], $journal['foreign_amount']); - $response[$foreignCurrencyId]['difference_float'] = (float) $response[$foreignCurrencyId]['difference']; // float but on purpose. + $response[$foreignCurrencyId]['difference_float'] = (float)$response[$foreignCurrencyId]['difference']; // float but on purpose. } } @@ -130,8 +130,8 @@ class TagController extends Controller /** @var array $journal */ foreach ($genericSet as $journal) { - $currencyId = (int) $journal['currency_id']; - $foreignCurrencyId = (int) $journal['foreign_currency_id']; + $currencyId = (int)$journal['currency_id']; + $foreignCurrencyId = (int)$journal['foreign_currency_id']; /** @var array $tag */ foreach ($journal['tags'] as $tag) { @@ -142,15 +142,15 @@ class TagController extends Controller // on currency ID if (0 !== $currencyId) { $response[$key] ??= [ - 'id' => (string) $tagId, + 'id' => (string)$tagId, 'name' => $tag['name'], 'difference' => '0', 'difference_float' => 0, - 'currency_id' => (string) $currencyId, + 'currency_id' => (string)$currencyId, 'currency_code' => $journal['currency_code'], ]; $response[$key]['difference'] = bcadd($response[$key]['difference'], $journal['amount']); - $response[$key]['difference_float'] = (float) $response[$key]['difference']; // float but on purpose. + $response[$key]['difference_float'] = (float)$response[$key]['difference']; // float but on purpose. } // on foreign ID @@ -158,11 +158,11 @@ class TagController extends Controller $response[$foreignKey] = $journal[$foreignKey] ?? [ 'difference' => '0', 'difference_float' => 0, - 'currency_id' => (string) $foreignCurrencyId, + 'currency_id' => (string)$foreignCurrencyId, 'currency_code' => $journal['foreign_currency_code'], ]; $response[$foreignKey]['difference'] = bcadd($response[$foreignKey]['difference'], $journal['foreign_amount']); - $response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // float but on purpose. + $response[$foreignKey]['difference_float'] = (float)$response[$foreignKey]['difference']; // float but on purpose. } } } diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php index 965aaf4343..0ac913f809 100644 --- a/app/Api/V1/Controllers/Summary/BasicController.php +++ b/app/Api/V1/Controllers/Summary/BasicController.php @@ -118,23 +118,6 @@ class BasicController extends Controller return response()->json($return); } - /** - * Check if date is outside session range. - */ - 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; - } - private function getBalanceInformation(Carbon $start, Carbon $end): array { // prep some arrays: @@ -152,7 +135,7 @@ class BasicController extends Controller /** @var array $transactionJournal */ foreach ($set as $transactionJournal) { - $currencyId = (int) $transactionJournal['currency_id']; + $currencyId = (int)$transactionJournal['currency_id']; $incomes[$currencyId] ??= '0'; $incomes[$currencyId] = bcadd( $incomes[$currencyId], @@ -170,7 +153,7 @@ class BasicController extends Controller /** @var array $transactionJournal */ foreach ($set as $transactionJournal) { - $currencyId = (int) $transactionJournal['currency_id']; + $currencyId = (int)$transactionJournal['currency_id']; $expenses[$currencyId] ??= '0'; $expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']); $sums[$currencyId] ??= '0'; @@ -189,7 +172,7 @@ class BasicController extends Controller 'key' => sprintf('balance-in-%s', $currency->code), 'title' => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]), 'monetary_value' => $sums[$currencyId] ?? '0', - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, @@ -202,7 +185,7 @@ class BasicController extends Controller 'key' => sprintf('spent-in-%s', $currency->code), 'title' => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]), 'monetary_value' => $expenses[$currencyId] ?? '0', - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, @@ -214,7 +197,7 @@ class BasicController extends Controller 'key' => sprintf('earned-in-%s', $currency->code), 'title' => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]), 'monetary_value' => $incomes[$currencyId] ?? '0', - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, @@ -248,7 +231,7 @@ class BasicController extends Controller 'key' => sprintf('bills-paid-in-%s', $info['code']), 'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]), 'monetary_value' => $amount, - 'currency_id' => (string) $info['id'], + 'currency_id' => (string)$info['id'], 'currency_code' => $info['code'], 'currency_symbol' => $info['symbol'], 'currency_decimal_places' => $info['decimal_places'], @@ -267,7 +250,7 @@ class BasicController extends Controller 'key' => sprintf('bills-unpaid-in-%s', $info['code']), 'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]), 'monetary_value' => $amount, - 'currency_id' => (string) $info['id'], + 'currency_id' => (string)$info['id'], 'currency_code' => $info['code'], 'currency_symbol' => $info['symbol'], 'currency_decimal_places' => $info['decimal_places'], @@ -294,21 +277,21 @@ class BasicController extends Controller foreach ($spent as $row) { // either an amount was budgeted or 0 is available. - $amount = (string) ($available[$row['currency_id']] ?? '0'); + $amount = (string)($available[$row['currency_id']] ?? '0'); $spentInCurrency = $row['sum']; $leftToSpend = bcadd($amount, $spentInCurrency); $days = $today->diffInDays($end) + 1; $perDay = '0'; if (0 !== $days && bccomp($leftToSpend, '0') > -1) { - $perDay = bcdiv($leftToSpend, (string) $days); + $perDay = bcdiv($leftToSpend, (string)$days); } $return[] = [ 'key' => sprintf('left-to-spend-in-%s', $row['currency_code']), 'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]), 'monetary_value' => $leftToSpend, - 'currency_id' => (string) $row['currency_id'], + 'currency_id' => (string)$row['currency_id'], 'currency_code' => $row['currency_code'], 'currency_symbol' => $row['currency_symbol'], 'currency_decimal_places' => $row['currency_decimal_places'], @@ -368,7 +351,7 @@ class BasicController extends Controller 'key' => sprintf('net-worth-in-%s', $data['currency_code']), 'title' => trans('firefly.box_net_worth_in_currency', ['currency' => $data['currency_symbol']]), 'monetary_value' => $amount, - 'currency_id' => (string) $data['currency_id'], + 'currency_id' => (string)$data['currency_id'], 'currency_code' => $data['currency_code'], 'currency_symbol' => $data['currency_symbol'], 'currency_decimal_places' => $data['currency_decimal_places'], @@ -380,4 +363,21 @@ class BasicController extends Controller return $return; } + + /** + * Check if date is outside session range. + */ + 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 7eed5ba624..d1ae4de98d 100644 --- a/app/Api/V1/Controllers/System/ConfigurationController.php +++ b/app/Api/V1/Controllers/System/ConfigurationController.php @@ -88,6 +88,35 @@ class ConfigurationController extends Controller return response()->json($return); } + /** + * Get all config values. + */ + 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, + ]; + } + + 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 @@ -145,33 +174,4 @@ class ConfigurationController extends Controller return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE); } - - /** - * Get all config values. - */ - 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, - ]; - } - - private function getStaticConfiguration(): array - { - $list = EitherConfigKey::$static; - $return = []; - foreach ($list as $key) { - $return[$key] = config($key); - } - - return $return; - } } diff --git a/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php index f3dacae241..4ec6250dd2 100644 --- a/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php +++ b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php @@ -83,12 +83,12 @@ class MoveTransactionsRequest extends FormRequest $data = $validator->getData(); $repository = app(AccountRepositoryInterface::class); $repository->setUser(auth()->user()); - $original = $repository->find((int) $data['original_account']); - $destination = $repository->find((int) $data['destination_account']); + $original = $repository->find((int)$data['original_account']); + $destination = $repository->find((int)$data['destination_account']); // not the same type: if ($original->accountType->type !== $destination->accountType->type) { - $validator->errors()->add('title', (string) trans('validation.same_account_type')); + $validator->errors()->add('title', (string)trans('validation.same_account_type')); return; } @@ -98,7 +98,7 @@ class MoveTransactionsRequest extends FormRequest // check different scenario's. if (null === $originalCurrency xor null === $destinationCurrency) { - $validator->errors()->add('title', (string) trans('validation.same_account_currency')); + $validator->errors()->add('title', (string)trans('validation.same_account_currency')); return; } @@ -107,7 +107,7 @@ class MoveTransactionsRequest extends FormRequest return; } if ($originalCurrency->code !== $destinationCurrency->code) { - $validator->errors()->add('title', (string) trans('validation.same_account_currency')); + $validator->errors()->add('title', (string)trans('validation.same_account_currency')); } } } diff --git a/app/Api/V1/Requests/Insight/GenericRequest.php b/app/Api/V1/Requests/Insight/GenericRequest.php index 45e4a6d6df..1bde6efeb7 100644 --- a/app/Api/V1/Requests/Insight/GenericRequest.php +++ b/app/Api/V1/Requests/Insight/GenericRequest.php @@ -78,6 +78,25 @@ 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); + } + } + } + } + public function getBills(): Collection { $this->parseBills(); @@ -85,6 +104,25 @@ 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); + } + } + } + } + public function getBudgets(): Collection { $this->parseBudgets(); @@ -92,6 +130,25 @@ 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); + } + } + } + } + public function getCategories(): Collection { $this->parseCategories(); @@ -99,6 +156,25 @@ 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); + } + } + } + } + public function getEnd(): Carbon { $date = $this->getCarbonDate('end'); @@ -154,100 +230,6 @@ class GenericRequest extends FormRequest return $this->tags; } - /** - * The rules that the incoming request must be matched against. - */ - 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); - } - } - } - } - private function parseTags(): void { if (0 !== $this->tags->count()) { @@ -266,4 +248,22 @@ class GenericRequest extends FormRequest } } } + + /** + * The rules that the incoming request must be matched against. + */ + 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/BudgetLimit/UpdateRequest.php b/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php index fa20b2db8f..67060a6dd2 100644 --- a/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php @@ -83,7 +83,7 @@ class UpdateRequest extends FormRequest $start = new Carbon($data['start']); $end = new Carbon($data['end']); if ($end->isBefore($start)) { - $validator->errors()->add('end', (string) trans('validation.date_after')); + $validator->errors()->add('end', (string)trans('validation.date_after')); } } } diff --git a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php index 09c80115ec..e6f9b52d28 100644 --- a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php @@ -73,6 +73,65 @@ 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. + */ + private function getTransactionData(): array + { + $return = []; + + // transaction data: + /** @var null|array $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. + */ + private function getRepetitionData(): array + { + $return = []; + + // repetition data: + /** @var null|array $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. */ @@ -136,63 +195,4 @@ class StoreRequest extends FormRequest Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); } } - - /** - * 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. - */ - private function getTransactionData(): array - { - $return = []; - - // transaction data: - /** @var null|array $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. - */ - private function getRepetitionData(): array - { - $return = []; - - // repetition data: - /** @var null|array $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; - } } diff --git a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php index 49ae22bd17..6bc7913069 100644 --- a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php @@ -78,6 +78,70 @@ class UpdateRequest extends FormRequest return $return; } + /** + * Returns the repetition data as it is found in the submitted data. + */ + private function getRepetitionData(): ?array + { + $return = []; + + // repetition data: + /** @var null|array $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. + */ + private function getTransactionData(): array + { + $return = []; + + // transaction data: + /** @var null|array $transactions */ + $transactions = $this->get('transactions'); + if (null === $transactions) { + return []; + } + + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = $this->getSingleTransactionData($transaction); + } + + return $return; + } + /** * The rules that the incoming request must be matched against. */ @@ -146,68 +210,4 @@ class UpdateRequest extends FormRequest Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); } } - - /** - * Returns the repetition data as it is found in the submitted data. - */ - private function getRepetitionData(): ?array - { - $return = []; - - // repetition data: - /** @var null|array $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. - */ - private function getTransactionData(): array - { - $return = []; - - // transaction data: - /** @var null|array $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/Rule/StoreRequest.php b/app/Api/V1/Requests/Models/Rule/StoreRequest.php index 697447cfde..dff5c76d8d 100644 --- a/app/Api/V1/Requests/Models/Rule/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Rule/StoreRequest.php @@ -64,6 +64,42 @@ class StoreRequest extends FormRequest return $data; } + 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; + } + + 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. */ @@ -197,40 +233,4 @@ class StoreRequest extends FormRequest $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); } } - - 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; - } - - 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; - } } diff --git a/app/Api/V1/Requests/Models/Rule/TestRequest.php b/app/Api/V1/Requests/Models/Rule/TestRequest.php index e8c466235f..1b081e2e88 100644 --- a/app/Api/V1/Requests/Models/Rule/TestRequest.php +++ b/app/Api/V1/Requests/Models/Rule/TestRequest.php @@ -47,16 +47,6 @@ class TestRequest extends FormRequest ]; } - public function rules(): array - { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - 'accounts' => '', - 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', - ]; - } - private function getPage(): int { return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); @@ -81,4 +71,14 @@ class TestRequest extends FormRequest { return $this->get('accounts'); } + + public function rules(): array + { + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + 'accounts' => '', + 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', + ]; + } } diff --git a/app/Api/V1/Requests/Models/Rule/TriggerRequest.php b/app/Api/V1/Requests/Models/Rule/TriggerRequest.php index 0fc38b0e96..f0ccc1b0b5 100644 --- a/app/Api/V1/Requests/Models/Rule/TriggerRequest.php +++ b/app/Api/V1/Requests/Models/Rule/TriggerRequest.php @@ -46,16 +46,6 @@ class TriggerRequest extends FormRequest ]; } - public function rules(): array - { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - 'accounts' => '', - 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', - ]; - } - private function getDate(string $field): ?Carbon { $value = $this->query($field); @@ -75,4 +65,14 @@ class TriggerRequest extends FormRequest { return $this->get('accounts') ?? []; } + + public function rules(): array + { + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + 'accounts' => '', + 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', + ]; + } } diff --git a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php index dd0b1810d3..3e89716d7f 100644 --- a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php @@ -70,6 +70,50 @@ class UpdateRequest extends FormRequest return $return; } + 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; + } + + 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. */ @@ -204,48 +248,4 @@ class UpdateRequest extends FormRequest $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); } } - - 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; - } - - 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; - } } diff --git a/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php b/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php index b8d125780c..c7927ee67d 100644 --- a/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php +++ b/app/Api/V1/Requests/Models/RuleGroup/TestRequest.php @@ -46,16 +46,6 @@ class TestRequest extends FormRequest ]; } - public function rules(): array - { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - 'accounts' => '', - 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', - ]; - } - private function getDate(string $field): ?Carbon { $value = $this->query($field); @@ -75,4 +65,14 @@ class TestRequest extends FormRequest { return $this->get('accounts'); } + + public function rules(): array + { + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + 'accounts' => '', + 'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', + ]; + } } diff --git a/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php b/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php index b8aafaf0d5..37f2a65513 100644 --- a/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php +++ b/app/Api/V1/Requests/Models/RuleGroup/TriggerRequest.php @@ -46,14 +46,6 @@ class TriggerRequest extends FormRequest ]; } - public function rules(): array - { - return [ - 'start' => 'date', - 'end' => 'date|after_or_equal:start', - ]; - } - private function getDate(string $field): ?Carbon { $value = $this->query($field); @@ -77,4 +69,12 @@ class TriggerRequest extends FormRequest return $this->get('accounts'); } + + public function rules(): array + { + return [ + 'start' => 'date', + 'end' => 'date|after_or_equal:start', + ]; + } } diff --git a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php index f50122ed65..7910be0d16 100644 --- a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php @@ -69,134 +69,6 @@ class StoreRequest extends FormRequest // TODO include location and ability to process it. } - /** - * The rules that the incoming request must be matched against. - */ - public function rules(): array - { - app('log')->debug('Collect rules of TransactionStoreRequest'); - $validProtocols = config('firefly.valid_url_protocols'); - - return [ - // basic fields for group: - 'group_title' => 'min:1|max:1000|nullable', - 'error_if_duplicate_hash' => [new IsBoolean()], - 'apply_rules' => [new IsBoolean()], - - // transaction rules (in array for splits): - 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', - 'transactions.*.date' => ['required', new IsDateOrTime()], - 'transactions.*.order' => 'numeric|min:0', - - // currency info - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - - // amount - 'transactions.*.amount' => ['required', new IsValidPositiveAmount()], - 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], - - // description - 'transactions.*.description' => 'nullable|min:1|max:1000', - - // source of transaction - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.source_name' => 'min:1|max:255|nullable', - 'transactions.*.source_iban' => 'min:1|max:255|nullable|iban', - 'transactions.*.source_number' => 'min:1|max:255|nullable', - 'transactions.*.source_bic' => 'min:1|max:255|nullable|bic', - - // destination of transaction - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.destination_name' => 'min:1|max:255|nullable', - 'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban', - 'transactions.*.destination_number' => 'min:1|max:255|nullable', - 'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic', - - // budget, category, bill and piggy - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser()], - 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], - 'transactions.*.category_name' => 'min:1|max:255|nullable', - 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], - 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - 'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()], - 'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - - // other interesting fields - 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1|max:32768|nullable', - 'transactions.*.tags' => 'min:0|max:255', - 'transactions.*.tags.*' => 'min:0|max:255', - - // meta info fields - 'transactions.*.internal_reference' => 'min:1|max:255|nullable', - 'transactions.*.external_id' => 'min:1|max:255|nullable', - 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', - 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', - 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), - - // SEPA fields: - 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', - 'transactions.*.sepa_db' => 'min:1|max:255|nullable', - 'transactions.*.sepa_country' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', - 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', - - // dates - 'transactions.*.interest_date' => 'date|nullable', - 'transactions.*.book_date' => 'date|nullable', - 'transactions.*.process_date' => 'date|nullable', - 'transactions.*.due_date' => 'date|nullable', - 'transactions.*.payment_date' => 'date|nullable', - 'transactions.*.invoice_date' => 'date|nullable', - ]; - } - - /** - * Configure the validator instance. - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator): void { - // must be valid array. - $this->validateTransactionArray($validator); - - // must submit at least one transaction. - app('log')->debug('Now going to validateOneTransaction'); - $this->validateOneTransaction($validator); - app('log')->debug('Now done with validateOneTransaction'); - - // all journals must have a description - $this->validateDescriptions($validator); - - // all transaction types must be equal: - $this->validateTransactionTypes($validator); - - // validate foreign currency info - $this->validateForeignCurrencyInformation($validator); - - // validate all account info - $this->validateAccountInformation($validator); - - // validate source/destination is equal, depending on the transaction journal type. - $this->validateEqualAccounts($validator); - - // the group must have a description if > 1 journal. - $this->validateGroupDescription($validator); - } - ); - if ($validator->fails()) { - Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); - } - } - /** * Get transaction data. */ @@ -291,4 +163,132 @@ class StoreRequest extends FormRequest return $return; } + + /** + * The rules that the incoming request must be matched against. + */ + public function rules(): array + { + app('log')->debug('Collect rules of TransactionStoreRequest'); + $validProtocols = config('firefly.valid_url_protocols'); + + return [ + // basic fields for group: + 'group_title' => 'min:1|max:1000|nullable', + 'error_if_duplicate_hash' => [new IsBoolean()], + 'apply_rules' => [new IsBoolean()], + + // transaction rules (in array for splits): + 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => ['required', new IsDateOrTime()], + 'transactions.*.order' => 'numeric|min:0', + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + + // amount + 'transactions.*.amount' => ['required', new IsValidPositiveAmount()], + 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], + + // description + 'transactions.*.description' => 'nullable|min:1|max:1000', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.source_name' => 'min:1|max:255|nullable', + 'transactions.*.source_iban' => 'min:1|max:255|nullable|iban', + 'transactions.*.source_number' => 'min:1|max:255|nullable', + 'transactions.*.source_bic' => 'min:1|max:255|nullable|bic', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.destination_name' => 'min:1|max:255|nullable', + 'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban', + 'transactions.*.destination_number' => 'min:1|max:255|nullable', + 'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser()], + 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], + 'transactions.*.category_name' => 'min:1|max:255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], + 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + 'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()], + 'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean()], + 'transactions.*.notes' => 'min:1|max:32768|nullable', + 'transactions.*.tags' => 'min:0|max:255', + 'transactions.*.tags.*' => 'min:0|max:255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1|max:255|nullable', + 'transactions.*.external_id' => 'min:1|max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', + 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', + 'transactions.*.sepa_db' => 'min:1|max:255|nullable', + 'transactions.*.sepa_country' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + ]; + } + + /** + * Configure the validator instance. + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + // must be valid array. + $this->validateTransactionArray($validator); + + // must submit at least one transaction. + app('log')->debug('Now going to validateOneTransaction'); + $this->validateOneTransaction($validator); + app('log')->debug('Now done with validateOneTransaction'); + + // all journals must have a description + $this->validateDescriptions($validator); + + // all transaction types must be equal: + $this->validateTransactionTypes($validator); + + // validate foreign currency info + $this->validateForeignCurrencyInformation($validator); + + // validate all account info + $this->validateAccountInformation($validator); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccounts($validator); + + // the group must have a description if > 1 journal. + $this->validateGroupDescription($validator); + } + ); + if ($validator->fails()) { + Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); + } + } } diff --git a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php index 77738174ec..b6a3f570b4 100644 --- a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php @@ -90,127 +90,6 @@ class UpdateRequest extends FormRequest return $data; } - /** - * The rules that the incoming request must be matched against. - */ - public function rules(): array - { - app('log')->debug(sprintf('Now in %s', __METHOD__)); - $validProtocols = config('firefly.valid_url_protocols'); - - return [ - // basic fields for group: - 'group_title' => 'min:1|max:1000|nullable', - 'apply_rules' => [new IsBoolean()], - - // transaction rules (in array for splits): - 'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation', - 'transactions.*.date' => [new IsDateOrTime()], - 'transactions.*.order' => 'numeric|min:0', - - // group id: - 'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()], - - // currency info - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', - - // amount - 'transactions.*.amount' => [new IsValidPositiveAmount()], - 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], - - // description - 'transactions.*.description' => 'nullable|min:1|max:1000', - - // source of transaction - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.source_name' => 'min:1|max:255|nullable', - - // destination of transaction - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.destination_name' => 'min:1|max:255|nullable', - - // budget, category, bill and piggy - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'], - 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], - 'transactions.*.category_name' => 'min:1|max:255|nullable', - 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], - 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - - // other interesting fields - 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1|max:32768|nullable', - 'transactions.*.tags' => 'min:0|max:255|nullable', - 'transactions.*.tags.*' => 'min:0|max:255', - - // meta info fields - 'transactions.*.internal_reference' => 'min:1|max:255|nullable', - 'transactions.*.external_id' => 'min:1|max:255|nullable', - 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', - 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', - 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), - - // SEPA fields: - 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', - 'transactions.*.sepa_db' => 'min:1|max:255|nullable', - 'transactions.*.sepa_country' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', - 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', - - // dates - 'transactions.*.interest_date' => 'date|nullable', - 'transactions.*.book_date' => 'date|nullable', - 'transactions.*.process_date' => 'date|nullable', - 'transactions.*.due_date' => 'date|nullable', - 'transactions.*.payment_date' => 'date|nullable', - 'transactions.*.invoice_date' => 'date|nullable', - ]; - } - - /** - * Configure the validator instance. - */ - public function withValidator(Validator $validator): void - { - app('log')->debug('Now in withValidator'); - - /** @var TransactionGroup $transactionGroup */ - $transactionGroup = $this->route()->parameter('transactionGroup'); - $validator->after( - function (Validator $validator) use ($transactionGroup): void { - // if more than one, verify that there are journal ID's present. - $this->validateJournalIds($validator, $transactionGroup); - - // all transaction types must be equal: - $this->validateTransactionTypesForUpdate($validator); - - // user wants to update a reconciled transaction. - // source, destination, amount + foreign_amount cannot be changed - // and must be omitted from the request. - $this->preventUpdateReconciled($validator, $transactionGroup); - - // validate source/destination is equal, depending on the transaction journal type. - $this->validateEqualAccountsForUpdate($validator, $transactionGroup); - - // see method: - // $this->preventNoAccountInfo($validator, ); - - // validate that the currency fits the source and/or destination account. - // validate all account info - $this->validateAccountInformationUpdate($validator, $transactionGroup); - } - ); - if ($validator->fails()) { - Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); - } - } - /** * Get transaction data. * @@ -258,7 +137,7 @@ class UpdateRequest extends FormRequest { foreach ($this->integerFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]); + $current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]); } } @@ -273,7 +152,7 @@ class UpdateRequest extends FormRequest { foreach ($this->stringFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->clearString((string) $transaction[$fieldName]); + $current[$fieldName] = $this->clearString((string)$transaction[$fieldName]); } } @@ -288,7 +167,7 @@ class UpdateRequest extends FormRequest { foreach ($this->textareaFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines + $current[$fieldName] = $this->clearStringKeepNewlines((string)$transaction[$fieldName]); // keep newlines } } @@ -304,8 +183,8 @@ class UpdateRequest extends FormRequest foreach ($this->dateFields as $fieldName) { app('log')->debug(sprintf('Now at date field %s', $fieldName)); if (array_key_exists($fieldName, $transaction)) { - app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName])); - $current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]); + app('log')->debug(sprintf('New value: "%s"', (string)$transaction[$fieldName])); + $current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]); } } @@ -320,7 +199,7 @@ class UpdateRequest extends FormRequest { foreach ($this->booleanFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]); + $current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]); } } @@ -355,11 +234,132 @@ class UpdateRequest extends FormRequest $current[$fieldName] = sprintf('%.12f', $value); } if (!is_float($value)) { - $current[$fieldName] = (string) $value; + $current[$fieldName] = (string)$value; } } } return $current; } + + /** + * The rules that the incoming request must be matched against. + */ + public function rules(): array + { + app('log')->debug(sprintf('Now in %s', __METHOD__)); + $validProtocols = config('firefly.valid_url_protocols'); + + return [ + // basic fields for group: + 'group_title' => 'min:1|max:1000|nullable', + 'apply_rules' => [new IsBoolean()], + + // transaction rules (in array for splits): + 'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => [new IsDateOrTime()], + 'transactions.*.order' => 'numeric|min:0', + + // group id: + 'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()], + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', + + // amount + 'transactions.*.amount' => [new IsValidPositiveAmount()], + 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], + + // description + 'transactions.*.description' => 'nullable|min:1|max:1000', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.source_name' => 'min:1|max:255|nullable', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.destination_name' => 'min:1|max:255|nullable', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'], + 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], + 'transactions.*.category_name' => 'min:1|max:255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], + 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean()], + 'transactions.*.notes' => 'min:1|max:32768|nullable', + 'transactions.*.tags' => 'min:0|max:255|nullable', + 'transactions.*.tags.*' => 'min:0|max:255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1|max:255|nullable', + 'transactions.*.external_id' => 'min:1|max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', + 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', + 'transactions.*.sepa_db' => 'min:1|max:255|nullable', + 'transactions.*.sepa_country' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + ]; + } + + /** + * Configure the validator instance. + */ + public function withValidator(Validator $validator): void + { + app('log')->debug('Now in withValidator'); + + /** @var TransactionGroup $transactionGroup */ + $transactionGroup = $this->route()->parameter('transactionGroup'); + $validator->after( + function (Validator $validator) use ($transactionGroup): void { + // if more than one, verify that there are journal ID's present. + $this->validateJournalIds($validator, $transactionGroup); + + // all transaction types must be equal: + $this->validateTransactionTypesForUpdate($validator); + + // user wants to update a reconciled transaction. + // source, destination, amount + foreign_amount cannot be changed + // and must be omitted from the request. + $this->preventUpdateReconciled($validator, $transactionGroup); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccountsForUpdate($validator, $transactionGroup); + + // see method: + // $this->preventNoAccountInfo($validator, ); + + // validate that the currency fits the source and/or destination account. + // validate all account info + $this->validateAccountInformationUpdate($validator, $transactionGroup); + } + ); + if ($validator->fails()) { + Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); + } + } } diff --git a/app/Api/V2/Controllers/Autocomplete/CategoryController.php b/app/Api/V2/Controllers/Autocomplete/CategoryController.php index a9a8f71bfd..0df59ffbe2 100644 --- a/app/Api/V2/Controllers/Autocomplete/CategoryController.php +++ b/app/Api/V2/Controllers/Autocomplete/CategoryController.php @@ -72,7 +72,7 @@ class CategoryController extends Controller $filtered = $result->map( static function (Category $item) { return [ - 'id' => (string) $item->id, + 'id' => (string)$item->id, 'name' => $item->name, ]; } diff --git a/app/Api/V2/Controllers/Autocomplete/TagController.php b/app/Api/V2/Controllers/Autocomplete/TagController.php index 356c3e8456..2c977f3bf6 100644 --- a/app/Api/V2/Controllers/Autocomplete/TagController.php +++ b/app/Api/V2/Controllers/Autocomplete/TagController.php @@ -72,10 +72,10 @@ class TagController extends Controller $filtered = $result->map( static function (Tag $item) { return [ - 'id' => (string) $item->id, - 'name' => $item->tag, - 'value' => (string) $item->id, - 'label' => $item->tag, + 'id' => (string)$item->id, + 'name' => $item->tag, + 'value' => (string)$item->id, + 'label' => $item->tag, ]; } ); diff --git a/app/Api/V2/Controllers/Chart/AccountController.php b/app/Api/V2/Controllers/Chart/AccountController.php index 27aa842a4a..0df13c4e0e 100644 --- a/app/Api/V2/Controllers/Chart/AccountController.php +++ b/app/Api/V2/Controllers/Chart/AccountController.php @@ -126,23 +126,23 @@ class AccountController extends Controller $currency = $default; } $currentSet = [ - 'label' => $account->name, + 'label' => $account->name, // the currency that belongs to the account. - 'currency_id' => (string)$currency->id, - 'currency_code' => $currency->code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, + 'currency_id' => (string)$currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, // the default currency of the user (could be the same!) - 'native_currency_id' => (string)$default->id, - 'native_currency_code' => $default->code, - 'native_currency_symbol' => $default->symbol, - 'native_currency_decimal_places' => $default->decimal_places, - 'start' => $start->toAtomString(), - 'end' => $end->toAtomString(), - 'period' => '1D', - 'entries' => [], - 'native_entries' => [], + 'native_currency_id' => (string)$default->id, + 'native_currency_code' => $default->code, + 'native_currency_symbol' => $default->symbol, + 'native_currency_decimal_places' => $default->decimal_places, + 'start' => $start->toAtomString(), + 'end' => $end->toAtomString(), + 'period' => '1D', + 'entries' => [], + 'native_entries' => [], ]; $currentStart = clone $start; $range = app('steam')->balanceInRange($account, $start, clone $end, $currency); diff --git a/app/Api/V2/Controllers/Chart/BudgetController.php b/app/Api/V2/Controllers/Chart/BudgetController.php index a62256147b..72987816f4 100644 --- a/app/Api/V2/Controllers/Chart/BudgetController.php +++ b/app/Api/V2/Controllers/Chart/BudgetController.php @@ -124,11 +124,11 @@ class BudgetController extends Controller foreach ($rows as $row) { $current = [ 'label' => $budget->name, - 'currency_id' => (string) $row['currency_id'], + 'currency_id' => (string)$row['currency_id'], 'currency_code' => $row['currency_code'], 'currency_name' => $row['currency_name'], 'currency_decimal_places' => $row['currency_decimal_places'], - 'native_currency_id' => (string) $row['native_currency_id'], + 'native_currency_id' => (string)$row['native_currency_id'], 'native_currency_code' => $row['native_currency_code'], 'native_currency_name' => $row['native_currency_name'], 'native_currency_decimal_places' => $row['native_currency_decimal_places'], @@ -189,12 +189,12 @@ class BudgetController extends Controller foreach ($array as $currencyId => $block) { $this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId); $return[$currencyId] ??= [ - 'currency_id' => (string) $currencyId, + 'currency_id' => (string)$currencyId, 'currency_code' => $block['currency_code'], 'currency_name' => $block['currency_name'], 'currency_symbol' => $block['currency_symbol'], - 'currency_decimal_places' => (int) $block['currency_decimal_places'], - 'native_currency_id' => (string) $this->currency->id, + 'currency_decimal_places' => (int)$block['currency_decimal_places'], + 'native_currency_id' => (string)$this->currency->id, 'native_currency_code' => $this->currency->code, 'native_currency_name' => $this->currency->name, 'native_currency_symbol' => $this->currency->symbol, diff --git a/app/Api/V2/Controllers/Chart/CategoryController.php b/app/Api/V2/Controllers/Chart/CategoryController.php index f0bad7c828..09124d9a8c 100644 --- a/app/Api/V2/Controllers/Chart/CategoryController.php +++ b/app/Api/V2/Controllers/Chart/CategoryController.php @@ -112,22 +112,22 @@ class CategoryController extends Controller } // create arrays $return[$key] ??= [ - 'label' => $categoryName, - 'currency_id' => (string)$currency->id, - 'currency_code' => $currency->code, - 'currency_name' => $currency->name, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => (string)$default->id, - 'native_currency_code' => $default->code, - 'native_currency_name' => $default->name, - 'native_currency_symbol' => $default->symbol, - 'native_currency_decimal_places' => $default->decimal_places, - 'period' => null, - 'start' => $start->toAtomString(), - 'end' => $end->toAtomString(), - 'amount' => '0', - 'native_amount' => '0', + 'label' => $categoryName, + 'currency_id' => (string)$currency->id, + 'currency_code' => $currency->code, + 'currency_name' => $currency->name, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + 'native_currency_id' => (string)$default->id, + 'native_currency_code' => $default->code, + 'native_currency_name' => $default->name, + 'native_currency_symbol' => $default->symbol, + 'native_currency_decimal_places' => $default->decimal_places, + 'period' => null, + 'start' => $start->toAtomString(), + 'end' => $end->toAtomString(), + 'amount' => '0', + 'native_amount' => '0', ]; // add monies diff --git a/app/Api/V2/Controllers/Controller.php b/app/Api/V2/Controllers/Controller.php index 13e15d8494..99f30570d0 100644 --- a/app/Api/V2/Controllers/Controller.php +++ b/app/Api/V2/Controllers/Controller.php @@ -67,43 +67,6 @@ class Controller extends BaseController ); } - 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 array|Model $object - */ - final protected function jsonApiObject(string $key, array|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. @@ -184,4 +147,41 @@ class Controller extends BaseController return $bag; } + + 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 array|Model $object + */ + final protected function jsonApiObject(string $key, array|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/Budget/ShowController.php b/app/Api/V2/Controllers/Model/Budget/ShowController.php index f5b0567723..6e5f9499c1 100644 --- a/app/Api/V2/Controllers/Model/Budget/ShowController.php +++ b/app/Api/V2/Controllers/Model/Budget/ShowController.php @@ -51,20 +51,6 @@ class ShowController extends Controller ); } - /** - * Show a budget. - */ - public function show(Budget $budget): JsonResponse - { - $transformer = new BudgetTransformer(); - $transformer->setParameters($this->parameters); - - return response() - ->api($this->jsonApiObject('budgets', $budget, $transformer)) - ->header('Content-Type', self::CONTENT_TYPE) - ; - } - /** * 2023-10-29 removed the cerSum reference, not sure where this is used atm * so removed from api.php. Also applies to "spent" method. @@ -80,6 +66,20 @@ class ShowController extends Controller return response()->json($result); } + /** + * Show a budget. + */ + public function show(Budget $budget): JsonResponse + { + $transformer = new BudgetTransformer(); + $transformer->setParameters($this->parameters); + + return response() + ->api($this->jsonApiObject('budgets', $budget, $transformer)) + ->header('Content-Type', self::CONTENT_TYPE) + ; + } + /** * This endpoint is documented at: * TODO add URL diff --git a/app/Api/V2/Controllers/Summary/BasicController.php b/app/Api/V2/Controllers/Summary/BasicController.php index 41ee356342..c166f3573b 100644 --- a/app/Api/V2/Controllers/Summary/BasicController.php +++ b/app/Api/V2/Controllers/Summary/BasicController.php @@ -114,23 +114,6 @@ class BasicController extends Controller return response()->json($total); } - /** - * Check if date is outside session range. - */ - 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; - } - /** * @throws FireflyException */ @@ -411,4 +394,21 @@ class BasicController extends Controller return $return; } + + /** + * Check if date is outside session range. + */ + 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/V2/Controllers/Transaction/List/TransactionController.php b/app/Api/V2/Controllers/Transaction/List/TransactionController.php index edb6e50de0..a1e6d61fac 100644 --- a/app/Api/V2/Controllers/Transaction/List/TransactionController.php +++ b/app/Api/V2/Controllers/Transaction/List/TransactionController.php @@ -35,43 +35,6 @@ use Illuminate\Http\JsonResponse; */ class TransactionController extends Controller { - public function listByCount(ListByCountRequest $request): JsonResponse - { - // collect transactions: - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); - $collector->setUserGroup(auth()->user()->userGroup) - ->withAPIInformation() - ->setStartRow($request->getStartRow()) - ->setEndRow($request->getEndRow()) - ->setTypes($request->getTransactionTypes()) - ; - - $start = $this->parameters->get('start'); - $end = $this->parameters->get('end'); - if (null !== $start) { - $collector->setStart($start); - } - if (null !== $end) { - $collector->setEnd($end); - } - - $paginator = $collector->getPaginatedGroups(); - $params = $request->buildParams(); - $paginator->setPath( - sprintf( - '%s?%s', - route('api.v2.transactions.list'), - $params - ) - ); - - return response() - ->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer())) - ->header('Content-Type', self::CONTENT_TYPE) - ; - } - public function list(ListRequest $request): JsonResponse { // collect transactions: @@ -115,4 +78,40 @@ class TransactionController extends Controller ->header('Content-Type', self::CONTENT_TYPE) ; } + + public function listByCount(ListByCountRequest $request): JsonResponse + { + // collect transactions: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUserGroup(auth()->user()->userGroup) + ->withAPIInformation() + ->setStartRow($request->getStartRow()) + ->setEndRow($request->getEndRow()) + ->setTypes($request->getTransactionTypes()) + ; + + $start = $this->parameters->get('start'); + $end = $this->parameters->get('end'); + if (null !== $start) { + $collector->setStart($start); + } + if (null !== $end) { + $collector->setEnd($end); + } + + $paginator = $collector->getPaginatedGroups(); + $params = $request->buildParams(); + $paginator->setPath( + sprintf( + '%s?%s', + route('api.v2.transactions.list'), + $params + ) + ); + + return response() + ->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer())) + ->header('Content-Type', self::CONTENT_TYPE); + } } diff --git a/app/Api/V2/Request/Model/Transaction/ListByCountRequest.php b/app/Api/V2/Request/Model/Transaction/ListByCountRequest.php index b0f4c719cb..dbd9083667 100644 --- a/app/Api/V2/Request/Model/Transaction/ListByCountRequest.php +++ b/app/Api/V2/Request/Model/Transaction/ListByCountRequest.php @@ -57,13 +57,6 @@ class ListByCountRequest extends FormRequest return http_build_query($array); } - public function getPage(): int - { - $page = $this->convertInteger('page'); - - return 0 === $page || $page > 65536 ? 1 : $page; - } - public function getStartRow(): int { $startRow = $this->convertInteger('start_row'); @@ -88,6 +81,13 @@ class ListByCountRequest extends FormRequest return $this->getCarbonDate('end'); } + public function getPage(): int + { + $page = $this->convertInteger('page'); + + return 0 === $page || $page > 65536 ? 1 : $page; + } + public function getTransactionTypes(): array { $type = (string)$this->get('type', 'default'); diff --git a/app/Api/V2/Request/Model/Transaction/StoreRequest.php b/app/Api/V2/Request/Model/Transaction/StoreRequest.php index 38a811a64c..872edca91e 100644 --- a/app/Api/V2/Request/Model/Transaction/StoreRequest.php +++ b/app/Api/V2/Request/Model/Transaction/StoreRequest.php @@ -78,145 +78,6 @@ class StoreRequest extends FormRequest ]; } - /** - * The rules that the incoming request must be matched against. - */ - public function rules(): array - { - app('log')->debug('V2: Collect rules of TransactionStoreRequest'); - - // at this point the userGroup can't be NULL because the - // authorize() method will complain. Loudly. - /** @var UserGroup $userGroup */ - $userGroup = $this->getUserGroup(); - - return [ - // basic fields for group: - 'group_title' => 'min:1|max:1000|nullable', - 'error_if_duplicate_hash' => [new IsBoolean()], - 'apply_rules' => [new IsBoolean()], - - // transaction rules (in array for splits): - 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', - 'transactions.*.date' => ['required', new IsDateOrTime()], - 'transactions.*.order' => 'numeric|min:0', - - // currency info - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - - // amount - 'transactions.*.amount' => ['required', new IsValidPositiveAmount()], - 'transactions.*.foreign_amount' => ['nullable', new IsValidPositiveAmount()], - - // description - 'transactions.*.description' => 'nullable|min:1|max:1000', - - // source of transaction - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)], - 'transactions.*.source_name' => 'min:1|max:255|nullable', - 'transactions.*.source_iban' => 'min:1|max:255|nullable|iban', - 'transactions.*.source_number' => 'min:1|max:255|nullable', - 'transactions.*.source_bic' => 'min:1|max:255|nullable|bic', - - // destination of transaction - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)], - 'transactions.*.destination_name' => 'min:1|max:255|nullable', - 'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban', - 'transactions.*.destination_number' => 'min:1|max:255|nullable', - 'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic', - - // budget, category, bill and piggy - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUserGroup($userGroup)], - 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUserGroup($userGroup), 'nullable'], - 'transactions.*.category_name' => 'min:1|max:255|nullable', - 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUserGroup($userGroup)], - 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], - 'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUserGroup($userGroup)], - 'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], - - // other interesting fields - 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1|max:32768|nullable', - 'transactions.*.tags' => 'min:0|max:255', - 'transactions.*.tags.*' => 'min:0|max:255', - - // meta info fields - 'transactions.*.internal_reference' => 'min:1|max:255|nullable', - 'transactions.*.external_id' => 'min:1|max:255|nullable', - 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', - 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', - 'transactions.*.external_url' => 'min:1|max:255|nullable|url', - - // SEPA fields: - 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', - 'transactions.*.sepa_db' => 'min:1|max:255|nullable', - 'transactions.*.sepa_country' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', - 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', - - // dates - 'transactions.*.interest_date' => 'date|nullable', - 'transactions.*.book_date' => 'date|nullable', - 'transactions.*.process_date' => 'date|nullable', - 'transactions.*.due_date' => 'date|nullable', - 'transactions.*.payment_date' => 'date|nullable', - 'transactions.*.invoice_date' => 'date|nullable', - - // TODO include location and ability to process it. - ]; - } - - /** - * Configure the validator instance. - */ - public function withValidator(Validator $validator): void - { - /** @var User $user */ - $user = auth()->user(); - - /** @var UserGroup $userGroup */ - $userGroup = $this->getUserGroup(); - $validator->after( - function (Validator $validator) use ($user, $userGroup): void { - // must be valid array. - $this->validateTransactionArray($validator); // does not need group validation. - - // must submit at least one transaction. - app('log')->debug('Now going to validateOneTransaction'); - $this->validateOneTransaction($validator); // does not need group validation. - app('log')->debug('Now done with validateOneTransaction'); - - // all journals must have a description - $this->validateDescriptions($validator); // does not need group validation. - - // all transaction types must be equal: - $this->validateTransactionTypes($validator); // does not need group validation. - - // validate foreign currency info - $this->validateForeignCurrencyInformation($validator); // does not need group validation. - - // validate all account info - $this->validateAccountInformation($validator, $user, $userGroup); - - // validate source/destination is equal, depending on the transaction journal type. - $this->validateEqualAccounts($validator); - - // the group must have a description if > 1 journal. - $this->validateGroupDescription($validator); - } - ); - if ($validator->fails()) { - Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); - } - } - /** * Get transaction data. */ @@ -313,4 +174,143 @@ class StoreRequest extends FormRequest return $return; } + + /** + * The rules that the incoming request must be matched against. + */ + public function rules(): array + { + app('log')->debug('V2: Collect rules of TransactionStoreRequest'); + + // at this point the userGroup can't be NULL because the + // authorize() method will complain. Loudly. + /** @var UserGroup $userGroup */ + $userGroup = $this->getUserGroup(); + + return [ + // basic fields for group: + 'group_title' => 'min:1|max:1000|nullable', + 'error_if_duplicate_hash' => [new IsBoolean()], + 'apply_rules' => [new IsBoolean()], + + // transaction rules (in array for splits): + 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => ['required', new IsDateOrTime()], + 'transactions.*.order' => 'numeric|min:0', + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + + // amount + 'transactions.*.amount' => ['required', new IsValidPositiveAmount()], + 'transactions.*.foreign_amount' => ['nullable', new IsValidPositiveAmount()], + + // description + 'transactions.*.description' => 'nullable|min:1|max:1000', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)], + 'transactions.*.source_name' => 'min:1|max:255|nullable', + 'transactions.*.source_iban' => 'min:1|max:255|nullable|iban', + 'transactions.*.source_number' => 'min:1|max:255|nullable', + 'transactions.*.source_bic' => 'min:1|max:255|nullable|bic', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)], + 'transactions.*.destination_name' => 'min:1|max:255|nullable', + 'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban', + 'transactions.*.destination_number' => 'min:1|max:255|nullable', + 'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUserGroup($userGroup)], + 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUserGroup($userGroup), 'nullable'], + 'transactions.*.category_name' => 'min:1|max:255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUserGroup($userGroup)], + 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], + 'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUserGroup($userGroup)], + 'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean()], + 'transactions.*.notes' => 'min:1|max:32768|nullable', + 'transactions.*.tags' => 'min:0|max:255', + 'transactions.*.tags.*' => 'min:0|max:255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1|max:255|nullable', + 'transactions.*.external_id' => 'min:1|max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', + 'transactions.*.external_url' => 'min:1|max:255|nullable|url', + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', + 'transactions.*.sepa_db' => 'min:1|max:255|nullable', + 'transactions.*.sepa_country' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + + // TODO include location and ability to process it. + ]; + } + + /** + * Configure the validator instance. + */ + public function withValidator(Validator $validator): void + { + /** @var User $user */ + $user = auth()->user(); + + /** @var UserGroup $userGroup */ + $userGroup = $this->getUserGroup(); + $validator->after( + function (Validator $validator) use ($user, $userGroup): void { + // must be valid array. + $this->validateTransactionArray($validator); // does not need group validation. + + // must submit at least one transaction. + app('log')->debug('Now going to validateOneTransaction'); + $this->validateOneTransaction($validator); // does not need group validation. + app('log')->debug('Now done with validateOneTransaction'); + + // all journals must have a description + $this->validateDescriptions($validator); // does not need group validation. + + // all transaction types must be equal: + $this->validateTransactionTypes($validator); // does not need group validation. + + // validate foreign currency info + $this->validateForeignCurrencyInformation($validator); // does not need group validation. + + // validate all account info + $this->validateAccountInformation($validator, $user, $userGroup); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccounts($validator); + + // the group must have a description if > 1 journal. + $this->validateGroupDescription($validator); + } + ); + if ($validator->fails()) { + Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); + } + } } diff --git a/app/Api/V2/Request/Model/Transaction/UpdateRequest.php b/app/Api/V2/Request/Model/Transaction/UpdateRequest.php index d33b3ad83e..b5a10e4e97 100644 --- a/app/Api/V2/Request/Model/Transaction/UpdateRequest.php +++ b/app/Api/V2/Request/Model/Transaction/UpdateRequest.php @@ -91,127 +91,6 @@ class UpdateRequest extends Request return $data; } - /** - * The rules that the incoming request must be matched against. - */ - public function rules(): array - { - app('log')->debug(sprintf('Now in %s', __METHOD__)); - $validProtocols = config('firefly.valid_url_protocols'); - - return [ - // basic fields for group: - 'group_title' => 'min:1|max:1000|nullable', - 'apply_rules' => [new IsBoolean()], - - // transaction rules (in array for splits): - 'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation', - 'transactions.*.date' => [new IsDateOrTime()], - 'transactions.*.order' => 'numeric|min:0', - - // group id: - 'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()], - - // currency info - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', - 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', - - // amount - 'transactions.*.amount' => ['nullable', new IsValidPositiveAmount()], - 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], - - // description - 'transactions.*.description' => 'nullable|min:1|max:1000', - - // source of transaction - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.source_name' => 'min:1|max:255|nullable', - - // destination of transaction - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], - 'transactions.*.destination_name' => 'min:1|max:255|nullable', - - // budget, category, bill and piggy - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'], - 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], - 'transactions.*.category_name' => 'min:1|max:255|nullable', - 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], - 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], - - // other interesting fields - 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1|max:32768|nullable', - 'transactions.*.tags' => 'min:0|max:255|nullable', - 'transactions.*.tags.*' => 'min:0|max:255', - - // meta info fields - 'transactions.*.internal_reference' => 'min:1|max:255|nullable', - 'transactions.*.external_id' => 'min:1|max:255|nullable', - 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', - 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', - 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), - - // SEPA fields: - 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', - 'transactions.*.sepa_db' => 'min:1|max:255|nullable', - 'transactions.*.sepa_country' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', - 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', - 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', - - // dates - 'transactions.*.interest_date' => 'date|nullable', - 'transactions.*.book_date' => 'date|nullable', - 'transactions.*.process_date' => 'date|nullable', - 'transactions.*.due_date' => 'date|nullable', - 'transactions.*.payment_date' => 'date|nullable', - 'transactions.*.invoice_date' => 'date|nullable', - ]; - } - - /** - * Configure the validator instance. - */ - public function withValidator(Validator $validator): void - { - app('log')->debug('Now in withValidator'); - - /** @var TransactionGroup $transactionGroup */ - $transactionGroup = $this->route()->parameter('userGroupTransaction'); - $validator->after( - function (Validator $validator) use ($transactionGroup): void { - // if more than one, verify that there are journal ID's present. - $this->validateJournalIds($validator, $transactionGroup); - - // all transaction types must be equal: - $this->validateTransactionTypesForUpdate($validator); - - // user wants to update a reconciled transaction. - // source, destination, amount + foreign_amount cannot be changed - // and must be omitted from the request. - $this->preventUpdateReconciled($validator, $transactionGroup); - - // validate source/destination is equal, depending on the transaction journal type. - $this->validateEqualAccountsForUpdate($validator, $transactionGroup); - - // see method: - // $this->preventNoAccountInfo($validator, ); - - // validate that the currency fits the source and/or destination account. - // validate all account info - $this->validateAccountInformationUpdate($validator, $transactionGroup); - } - ); - if ($validator->fails()) { - Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); - } - } - /** * Get transaction data. * @@ -259,7 +138,7 @@ class UpdateRequest extends Request { foreach ($this->integerFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]); + $current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]); } } @@ -274,7 +153,7 @@ class UpdateRequest extends Request { foreach ($this->stringFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->clearString((string) $transaction[$fieldName]); + $current[$fieldName] = $this->clearString((string)$transaction[$fieldName]); } } @@ -289,7 +168,7 @@ class UpdateRequest extends Request { foreach ($this->textareaFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines + $current[$fieldName] = $this->clearStringKeepNewlines((string)$transaction[$fieldName]); // keep newlines } } @@ -305,8 +184,8 @@ class UpdateRequest extends Request foreach ($this->dateFields as $fieldName) { app('log')->debug(sprintf('Now at date field %s', $fieldName)); if (array_key_exists($fieldName, $transaction)) { - app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName])); - $current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]); + app('log')->debug(sprintf('New value: "%s"', (string)$transaction[$fieldName])); + $current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]); } } @@ -321,7 +200,7 @@ class UpdateRequest extends Request { foreach ($this->booleanFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]); + $current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]); } } @@ -356,11 +235,132 @@ class UpdateRequest extends Request $current[$fieldName] = sprintf('%.12f', $value); } if (!is_float($value)) { - $current[$fieldName] = (string) $value; + $current[$fieldName] = (string)$value; } } } return $current; } + + /** + * The rules that the incoming request must be matched against. + */ + public function rules(): array + { + app('log')->debug(sprintf('Now in %s', __METHOD__)); + $validProtocols = config('firefly.valid_url_protocols'); + + return [ + // basic fields for group: + 'group_title' => 'min:1|max:1000|nullable', + 'apply_rules' => [new IsBoolean()], + + // transaction rules (in array for splits): + 'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => [new IsDateOrTime()], + 'transactions.*.order' => 'numeric|min:0', + + // group id: + 'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()], + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', + 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', + 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', + + // amount + 'transactions.*.amount' => ['nullable', new IsValidPositiveAmount()], + 'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()], + + // description + 'transactions.*.description' => 'nullable|min:1|max:1000', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.source_name' => 'min:1|max:255|nullable', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], + 'transactions.*.destination_name' => 'min:1|max:255|nullable', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'], + 'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'], + 'transactions.*.category_name' => 'min:1|max:255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()], + 'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean()], + 'transactions.*.notes' => 'min:1|max:32768|nullable', + 'transactions.*.tags' => 'min:0|max:255|nullable', + 'transactions.*.tags.*' => 'min:0|max:255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1|max:255|nullable', + 'transactions.*.external_id' => 'min:1|max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1|max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable', + 'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols), + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable', + 'transactions.*.sepa_db' => 'min:1|max:255|nullable', + 'transactions.*.sepa_country' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1|max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1|max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + ]; + } + + /** + * Configure the validator instance. + */ + public function withValidator(Validator $validator): void + { + app('log')->debug('Now in withValidator'); + + /** @var TransactionGroup $transactionGroup */ + $transactionGroup = $this->route()->parameter('userGroupTransaction'); + $validator->after( + function (Validator $validator) use ($transactionGroup): void { + // if more than one, verify that there are journal ID's present. + $this->validateJournalIds($validator, $transactionGroup); + + // all transaction types must be equal: + $this->validateTransactionTypesForUpdate($validator); + + // user wants to update a reconciled transaction. + // source, destination, amount + foreign_amount cannot be changed + // and must be omitted from the request. + $this->preventUpdateReconciled($validator, $transactionGroup); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccountsForUpdate($validator, $transactionGroup); + + // see method: + // $this->preventNoAccountInfo($validator, ); + + // validate that the currency fits the source and/or destination account. + // validate all account info + $this->validateAccountInformationUpdate($validator, $transactionGroup); + } + ); + if ($validator->fails()) { + Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); + } + } } diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php index aa769e9dde..3ba5ea8515 100644 --- a/app/Console/Commands/Correction/DeleteEmptyGroups.php +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -47,7 +47,7 @@ class DeleteEmptyGroups extends Command { $groupIds = TransactionGroup::leftJoin('transaction_journals', 'transaction_groups.id', '=', 'transaction_journals.transaction_group_id') - ->whereNull('transaction_journals.id')->get(['transaction_groups.id'])->pluck('id')->toArray() + ->whereNull('transaction_journals.id')->get(['transaction_groups.id'])->pluck('id')->toArray() ; $total = count($groupIds); diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php index 7713cdab34..cc963e024f 100644 --- a/app/Console/Commands/Correction/FixAccountTypes.php +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -248,29 +248,14 @@ class FixAccountTypes extends Command } } - private function isLiability(string $destinationType): bool - { - return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType; - } - private function shouldBeTransfer(string $transactionType, string $sourceType, string $destinationType): bool { return TransactionType::TRANSFER === $transactionType && AccountType::ASSET === $sourceType && $this->isLiability($destinationType); } - private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool + private function isLiability(string $destinationType): bool { - return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType; - } - - private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool - { - return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType; - } - - private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool - { - return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType; + return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType; } private function makeTransfer(TransactionJournal $journal): void @@ -286,6 +271,11 @@ class FixAccountTypes extends Command $this->inspectJournal($journal); } + private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool + { + return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType; + } + private function makeDeposit(TransactionJournal $journal): void { // from a liability to an asset should be a deposit. @@ -299,6 +289,11 @@ class FixAccountTypes extends Command $this->inspectJournal($journal); } + private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool + { + return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType; + } + private function makeExpenseDestination(TransactionJournal $journal, Transaction $destination): void { // withdrawals with a revenue account as destination instead of an expense account. @@ -320,6 +315,11 @@ class FixAccountTypes extends Command $this->inspectJournal($journal); } + private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool + { + return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType; + } + private function makeRevenueSource(TransactionJournal $journal, Transaction $source): void { // deposits with an expense account as source instead of a revenue account. @@ -355,11 +355,6 @@ class FixAccountTypes extends Command return in_array($accountType, $validTypes, true); } - private function canCreateDestination(array $validDestinations): bool - { - return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true); - } - private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void { app('log')->debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value)); @@ -373,6 +368,11 @@ class FixAccountTypes extends Command $this->inspectJournal($journal); } + private function canCreateDestination(array $validDestinations): bool + { + return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true); + } + private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void { app('log')->debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value)); diff --git a/app/Console/Commands/Integrity/CreateGroupMemberships.php b/app/Console/Commands/Integrity/CreateGroupMemberships.php index e6ed94773d..0927f1b7cb 100644 --- a/app/Console/Commands/Integrity/CreateGroupMemberships.php +++ b/app/Console/Commands/Integrity/CreateGroupMemberships.php @@ -57,6 +57,19 @@ class CreateGroupMemberships extends Command return 0; } + /** + * @throws FireflyException + */ + private function createGroupMemberships(): void + { + $users = User::get(); + + /** @var User $user */ + foreach ($users as $user) { + self::createGroupMembership($user); + } + } + /** * TODO move to helper. * @@ -93,17 +106,4 @@ class CreateGroupMemberships extends Command $user->save(); } } - - /** - * @throws FireflyException - */ - private function createGroupMemberships(): void - { - $users = User::get(); - - /** @var User $user */ - foreach ($users as $user) { - self::createGroupMembership($user); - } - } } diff --git a/app/Console/Commands/System/ForceDecimalSize.php b/app/Console/Commands/System/ForceDecimalSize.php index 19cda1faa2..383f004554 100644 --- a/app/Console/Commands/System/ForceDecimalSize.php +++ b/app/Console/Commands/System/ForceDecimalSize.php @@ -74,19 +74,19 @@ class ForceDecimalSize extends Command 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'], - ]; + '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. diff --git a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php index 5b79a96f3c..207e45d1fa 100644 --- a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php +++ b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php @@ -71,7 +71,7 @@ class MigrateRecurrenceMeta extends Command { $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); if (null !== $configVar) { - return (bool) $configVar->data; + return (bool)$configVar->data; } return false; diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php index 5b7c49ebe1..e18ba489fd 100644 --- a/app/Console/Commands/Upgrade/MigrateToGroups.php +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -104,7 +104,7 @@ class MigrateToGroups extends Command { $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); if (null !== $configVar) { - return (bool) $configVar->data; + return (bool)$configVar->data; } return false; @@ -193,22 +193,6 @@ class MigrateToGroups extends Command ); } - private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction - { - $set = $journal->transactions->filter( - static function (Transaction $subject) use ($transaction) { - $amount = (float) $transaction->amount * -1 === (float) $subject->amount; // intentional float - $identifier = $transaction->identifier === $subject->identifier; - app('log')->debug(sprintf('Amount the same? %s', var_export($amount, true))); - app('log')->debug(sprintf('ID the same? %s', var_export($identifier, true))); - - return $amount && $identifier; - } - ); - - return $set->first(); - } - /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -299,6 +283,22 @@ class MigrateToGroups extends Command ]; } + private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction + { + $set = $journal->transactions->filter( + static function (Transaction $subject) use ($transaction) { + $amount = (float)$transaction->amount * -1 === (float)$subject->amount; // intentional float + $identifier = $transaction->identifier === $subject->identifier; + app('log')->debug(sprintf('Amount the same? %s', var_export($amount, true))); + app('log')->debug(sprintf('ID the same? %s', var_export($identifier, true))); + + return $amount && $identifier; + } + ); + + return $set->first(); + } + private function getTransactionBudget(Transaction $left, Transaction $right): ?int { app('log')->debug('Now in getTransactionBudget()'); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index d737656d97..f43d151f90 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -128,7 +128,7 @@ class Handler extends ExceptionHandler $errorCode = 500; $errorCode = $e instanceof MethodNotAllowedHttpException ? 405 : $errorCode; - $isDebug = (bool) config('app.debug', false); + $isDebug = (bool)config('app.debug', false); if ($isDebug) { app('log')->debug(sprintf('Return JSON %s with debug.', get_class($e))); @@ -185,7 +185,7 @@ class Handler extends ExceptionHandler */ public function report(\Throwable $e): void { - $doMailError = (bool) config('firefly.send_error_message'); + $doMailError = (bool)config('firefly.send_error_message'); if ($this->shouldntReportLocal($e) || !$doMailError) { parent::report($e); @@ -221,12 +221,22 @@ class Handler extends ExceptionHandler // create job that will mail. $ipAddress = request()->ip() ?? '0.0.0.0'; - $job = new MailError($userData, (string) config('firefly.site_owner'), $ipAddress, $data); + $job = new MailError($userData, (string)config('firefly.site_owner'), $ipAddress, $data); dispatch($job); parent::report($e); } + private function shouldntReportLocal(\Throwable $e): bool + { + return null !== Arr::first( + $this->dontReport, + static function ($type) use ($e) { + return $e instanceof $type; + } + ); + } + /** * Convert a validation exception into a response. * @@ -244,16 +254,6 @@ class Handler extends ExceptionHandler ; } - private function shouldntReportLocal(\Throwable $e): bool - { - return null !== Arr::first( - $this->dontReport, - static function ($type) use ($e) { - return $e instanceof $type; - } - ); - } - /** * Only return the redirectTo property from the exception if it is a valid URL. Return NULL otherwise. */ diff --git a/app/Exceptions/IntervalException.php b/app/Exceptions/IntervalException.php index 592eb5f858..c411b2dd1c 100644 --- a/app/Exceptions/IntervalException.php +++ b/app/Exceptions/IntervalException.php @@ -48,7 +48,7 @@ final class IntervalException extends \Exception Periodicity $periodicity, array $intervals, int $code = 0, - ?\Throwable $previous = null + ?\Throwable $previous = null ): self { $message = sprintf( 'The periodicity %s is unknown. Choose one of available periodicity: %s', diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index 2fec83f87b..faaaaa6c52 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -121,21 +121,6 @@ class AccountFactory return $return; } - public function find(string $accountName, string $accountType): ?Account - { - app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); - $type = AccountType::whereType($accountType)->first(); - - // @var Account|null - return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); - } - - public function setUser(User $user): void - { - $this->user = $user; - $this->accountRepository->setUser($user); - } - /** * @throws FireflyException */ @@ -169,6 +154,15 @@ class AccountFactory return $result; } + public function find(string $accountName, string $accountType): ?Account + { + app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); + $type = AccountType::whereType($accountType)->first(); + + // @var Account|null + return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); + } + /** * @throws FireflyException */ @@ -365,4 +359,10 @@ class AccountFactory $updateService->setUser($account->user); $updateService->update($account, ['order' => $order]); } + + public function setUser(User $user): void + { + $this->user = $user; + $this->accountRepository->setUser($user); + } } diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index c2ad81b6ab..fc9769ab0a 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -79,7 +79,7 @@ class RecurrenceFactory $firstDate = $data['recurrence']['first_date']; } if (array_key_exists('nr_of_repetitions', $data['recurrence'])) { - $repetitions = (int) $data['recurrence']['nr_of_repetitions']; + $repetitions = (int)$data['recurrence']['nr_of_repetitions']; } if (array_key_exists('repeat_until', $data['recurrence'])) { $repeatUntil = $data['recurrence']['repeat_until']; @@ -116,7 +116,7 @@ class RecurrenceFactory $recurrence->save(); if (array_key_exists('notes', $data['recurrence'])) { - $this->updateNote($recurrence, (string) $data['recurrence']['notes']); + $this->updateNote($recurrence, (string)$data['recurrence']['notes']); } $this->createRepetitions($recurrence, $data['repetitions'] ?? []); diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index f38452e259..15308c674f 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -72,64 +72,6 @@ class TransactionFactory return $this->create(app('steam')->negative($amount), $foreignAmount); } - /** - * Create transaction with positive amount (for destination accounts). - * - * @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); - } - - public function setAccount(Account $account): void - { - $this->account = $account; - } - - public function setAccountInformation(array $accountInformation): void - { - $this->accountInformation = $accountInformation; - } - - public function setCurrency(TransactionCurrency $currency): void - { - $this->currency = $currency; - } - - /** - * @param null|TransactionCurrency $foreignCurrency |null - */ - public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void - { - $this->foreignCurrency = $foreignCurrency; - } - - public function setJournal(TransactionJournal $journal): void - { - $this->journal = $journal; - } - - public function setReconciled(bool $reconciled): void - { - $this->reconciled = $reconciled; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function setUser(User $user): void - { - // empty function. - } - /** * @throws FireflyException */ @@ -224,4 +166,62 @@ class TransactionFactory $service = app(AccountUpdateService::class); $service->update($this->account, ['iban' => $this->accountInformation['iban']]); } + + /** + * Create transaction with positive amount (for destination accounts). + * + * @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); + } + + public function setAccount(Account $account): void + { + $this->account = $account; + } + + public function setAccountInformation(array $accountInformation): void + { + $this->accountInformation = $accountInformation; + } + + public function setCurrency(TransactionCurrency $currency): void + { + $this->currency = $currency; + } + + /** + * @param null|TransactionCurrency $foreignCurrency |null + */ + public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void + { + $this->foreignCurrency = $foreignCurrency; + } + + public function setJournal(TransactionJournal $journal): void + { + $this->journal = $journal; + } + + public function setReconciled(bool $reconciled): void + { + $this->reconciled = $reconciled; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setUser(User $user): void + { + // empty function. + } } diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 50bb61570d..bbe9ce33b1 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -141,49 +141,6 @@ class TransactionJournalFactory return $collection; } - /** - * Set the 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); - } - - public function setErrorOnHash(bool $errorOnHash): void - { - $this->errorOnHash = $errorOnHash; - if (true === $errorOnHash) { - app('log')->info('Will trigger duplication alert for this journal.'); - } - } - - 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')); - app('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'); - } - - app('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); - } - /** * TODO typeOverrule: the account validator may have another opinion on the transaction type. not sure what to do * with this. @@ -203,11 +160,11 @@ class TransactionJournalFactory $type = $this->typeRepository->findTransactionType(null, $row['type']); $carbon = $row['date'] ?? today(config('app.timezone')); $order = $row['order'] ?? 0; - $currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']); + $currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']); $foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']); - $bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']); + $bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']); $billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null; - $description = (string) $row['description']; + $description = (string)$row['description']; // Manipulate basic fields $carbon->setTimezone(config('app.timezone')); @@ -286,7 +243,7 @@ class TransactionJournalFactory $transactionFactory->setReconciled($row['reconciled'] ?? false); try { - $negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']); + $negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']); } catch (FireflyException $e) { app('log')->error(sprintf('Exception creating negative transaction: %s', $e->getMessage())); $this->forceDeleteOnError(new Collection([$journal])); @@ -305,7 +262,7 @@ class TransactionJournalFactory $transactionFactory->setReconciled($row['reconciled'] ?? false); try { - $transactionFactory->createPositive((string) $row['amount'], (string) $row['foreign_amount']); + $transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']); } catch (FireflyException $e) { app('log')->error(sprintf('Exception creating positive transaction: %s', $e->getMessage())); $this->forceTrDelete($negative); @@ -326,18 +283,6 @@ class TransactionJournalFactory return $journal; } - private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void - { - if (true === $data['store_location']) { - $location = new Location(); - $location->longitude = $data['longitude']; - $location->latitude = $data['latitude']; - $location->zoom_level = $data['zoom_level']; - $location->locatable()->associate($journal); - $location->save(); - } - } - private function hashArray(NullArrayObject $row): string { $dataRow = $row->getArrayCopy(); @@ -382,7 +327,7 @@ class TransactionJournalFactory app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash)); $journal = $result->transactionJournal()->withTrashed()->first(); $group = $journal?->transactionGroup()->withTrashed()->first(); - $groupId = (int) $group?->id; + $groupId = (int)$group?->id; throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $groupId)); } @@ -400,10 +345,10 @@ class TransactionJournalFactory // validate source account. $array = [ - 'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null, - 'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null, - 'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null, - 'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null, + 'id' => null !== $data['source_id'] ? (int)$data['source_id'] : null, + 'name' => null !== $data['source_name'] ? (string)$data['source_name'] : null, + 'iban' => null !== $data['source_iban'] ? (string)$data['source_iban'] : null, + 'number' => null !== $data['source_number'] ? (string)$data['source_number'] : null, ]; $validSource = $this->accountValidator->validateSource($array); @@ -415,10 +360,10 @@ class TransactionJournalFactory // validate destination account $array = [ - 'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null, - 'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null, - 'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null, - 'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null, + 'id' => null !== $data['destination_id'] ? (int)$data['destination_id'] : null, + 'name' => null !== $data['destination_name'] ? (string)$data['destination_name'] : null, + 'iban' => null !== $data['destination_iban'] ? (string)$data['destination_iban'] : null, + 'number' => null !== $data['destination_number'] ? (string)$data['destination_number'] : null, ]; $validDestination = $this->accountValidator->validateDestination($array); @@ -428,6 +373,21 @@ class TransactionJournalFactory } } + /** + * Set the 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); + } + private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array { app('log')->debug(sprintf('Now in %s', __METHOD__)); @@ -550,7 +510,7 @@ class TransactionJournalFactory { app('log')->debug('Will now store piggy event.'); - $piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']); + $piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']); if (null !== $piggyBank) { $this->piggyEventFactory->create($journal, $piggyBank); @@ -567,4 +527,44 @@ class TransactionJournalFactory $this->storeMeta($journal, $transaction, $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')); + app('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'); + } + + app('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); + } + + private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void + { + if (true === $data['store_location']) { + $location = new Location(); + $location->longitude = $data['longitude']; + $location->latitude = $data['latitude']; + $location->zoom_level = $data['zoom_level']; + $location->locatable()->associate($journal); + $location->save(); + } + } + + public function setErrorOnHash(bool $errorOnHash): void + { + $this->errorOnHash = $errorOnHash; + if (true === $errorOnHash) { + app('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 5562b01f2d..49487a84b5 100644 --- a/app/Generator/Report/Account/MonthReportGenerator.php +++ b/app/Generator/Report/Account/MonthReportGenerator.php @@ -67,6 +67,14 @@ class MonthReportGenerator implements ReportGeneratorInterface return $result; } + /** + * Return the preferred period. + */ + protected function preferredPeriod(): string + { + return 'day'; + } + /** * Set accounts. */ @@ -130,12 +138,4 @@ class MonthReportGenerator implements ReportGeneratorInterface { return $this; } - - /** - * Return the preferred period. - */ - protected function preferredPeriod(): string - { - return 'day'; - } } diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 505535c403..7ec1cad458 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -125,26 +125,6 @@ class MonthReportGenerator implements ReportGeneratorInterface return $this; } - /** - * Set the involved budgets. - */ - public function setBudgets(Collection $budgets): ReportGeneratorInterface - { - $this->budgets = $budgets; - - return $this; - } - - /** - * Set the involved accounts. - */ - public function setAccounts(Collection $accounts): ReportGeneratorInterface - { - $this->accounts = $accounts; - - return $this; - } - /** * Get the expenses. */ @@ -170,4 +150,24 @@ class MonthReportGenerator implements ReportGeneratorInterface return $journals; } + + /** + * Set the involved budgets. + */ + public function setBudgets(Collection $budgets): ReportGeneratorInterface + { + $this->budgets = $budgets; + + return $this; + } + + /** + * Set the involved accounts. + */ + 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 e931057f43..b840358ccf 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -124,26 +124,6 @@ class MonthReportGenerator implements ReportGeneratorInterface return $this; } - /** - * Set the categories involved in this report. - */ - public function setCategories(Collection $categories): ReportGeneratorInterface - { - $this->categories = $categories; - - return $this; - } - - /** - * Set the involved accounts. - */ - public function setAccounts(Collection $accounts): ReportGeneratorInterface - { - $this->accounts = $accounts; - - return $this; - } - /** * Get the expenses for this report. */ @@ -168,6 +148,26 @@ class MonthReportGenerator implements ReportGeneratorInterface return $transactions; } + /** + * Set the categories involved in this report. + */ + public function setCategories(Collection $categories): ReportGeneratorInterface + { + $this->categories = $categories; + + return $this; + } + + /** + * Set the involved accounts. + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + /** * Get the income for this report. */ diff --git a/app/Handlers/Events/Model/BudgetLimitHandler.php b/app/Handlers/Events/Model/BudgetLimitHandler.php index 373584cbea..0211e9e78c 100644 --- a/app/Handlers/Events/Model/BudgetLimitHandler.php +++ b/app/Handlers/Events/Model/BudgetLimitHandler.php @@ -49,20 +49,6 @@ class BudgetLimitHandler $this->updateAvailableBudget($event->budgetLimit); } - public function deleted(Deleted $event): void - { - app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id)); - $budgetLimit = $event->budgetLimit; - $budgetLimit->id = 0; - $this->updateAvailableBudget($event->budgetLimit); - } - - public function updated(Updated $event): void - { - app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); - $this->updateAvailableBudget($event->budgetLimit); - } - private function updateAvailableBudget(BudgetLimit $budgetLimit): void { app('log')->debug(sprintf('Now in updateAvailableBudget(#%d)', $budgetLimit->id)); @@ -248,4 +234,18 @@ class BudgetLimitHandler return $amount; } + + public function deleted(Deleted $event): void + { + app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id)); + $budgetLimit = $event->budgetLimit; + $budgetLimit->id = 0; + $this->updateAvailableBudget($event->budgetLimit); + } + + public function updated(Updated $event): void + { + app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); + $this->updateAvailableBudget($event->budgetLimit); + } } diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index a7c0bad1ee..8f414e6b97 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -220,7 +220,7 @@ class UserEventHandler public function sendAdminRegistrationNotification(RegisteredUser $event): void { - $sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data; + $sendMail = (bool)app('fireflyconfig')->get('notification_admin_new_reg', true)->data; if ($sendMail) { /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); @@ -285,7 +285,7 @@ class UserEventHandler $oldEmail = $event->oldEmail; $user = $event->user; $token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid'); - $hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail)); + $hashed = hash('sha256', sprintf('%s%s', (string)config('app.key'), $oldEmail)); $url = route('profile.undo-email-change', [$token->data, $hashed]); try { @@ -347,7 +347,7 @@ class UserEventHandler */ public function sendRegistrationMail(RegisteredUser $event): void { - $sendMail = (bool) app('fireflyconfig')->get('notification_user_new_reg', true)->data; + $sendMail = (bool)app('fireflyconfig')->get('notification_user_new_reg', true)->data; if ($sendMail) { try { Notification::send($event->user, new UserRegistrationNotification()); diff --git a/app/Helpers/Collector/Extensions/AttachmentCollection.php b/app/Helpers/Collector/Extensions/AttachmentCollection.php index 517d865367..6140a20ba4 100644 --- a/app/Helpers/Collector/Extensions/AttachmentCollection.php +++ b/app/Helpers/Collector/Extensions/AttachmentCollection.php @@ -81,6 +81,27 @@ trait AttachmentCollection return $this; } + /** + * Join table to get attachment information. + */ + private function joinAttachmentTables(): void + { + if (false === $this->hasJoinedAttTables) { + // join some extra tables: + $this->hasJoinedAttTables = true; + $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') + ->where( + static function (EloquentBuilder $q1): void { // @phpstan-ignore-line + $q1->where('attachments.attachable_type', TransactionJournal::class); + $q1->where('attachments.uploaded', true); + $q1->whereNull('attachments.deleted_at'); + $q1->orWhereNull('attachments.attachable_type'); + } + ) + ; + } + } + public function withAttachmentInformation(): GroupCollectorInterface { $this->fields[] = 'attachments.id as attachment_id'; @@ -511,25 +532,4 @@ trait AttachmentCollection return $this; } - - /** - * Join table to get attachment information. - */ - private function joinAttachmentTables(): void - { - if (false === $this->hasJoinedAttTables) { - // join some extra tables: - $this->hasJoinedAttTables = true; - $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') - ->where( - static function (EloquentBuilder $q1): void { // @phpstan-ignore-line - $q1->where('attachments.attachable_type', TransactionJournal::class); - $q1->where('attachments.uploaded', true); - $q1->whereNull('attachments.deleted_at'); - $q1->orWhereNull('attachments.attachable_type'); - } - ) - ; - } - } } diff --git a/app/Helpers/Collector/Extensions/CollectorProperties.php b/app/Helpers/Collector/Extensions/CollectorProperties.php index b0ba86a713..a9a71a9e43 100644 --- a/app/Helpers/Collector/Extensions/CollectorProperties.php +++ b/app/Helpers/Collector/Extensions/CollectorProperties.php @@ -34,6 +34,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; trait CollectorProperties { public const string TEST = 'Test'; + private ?int $endRow; private bool $expandGroupSearch; private array $fields; private bool $hasAccountInfo; @@ -49,10 +50,8 @@ trait CollectorProperties private ?int $page; private array $postFilters; private HasMany $query; - private array $stringFields; - private ?int $startRow; - private ?int $endRow; + private array $stringFields; /* * This array is used to collect ALL tags the user may search for (using 'setTags'). * This way the user can call 'setTags' multiple times and get a joined result. diff --git a/app/Helpers/Collector/Extensions/MetaCollection.php b/app/Helpers/Collector/Extensions/MetaCollection.php index b86a697153..305d2a88f4 100644 --- a/app/Helpers/Collector/Extensions/MetaCollection.php +++ b/app/Helpers/Collector/Extensions/MetaCollection.php @@ -171,6 +171,19 @@ 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'; + } + } + public function excludeExternalUrl(string $url): GroupCollectorInterface { $this->joinMetaDataTables(); @@ -369,6 +382,19 @@ trait MetaCollection 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'); + } + } + public function internalReferenceContains(string $internalReference): GroupCollectorInterface { $internalReference = (string)json_encode($internalReference); @@ -539,6 +565,63 @@ trait MetaCollection return $this; } + /** + * Limit results to a SPECIFIC set of tags. + */ + public function setAllTags(Collection $tags): GroupCollectorInterface + { + Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count())); + $this->withTagInformation(); + $this->query->whereNotNull('tag_transaction_journal.tag_id'); + + // this method adds a "postFilter" to the collector. + $list = $tags->pluck('tag')->toArray(); + $list = array_map('strtolower', $list); + $filter = static function (array $object) use ($list): bool|array { + $includedJournals = []; + $return = $object; + unset($return['transactions']); + $return['transactions'] = []; + Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list))); + $expectedTagCount = count($list); + $foundTagCount = 0; + foreach ($object['transactions'] as $transaction) { + $transactionTagCount = count($transaction['tags']); + app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount)); + if ($transactionTagCount < $expectedTagCount) { + app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount)); + + return false; + } + foreach ($transaction['tags'] as $tag) { + Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list); + if (in_array(strtolower($tag['name']), $list, true)) { + app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name'])); + ++$foundTagCount; + $journalId = $transaction['transaction_journal_id']; + // #8377 prevent adding a transaction twice when multiple tag searches find this transaction + if (!in_array($journalId, $includedJournals, true)) { + $includedJournals[] = $journalId; + $return['transactions'][] = $transaction; + } + } + } + } + Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount)); + + // found at least the expected tags. + $result = $foundTagCount >= $expectedTagCount; + if (true === $result) { + return $return; + } + + return false; + }; + $this->postFilters[] = $filter; + + return $this; + } + /** * Limit the search to a specific bill. */ @@ -669,63 +752,6 @@ trait MetaCollection return $this; } - /** - * Limit results to a SPECIFIC set of tags. - */ - public function setAllTags(Collection $tags): GroupCollectorInterface - { - Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count())); - $this->withTagInformation(); - $this->query->whereNotNull('tag_transaction_journal.tag_id'); - - // this method adds a "postFilter" to the collector. - $list = $tags->pluck('tag')->toArray(); - $list = array_map('strtolower', $list); - $filter = static function (array $object) use ($list): bool|array { - $includedJournals = []; - $return = $object; - unset($return['transactions']); - $return['transactions'] = []; - Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list))); - $expectedTagCount = count($list); - $foundTagCount = 0; - foreach ($object['transactions'] as $transaction) { - $transactionTagCount = count($transaction['tags']); - app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount)); - if ($transactionTagCount < $expectedTagCount) { - app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount)); - - return false; - } - foreach ($transaction['tags'] as $tag) { - Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list); - if (in_array(strtolower($tag['name']), $list, true)) { - app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name'])); - ++$foundTagCount; - $journalId = $transaction['transaction_journal_id']; - // #8377 prevent adding a transaction twice when multiple tag searches find this transaction - if (!in_array($journalId, $includedJournals, true)) { - $includedJournals[] = $journalId; - $return['transactions'][] = $transaction; - } - } - } - } - Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount)); - - // found at least the expected tags. - $result = $foundTagCount >= $expectedTagCount; - if (true === $result) { - return $return; - } - - return false; - }; - $this->postFilters[] = $filter; - - return $this; - } - /** * Limit results to any of the tags in the list. */ @@ -938,30 +964,4 @@ 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/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index a3279019f6..fff221f886 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -25,6 +25,7 @@ namespace FireflyIII\Helpers\Collector; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\Extensions\AccountCollection; use FireflyIII\Helpers\Collector\Extensions\AmountCollection; @@ -483,226 +484,6 @@ class GroupCollector implements GroupCollectorInterface return $collection; } - /** - * Same as getGroups but everything is in a paginator. - */ - public function getPaginatedGroups(): LengthAwarePaginator - { - $set = $this->getGroups(); - if (0 === $this->limit) { - $this->setLimit(50); - } - if (null !== $this->startRow && null !== $this->endRow) { - $total = $this->endRow - $this->startRow; - - return new LengthAwarePaginator($set, $this->total, $total, 1); - } - - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); - } - - /** - * Limit the number of returned entries. - */ - public function setLimit(int $limit): GroupCollectorInterface - { - $this->limit = $limit; - // app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); - - return $this; - } - - public function isNotReconciled(): GroupCollectorInterface - { - $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); - - return $this; - } - - 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. - */ - public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where( - static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line - $q->where('source.transaction_currency_id', $currency->id); - $q->orWhere('source.foreign_currency_id', $currency->id); - } - ); - - return $this; - } - - public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface - { - $this->expandGroupSearch = $expandGroupSearch; - - return $this; - } - - 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. - */ - public function setIds(array $groupIds): GroupCollectorInterface - { - $this->query->whereIn('transaction_groups.id', $groupIds); - - return $this; - } - - /** - * Limit the result to a set of specific journals. - */ - public function setJournalIds(array $journalIds): GroupCollectorInterface - { - if (0 !== count($journalIds)) { - // make all integers. - $integerIDs = array_map('intval', $journalIds); - Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs))); - - $this->query->whereIn('transaction_journals.id', $integerIDs); - } - - return $this; - } - - /** - * Set the page to get. - */ - 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. - */ - public function setSearchWords(array $array): GroupCollectorInterface - { - if (0 === count($array)) { - return $this; - } - $this->query->where( - static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line - $q->where( - static function (EloquentBuilder $q1) use ($array): void { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q1->where('transaction_journals.description', 'LIKE', $keyword); - } - } - ); - $q->orWhere( - static function (EloquentBuilder $q2) use ($array): void { - 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. - */ - public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface - { - $this->query->where('transaction_groups.id', $transactionGroup->id); - - return $this; - } - - /** - * Limit the included transaction types. - */ - public function setTypes(array $types): GroupCollectorInterface - { - $this->query->whereIn('transaction_types.type', $types); - - return $this; - } - - /** - * Set the user object and start the query. - */ - public function setUser(User $user): GroupCollectorInterface - { - if (null === $this->user) { - $this->user = $user; - $this->startQuery(); - } - - return $this; - } - - /** - * Set the user object and start the query. - */ - public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface - { - if (null === $this->userGroup) { - $this->userGroup = $userGroup; - $this->startQueryForGroup(); - } - - return $this; - } - - /** - * Automatically include all stuff required to make API calls work. - */ - 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; - } - - public function setEndRow(int $endRow): self - { - $this->endRow = $endRow; - - return $this; - } - - public function setStartRow(int $startRow): self - { - $this->startRow = $startRow; - - return $this; - } - private function getCollectedGroupIds(): array { return $this->query->get(['transaction_journals.transaction_group_id'])->pluck('transaction_group_id')->toArray(); @@ -998,6 +779,195 @@ class GroupCollector implements GroupCollectorInterface return $currentCollection; } + /** + * Same as getGroups but everything is in a paginator. + */ + public function getPaginatedGroups(): LengthAwarePaginator + { + $set = $this->getGroups(); + if (0 === $this->limit) { + $this->setLimit(50); + } + if (null !== $this->startRow && null !== $this->endRow) { + $total = $this->endRow - $this->startRow; + + return new LengthAwarePaginator($set, $this->total, $total, 1); + } + + return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + } + + /** + * Limit the number of returned entries. + */ + public function setLimit(int $limit): GroupCollectorInterface + { + $this->limit = $limit; + // app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); + + return $this; + } + + public function isNotReconciled(): GroupCollectorInterface + { + $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); + + return $this; + } + + 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. + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where( + static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line + $q->where('source.transaction_currency_id', $currency->id); + $q->orWhere('source.foreign_currency_id', $currency->id); + } + ); + + return $this; + } + + public function setEndRow(int $endRow): self + { + $this->endRow = $endRow; + + return $this; + } + + public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface + { + $this->expandGroupSearch = $expandGroupSearch; + + return $this; + } + + 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. + */ + public function setIds(array $groupIds): GroupCollectorInterface + { + $this->query->whereIn('transaction_groups.id', $groupIds); + + return $this; + } + + /** + * Limit the result to a set of specific journals. + */ + public function setJournalIds(array $journalIds): GroupCollectorInterface + { + if (0 !== count($journalIds)) { + // make all integers. + $integerIDs = array_map('intval', $journalIds); + Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs))); + + $this->query->whereIn('transaction_journals.id', $integerIDs); + } + + return $this; + } + + /** + * Set the page to get. + */ + 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. + */ + public function setSearchWords(array $array): GroupCollectorInterface + { + if (0 === count($array)) { + return $this; + } + $this->query->where( + static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line + $q->where( + static function (EloquentBuilder $q1) use ($array): void { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q1->where('transaction_journals.description', 'LIKE', $keyword); + } + } + ); + $q->orWhere( + static function (EloquentBuilder $q2) use ($array): void { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q2->where('transaction_groups.title', 'LIKE', $keyword); + } + } + ); + } + ); + + return $this; + } + + public function setStartRow(int $startRow): self + { + $this->startRow = $startRow; + + return $this; + } + + /** + * Limit the search to one specific transaction group. + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface + { + $this->query->where('transaction_groups.id', $transactionGroup->id); + + return $this; + } + + /** + * Limit the included transaction types. + */ + public function setTypes(array $types): GroupCollectorInterface + { + $this->query->whereIn('transaction_types.type', $types); + + return $this; + } + + /** + * Set the user object and start the query. + */ + public function setUser(User $user): GroupCollectorInterface + { + if (null === $this->user) { + $this->user = $user; + $this->startQuery(); + } + + return $this; + } + /** * Build the query. */ @@ -1044,6 +1014,19 @@ class GroupCollector implements GroupCollectorInterface ; } + /** + * Set the user object and start the query. + */ + public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface + { + if (null === $this->userGroup) { + $this->userGroup = $userGroup; + $this->startQueryForGroup(); + } + + return $this; + } + /** * Build the query. */ @@ -1087,4 +1070,21 @@ class GroupCollector implements GroupCollectorInterface ->orderBy('source.amount', 'DESC') ; } + + /** + * Automatically include all stuff required to make API calls work. + */ + 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/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index 75639d6a4e..d80ee79f2c 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -401,6 +401,11 @@ interface GroupCollectorInterface */ public function setAfter(Carbon $date): self; + /** + * Limit results to a SPECIFIC set of tags. + */ + public function setAllTags(Collection $tags): self; + /** * Collect transactions before a specific date. */ @@ -461,6 +466,11 @@ interface GroupCollectorInterface */ public function setEnd(Carbon $end): self; + /** + * Set the page to get. + */ + public function setEndRow(int $endRow): self; + public function setExpandGroupSearch(bool $expandGroupSearch): self; /** @@ -526,16 +536,6 @@ interface GroupCollectorInterface */ public function setPage(int $page): self; - /** - * Set the page to get. - */ - public function setStartRow(int $startRow): self; - - /** - * Set the page to get. - */ - public function setEndRow(int $endRow): self; - /** * Set the start and end time of the results to return. */ @@ -563,6 +563,11 @@ interface GroupCollectorInterface */ public function setStart(Carbon $start): self; + /** + * Set the page to get. + */ + public function setStartRow(int $startRow): self; + /** * Limit results to a specific tag. */ @@ -573,11 +578,6 @@ interface GroupCollectorInterface */ public function setTags(Collection $tags): self; - /** - * Limit results to a SPECIFIC set of tags. - */ - public function setAllTags(Collection $tags): self; - /** * Limit the search to one specific transaction group. */ diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php index e66ca81732..78123dbd85 100644 --- a/app/Helpers/Report/NetWorth.php +++ b/app/Helpers/Report/NetWorth.php @@ -121,12 +121,12 @@ class NetWorth implements NetWorthInterface $netWorth[$currencyCode] ??= [ 'balance' => '0', 'native_balance' => '0', - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_code' => $currency->code, 'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => (string) $default->id, + 'native_currency_id' => (string)$default->id, 'native_currency_code' => $default->code, 'native_currency_name' => $default->name, 'native_currency_symbol' => $default->symbol, @@ -144,6 +144,15 @@ class NetWorth implements NetWorthInterface return $netWorth; } + private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface + { + if (null === $this->userGroup) { + return $this->accountRepository; + } + + return $this->adminAccountRepository; + } + public function setUser(null|Authenticatable|User $user): void { if (!$user instanceof User) { @@ -189,7 +198,7 @@ class NetWorth implements NetWorthInterface } $return[$currency->id] ??= [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => $currency->name, 'symbol' => $currency->symbol, 'code' => $currency->code, @@ -202,15 +211,6 @@ class NetWorth implements NetWorthInterface return $return; } - private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface - { - if (null === $this->userGroup) { - return $this->accountRepository; - } - - return $this->adminAccountRepository; - } - private function getAccounts(): Collection { $accounts = $this->getRepository()->getAccountsByType( @@ -220,7 +220,7 @@ class NetWorth implements NetWorthInterface /** @var Account $account */ foreach ($accounts as $account) { - if (1 === (int) $this->getRepository()->getMetaValue($account, 'include_net_worth')) { + if (1 === (int)$this->getRepository()->getMetaValue($account, 'include_net_worth')) { $filtered->push($account); } } diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php index b1ebda2f53..82e8b110e4 100644 --- a/app/Http/Controllers/Account/CreateController.php +++ b/app/Http/Controllers/Account/CreateController.php @@ -58,7 +58,7 @@ class CreateController extends Controller $this->middleware( function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-credit-card'); - app('view')->share('title', (string) trans('firefly.accounts')); + app('view')->share('title', (string)trans('firefly.accounts')); $this->repository = app(AccountRepositoryInterface::class); $this->attachments = app(AttachmentHelperInterface::class); @@ -77,7 +77,7 @@ class CreateController extends Controller { $defaultCurrency = app('amount')->getDefaultCurrency(); $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); - $subTitle = (string) trans(sprintf('firefly.make_new_%s_account', $objectType)); + $subTitle = (string)trans(sprintf('firefly.make_new_%s_account', $objectType)); $roles = $this->getRoles(); $liabilityTypes = $this->getLiabilityTypes(); $hasOldInput = null !== $request->old('_token'); @@ -96,9 +96,9 @@ class CreateController extends Controller // interest calculation periods: $interestPeriods = [ - 'daily' => (string) trans('firefly.interest_calc_daily'), - 'monthly' => (string) trans('firefly.interest_calc_monthly'), - 'yearly' => (string) trans('firefly.interest_calc_yearly'), + 'daily' => (string)trans('firefly.interest_calc_daily'), + 'monthly' => (string)trans('firefly.interest_calc_monthly'), + 'yearly' => (string)trans('firefly.interest_calc_yearly'), ]; // pre fill some data @@ -106,7 +106,7 @@ class CreateController extends Controller 'preFilled', [ 'currency_id' => $defaultCurrency->id, - 'include_net_worth' => $hasOldInput ? (bool) $request->old('include_net_worth') : true, + 'include_net_worth' => $hasOldInput ? (bool)$request->old('include_net_worth') : true, ] ); // issue #8321 @@ -139,7 +139,7 @@ class CreateController extends Controller { $data = $request->getAccountData(); $account = $this->repository->store($data); - $request->session()->flash('success', (string) trans('firefly.stored_new_account', ['name' => $account->name])); + $request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); app('preferences')->mark(); Log::channel('audit')->info('Stored new account.', $data); @@ -162,7 +162,7 @@ class CreateController extends Controller } if (null !== $files && auth()->user()->hasRole('demo')) { Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); - session()->flash('info', (string) trans('firefly.no_att_demo_user')); + session()->flash('info', (string)trans('firefly.no_att_demo_user')); } if (count($this->attachments->getMessages()->get('attachments')) > 0) { @@ -171,7 +171,7 @@ class CreateController extends Controller // redirect to previous URL. $redirect = redirect($this->getPreviousUrl('accounts.create.url')); - if (1 === (int) $request->get('create_another')) { + if (1 === (int)$request->get('create_another')) { // set value so create routine will not overwrite URL: $request->session()->put('accounts.create.fromStore', true); diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index b5bec2472a..81549f45e1 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -57,7 +57,7 @@ class EditController extends Controller $this->middleware( function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-credit-card'); - app('view')->share('title', (string) trans('firefly.accounts')); + app('view')->share('title', (string)trans('firefly.accounts')); $this->repository = app(AccountRepositoryInterface::class); $this->attachments = app(AttachmentHelperInterface::class); @@ -81,7 +81,7 @@ class EditController extends Controller } $objectType = config('firefly.shortNamesByFullName')[$account->accountType->type]; - $subTitle = (string) trans(sprintf('firefly.edit_%s_account', $objectType), ['name' => $account->name]); + $subTitle = (string)trans(sprintf('firefly.edit_%s_account', $objectType), ['name' => $account->name]); $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); $roles = $this->getRoles(); $liabilityTypes = $this->getLiabilityTypes(); @@ -106,9 +106,9 @@ class EditController extends Controller // interest calculation periods: $interestPeriods = [ - 'daily' => (string) trans('firefly.interest_calc_daily'), - 'monthly' => (string) trans('firefly.interest_calc_monthly'), - 'yearly' => (string) trans('firefly.interest_calc_yearly'), + 'daily' => (string)trans('firefly.interest_calc_daily'), + 'monthly' => (string)trans('firefly.interest_calc_monthly'), + 'yearly' => (string)trans('firefly.interest_calc_yearly'), ]; // put previous url in session if not redirect from store (not "return_to_edit"). @@ -117,7 +117,7 @@ class EditController extends Controller } $request->session()->forget('accounts.edit.fromUpdate'); - $openingBalanceAmount = (string) $repository->getOpeningBalanceAmount($account); + $openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account); if ('0' === $openingBalanceAmount) { $openingBalanceAmount = ''; } @@ -143,17 +143,17 @@ class EditController extends Controller 'cc_type' => $repository->getMetaValue($account, 'cc_type'), 'cc_monthly_payment_date' => $repository->getMetaValue($account, 'cc_monthly_payment_date'), 'BIC' => $repository->getMetaValue($account, 'BIC'), - 'opening_balance_date' => substr((string) $openingBalanceDate, 0, 10), + 'opening_balance_date' => substr((string)$openingBalanceDate, 0, 10), 'liability_type_id' => $account->account_type_id, 'opening_balance' => app('steam')->bcround($openingBalanceAmount, $currency->decimal_places), 'liability_direction' => $this->repository->getMetaValue($account, 'liability_direction'), 'virtual_balance' => app('steam')->bcround($virtualBalance, $currency->decimal_places), 'currency_id' => $currency->id, - 'include_net_worth' => $hasOldInput ? (bool) $request->old('include_net_worth') : $includeNetWorth, + 'include_net_worth' => $hasOldInput ? (bool)$request->old('include_net_worth') : $includeNetWorth, 'interest' => $repository->getMetaValue($account, 'interest'), 'interest_period' => $repository->getMetaValue($account, 'interest_period'), 'notes' => $this->repository->getNoteText($account), - 'active' => $hasOldInput ? (bool) $request->old('active') : $account->active, + 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, ]; if ('' === $openingBalanceAmount) { $preFilled['opening_balance'] = ''; @@ -178,7 +178,7 @@ class EditController extends Controller $data = $request->getAccountData(); $this->repository->update($account, $data); Log::channel('audit')->info(sprintf('Updated account #%d.', $account->id), $data); - $request->session()->flash('success', (string) trans('firefly.updated_account', ['name' => $account->name])); + $request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name])); // store new attachment(s): /** @var null|array $files */ @@ -188,7 +188,7 @@ class EditController extends Controller } if (null !== $files && auth()->user()->hasRole('demo')) { Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); - session()->flash('info', (string) trans('firefly.no_att_demo_user')); + session()->flash('info', (string)trans('firefly.no_att_demo_user')); } if (count($this->attachments->getMessages()->get('attachments')) > 0) { @@ -197,7 +197,7 @@ class EditController extends Controller // redirect $redirect = redirect($this->getPreviousUrl('accounts.edit.url')); - if (1 === (int) $request->get('return_to_edit')) { + if (1 === (int)$request->get('return_to_edit')) { // set value so edit routine will not overwrite URL: $request->session()->put('accounts.edit.fromUpdate', true); diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 2f567da6e5..41d7fa95b4 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -95,6 +95,21 @@ class ForgotPasswordController extends Controller return back()->with('status', trans($response)); } + /** + * @throws FireflyException + */ + private function validateHost(): void + { + $configuredHost = parse_url((string)config('app.url'), PHP_URL_HOST); + if (false === $configuredHost || null === $configuredHost) { + throw new FireflyException('Please set a valid and correct Firefly III URL in the APP_URL environment variable.'); + } + $host = request()->host(); + if ($configuredHost !== $host) { + throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header'); + } + } + /** * Show form for email recovery. * @@ -121,19 +136,4 @@ class ForgotPasswordController extends Controller return view('auth.passwords.email')->with(compact('allowRegistration', 'pageTitle')); } - - /** - * @throws FireflyException - */ - private function validateHost(): void - { - $configuredHost = parse_url((string)config('app.url'), PHP_URL_HOST); - if (false === $configuredHost || null === $configuredHost) { - throw new FireflyException('Please set a valid and correct Firefly III URL in the APP_URL environment variable.'); - } - $host = request()->host(); - if ($configuredHost !== $host) { - throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header'); - } - } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index ad69c15b56..137428953c 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -132,6 +132,25 @@ class LoginController extends Controller return $this->username; } + /** + * Get the failed login response instance. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @throws ValidationException + */ + protected function sendFailedLoginResponse(Request $request): void + { + $exception = ValidationException::withMessages( + [ + $this->username() => [trans('auth.failed')], + ] + ); + $exception->redirectTo = route('login'); + + throw $exception; + } + /** * Log the user out of the application. * @@ -210,23 +229,4 @@ class LoginController extends Controller return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField')); } - - /** - * Get the failed login response instance. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @throws ValidationException - */ - protected function sendFailedLoginResponse(Request $request): void - { - $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 8fba878c41..b84453522f 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -109,6 +109,31 @@ class RegisterController extends Controller return redirect($this->redirectPath()); } + /** + * @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. * @@ -164,29 +189,4 @@ class RegisterController extends Controller return view('auth.register', compact('isDemoSite', 'email', 'pageTitle')); } - - /** - * @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 e053441abd..7ea8448a18 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -31,7 +31,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; use PragmaRX\Google2FALaravel\Support\Authenticator; -use Preferences; /** * Class TwoFactorController. diff --git a/app/Http/Controllers/Bill/EditController.php b/app/Http/Controllers/Bill/EditController.php index abb062e606..73909da27d 100644 --- a/app/Http/Controllers/Bill/EditController.php +++ b/app/Http/Controllers/Bill/EditController.php @@ -52,7 +52,7 @@ class EditController extends Controller $this->middleware( function ($request, $next) { - app('view')->share('title', (string) trans('firefly.bills')); + app('view')->share('title', (string)trans('firefly.bills')); app('view')->share('mainTitleIcon', 'fa-calendar-o'); $this->attachments = app(AttachmentHelperInterface::class); $this->repository = app(BillRepositoryInterface::class); @@ -75,10 +75,10 @@ class EditController extends Controller $billPeriods = config('firefly.bill_periods'); foreach ($billPeriods as $current) { - $periods[$current] = (string) trans('firefly.'.$current); + $periods[$current] = (string)trans('firefly.'.$current); } - $subTitle = (string) trans('firefly.edit_bill', ['name' => $bill->name]); + $subTitle = (string)trans('firefly.edit_bill', ['name' => $bill->name]); // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('bills.edit.fromUpdate')) { @@ -99,7 +99,7 @@ class EditController extends Controller 'extension_date' => $bill->extension_date, 'notes' => $this->repository->getNoteText($bill), 'transaction_currency_id' => $bill->transaction_currency_id, - 'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active, + 'active' => $hasOldInput ? (bool)$request->old('active') : $bill->active, 'object_group' => null !== $bill->objectGroups->first() ? $bill->objectGroups->first()->title : '', ]; @@ -119,7 +119,7 @@ class EditController extends Controller Log::channel('audit')->info(sprintf('Updated bill #%d.', $bill->id), $billData); - $request->session()->flash('success', (string) trans('firefly.updated_bill', ['name' => $bill->name])); + $request->session()->flash('success', (string)trans('firefly.updated_bill', ['name' => $bill->name])); app('preferences')->mark(); /** @var null|array $files */ @@ -129,7 +129,7 @@ class EditController extends Controller } if (null !== $files && auth()->user()->hasRole('demo')) { Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); - session()->flash('info', (string) trans('firefly.no_att_demo_user')); + session()->flash('info', (string)trans('firefly.no_att_demo_user')); } // flash messages @@ -138,7 +138,7 @@ class EditController extends Controller } $redirect = redirect($this->getPreviousUrl('bills.edit.url')); - if (1 === (int) $request->get('return_to_edit')) { + if (1 === (int)$request->get('return_to_edit')) { $request->session()->put('bills.edit.fromUpdate', true); $redirect = redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]); diff --git a/app/Http/Controllers/Bill/IndexController.php b/app/Http/Controllers/Bill/IndexController.php index 069d1f0974..b78df5289c 100644 --- a/app/Http/Controllers/Bill/IndexController.php +++ b/app/Http/Controllers/Bill/IndexController.php @@ -134,24 +134,6 @@ class IndexController extends Controller return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today')); } - /** - * Set the order of a bill. - */ - 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']); - } - /** * @throws FireflyException */ @@ -268,4 +250,22 @@ class IndexController extends Controller return $totals; } + + /** + * Set the order of a bill. + */ + 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/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index cdf815f7db..935974063f 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -159,24 +159,6 @@ class IndexController extends Controller ); } - public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse - { - $this->abRepository->cleanup(); - $budgetIds = $request->get('budgetIds'); - - foreach ($budgetIds as $index => $budgetId) { - $budgetId = (int)$budgetId; - $budget = $repository->find($budgetId); - if (null !== $budget) { - app('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']); - } - private function getAllAvailableBudgets(Carbon $start, Carbon $end): array { // get all available budgets. @@ -296,11 +278,11 @@ class IndexController extends Controller // also calculate how much left from budgeted: $sums['left'][$currencyId] ??= [ - 'amount' => '0', - 'currency_id' => $budgeted['currency_id'], - 'currency_symbol' => $budgeted['currency_symbol'], - 'currency_decimal_places' => $budgeted['currency_decimal_places'], - ]; + 'amount' => '0', + 'currency_id' => $budgeted['currency_id'], + 'currency_symbol' => $budgeted['currency_symbol'], + 'currency_decimal_places' => $budgeted['currency_decimal_places'], + ]; } } @@ -316,4 +298,22 @@ class IndexController extends Controller return $sums; } + + public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse + { + $this->abRepository->cleanup(); + $budgetIds = $request->get('budgetIds'); + + foreach ($budgetIds as $index => $budgetId) { + $budgetId = (int)$budgetId; + $budget = $repository->find($budgetId); + if (null !== $budget) { + app('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/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 0ed853c01a..3482d02195 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -409,6 +409,58 @@ class AccountController extends Controller return response()->json($data); } + /** + * @throws FireflyException + */ + private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array + { + app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code)); + $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; + app('log')->debug(sprintf('Step is %s', $step)); + + // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041 + // have to make sure this chart is always based on the balance at the END of the period. + // This period depends on the size of the chart + $current = app('navigation')->endOfX($current, $step, null); + app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d'))); + 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) { + app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d'))); + $balance = (float)app('steam')->balance($account, $current, $currency); + $label = app('navigation')->periodShow($current, $step); + $entries[$label] = $balance; + $current = app('navigation')->addPeriod($current, $step, 0); + // here too, to fix #8041, the data is corrected to the end of the period. + $current = app('navigation')->endOfX($current, $step, null); + } + } + $result['entries'] = $entries; + + return $result; + } + /** * Shows the balances for a given set of dates and accounts. * @@ -512,56 +564,4 @@ class AccountController extends Controller return response()->json($data); } - - /** - * @throws FireflyException - */ - private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array - { - app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code)); - $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; - app('log')->debug(sprintf('Step is %s', $step)); - - // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041 - // have to make sure this chart is always based on the balance at the END of the period. - // This period depends on the size of the chart - $current = app('navigation')->endOfX($current, $step, null); - app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d'))); - 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) { - app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d'))); - $balance = (float)app('steam')->balance($account, $current, $currency); - $label = app('navigation')->periodShow($current, $step); - $entries[$label] = $balance; - $current = app('navigation')->addPeriod($current, $step, 0); - // here too, to fix #8041, the data is corrected to the end of the period. - $current = app('navigation')->endOfX($current, $step, null); - } - } - $result['entries'] = $entries; - - return $result; - } } diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 9f5260dd8d..fac020c8e8 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -195,6 +195,23 @@ class BudgetReportController extends Controller return response()->json($data); } + 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. */ @@ -224,21 +241,4 @@ class BudgetReportController extends Controller return response()->json($data); } - - 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 cc7a13bd24..aaf1504ecd 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -94,6 +94,11 @@ class CategoryController extends Controller return response()->json($data); } + private function getDate(): Carbon + { + return today(config('app.timezone')); + } + /** * Shows the category chart on the front page. * TODO test method for category refactor. @@ -141,6 +146,85 @@ class CategoryController extends Controller return response()->json($data); } + /** + * Generate report chart for either with or without category. + */ + private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array + { + $income = []; + $expenses = []; + $categoryId = 0; + if (null === $category) { + /** @var NoCategoryRepositoryInterface $noCatRepository */ + $noCatRepository = app(NoCategoryRepositoryInterface::class); + + // this gives us all currencies + $expenses = $noCatRepository->listExpenses($start, $end, $accounts); + $income = $noCatRepository->listIncome($start, $end, $accounts); + } + + if (null !== $category) { + /** @var OperationsRepositoryInterface $opsRepository */ + $opsRepository = app(OperationsRepositoryInterface::class); + $categoryId = $category->id; + // this gives us all currencies + $collection = new Collection([$category]); + $expenses = $opsRepository->listExpenses($start, $end, null, $collection); + $income = $opsRepository->listIncome($start, $end, null, $collection); + } + $currencies = array_unique(array_merge(array_keys($income), array_keys($expenses))); + $periods = app('navigation')->listOfPeriods($start, $end); + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $chartData = []; + // make empty data array: + // double foreach (bad) to make empty array: + foreach ($currencies as $currencyId) { + $currencyInfo = $expenses[$currencyId] ?? $income[$currencyId]; + $outKey = sprintf('%d-out', $currencyId); + $inKey = sprintf('%d-in', $currencyId); + $chartData[$outKey] + = [ + 'label' => sprintf('%s (%s)', (string)trans('firefly.spent'), $currencyInfo['currency_name']), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red + ]; + + $chartData[$inKey] + = [ + 'label' => sprintf('%s (%s)', (string)trans('firefly.earned'), $currencyInfo['currency_name']), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green + ]; + // loop empty periods: + foreach (array_keys($periods) as $period) { + $label = $periods[$period]; + $chartData[$outKey]['entries'][$label] = '0'; + $chartData[$inKey]['entries'][$label] = '0'; + } + // loop income and expenses for this category.: + $outSet = $expenses[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; + foreach ($outSet['transaction_journals'] as $journal) { + $amount = app('steam')->positive($journal['amount']); + $date = $journal['date']->isoFormat($format); + $chartData[$outKey]['entries'][$date] ??= '0'; + + $chartData[$outKey]['entries'][$date] = bcadd($amount, $chartData[$outKey]['entries'][$date]); + } + + $inSet = $income[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; + foreach ($inSet['transaction_journals'] as $journal) { + $amount = app('steam')->positive($journal['amount']); + $date = $journal['date']->isoFormat($format); + $chartData[$inKey]['entries'][$date] ??= '0'; + $chartData[$inKey]['entries'][$date] = bcadd($amount, $chartData[$inKey]['entries'][$date]); + } + } + + return $this->generator->multiSet($chartData); + } + /** * Chart for period for transactions without a category. * TODO test me. @@ -195,88 +279,4 @@ class CategoryController extends Controller return response()->json($data); } - - private function getDate(): Carbon - { - return today(config('app.timezone')); - } - - /** - * Generate report chart for either with or without category. - */ - private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array - { - $income = []; - $expenses = []; - $categoryId = 0; - if (null === $category) { - /** @var NoCategoryRepositoryInterface $noCatRepository */ - $noCatRepository = app(NoCategoryRepositoryInterface::class); - - // this gives us all currencies - $expenses = $noCatRepository->listExpenses($start, $end, $accounts); - $income = $noCatRepository->listIncome($start, $end, $accounts); - } - - if (null !== $category) { - /** @var OperationsRepositoryInterface $opsRepository */ - $opsRepository = app(OperationsRepositoryInterface::class); - $categoryId = $category->id; - // this gives us all currencies - $collection = new Collection([$category]); - $expenses = $opsRepository->listExpenses($start, $end, null, $collection); - $income = $opsRepository->listIncome($start, $end, null, $collection); - } - $currencies = array_unique(array_merge(array_keys($income), array_keys($expenses))); - $periods = app('navigation')->listOfPeriods($start, $end); - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $chartData = []; - // make empty data array: - // double foreach (bad) to make empty array: - foreach ($currencies as $currencyId) { - $currencyInfo = $expenses[$currencyId] ?? $income[$currencyId]; - $outKey = sprintf('%d-out', $currencyId); - $inKey = sprintf('%d-in', $currencyId); - $chartData[$outKey] - = [ - 'label' => sprintf('%s (%s)', (string)trans('firefly.spent'), $currencyInfo['currency_name']), - 'entries' => [], - 'type' => 'bar', - 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red - ]; - - $chartData[$inKey] - = [ - 'label' => sprintf('%s (%s)', (string)trans('firefly.earned'), $currencyInfo['currency_name']), - 'entries' => [], - 'type' => 'bar', - 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green - ]; - // loop empty periods: - foreach (array_keys($periods) as $period) { - $label = $periods[$period]; - $chartData[$outKey]['entries'][$label] = '0'; - $chartData[$inKey]['entries'][$label] = '0'; - } - // loop income and expenses for this category.: - $outSet = $expenses[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; - foreach ($outSet['transaction_journals'] as $journal) { - $amount = app('steam')->positive($journal['amount']); - $date = $journal['date']->isoFormat($format); - $chartData[$outKey]['entries'][$date] ??= '0'; - - $chartData[$outKey]['entries'][$date] = bcadd($amount, $chartData[$outKey]['entries'][$date]); - } - - $inSet = $income[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; - foreach ($inSet['transaction_journals'] as $journal) { - $amount = app('steam')->positive($journal['amount']); - $date = $journal['date']->isoFormat($format); - $chartData[$inKey]['entries'][$date] ??= '0'; - $chartData[$inKey]['entries'][$date] = bcadd($amount, $chartData[$inKey]['entries'][$date]); - } - } - - return $this->generator->multiSet($chartData); - } } diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index ad0e6555f9..ad34527918 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -269,6 +269,26 @@ class CategoryReportController extends Controller return response()->json($data); } + /** + * TODO duplicate function + */ + 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; + } + public function sourceExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse { $result = []; @@ -324,24 +344,4 @@ class CategoryReportController extends Controller return response()->json($data); } - - /** - * TODO duplicate function - */ - 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 0e9a9b08e1..8cf9117732 100644 --- a/app/Http/Controllers/Chart/DoubleReportController.php +++ b/app/Http/Controllers/Chart/DoubleReportController.php @@ -212,6 +212,44 @@ class DoubleReportController extends Controller return response()->json($data); } + /** + * TODO duplicate function + */ + 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 + */ + 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; + } + public function tagExpense(Collection $accounts, Collection $others, Carbon $start, Carbon $end): JsonResponse { $result = []; @@ -317,42 +355,4 @@ class DoubleReportController extends Controller return response()->json($data); } - - /** - * TODO duplicate function - */ - 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 - */ - 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/ExpenseReportController.php b/app/Http/Controllers/Chart/ExpenseReportController.php index f393f2a09e..889a004850 100644 --- a/app/Http/Controllers/Chart/ExpenseReportController.php +++ b/app/Http/Controllers/Chart/ExpenseReportController.php @@ -102,27 +102,27 @@ class ExpenseReportController extends Controller /** @var Account $exp */ $exp = $combination->first(); $chartData[$exp->id.'-in'] = [ - 'label' => sprintf('%s (%s)', $name, (string) trans('firefly.income')), + 'label' => sprintf('%s (%s)', $name, (string)trans('firefly.income')), 'type' => 'bar', 'yAxisID' => 'y-axis-0', 'entries' => [], ]; $chartData[$exp->id.'-out'] = [ - 'label' => sprintf('%s (%s)', $name, (string) trans('firefly.expenses')), + 'label' => sprintf('%s (%s)', $name, (string)trans('firefly.expenses')), 'type' => 'bar', 'yAxisID' => 'y-axis-0', 'entries' => [], ]; // total in, total out: $chartData[$exp->id.'-total-in'] = [ - 'label' => sprintf('%s (%s)', $name, (string) trans('firefly.sum_of_income')), + 'label' => sprintf('%s (%s)', $name, (string)trans('firefly.sum_of_income')), 'type' => 'line', 'fill' => false, 'yAxisID' => 'y-axis-1', 'entries' => [], ]; $chartData[$exp->id.'-total-out'] = [ - 'label' => sprintf('%s (%s)', $name, (string) trans('firefly.sum_of_expenses')), + 'label' => sprintf('%s (%s)', $name, (string)trans('firefly.sum_of_expenses')), 'type' => 'line', 'fill' => false, 'yAxisID' => 'y-axis-1', diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php index 9c1d2fdedc..2d12c01539 100644 --- a/app/Http/Controllers/Chart/TagReportController.php +++ b/app/Http/Controllers/Chart/TagReportController.php @@ -274,6 +274,26 @@ class TagReportController extends Controller return response()->json($data); } + /** + * TODO duplicate function + */ + 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; + } + public function sourceExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): JsonResponse { $result = []; @@ -381,24 +401,4 @@ class TagReportController extends Controller return response()->json($data); } - - /** - * TODO duplicate function - */ - 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/DebugController.php b/app/Http/Controllers/DebugController.php index d17ae5c079..745e4d8cf4 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Models\AccountType; @@ -140,21 +141,6 @@ class DebugController extends Controller return view('debug', compact('table', 'now', 'logContent')); } - /** - * Flash all types of messages. - * - * @return Redirector|RedirectResponse - */ - public function testFlash(Request $request) - { - $request->session()->flash('success', 'This is a success message.'); - $request->session()->flash('info', 'This is an info message.'); - $request->session()->flash('warning', 'This is a warning.'); - $request->session()->flash('error', 'This is an error!'); - - return redirect(route('home')); - } - private function generateTable(): string { // system information: @@ -336,4 +322,19 @@ class DebugController extends Controller return implode(' ', $flags); } + + /** + * Flash all types of messages. + * + * @return Redirector|RedirectResponse + */ + public function testFlash(Request $request) + { + $request->session()->flash('success', 'This is a success message.'); + $request->session()->flash('info', 'This is an info message.'); + $request->session()->flash('warning', 'This is a warning.'); + $request->session()->flash('error', 'This is an error!'); + + return redirect(route('home')); + } } diff --git a/app/Http/Controllers/Export/IndexController.php b/app/Http/Controllers/Export/IndexController.php index 0a427e9016..91da4e515e 100644 --- a/app/Http/Controllers/Export/IndexController.php +++ b/app/Http/Controllers/Export/IndexController.php @@ -67,7 +67,7 @@ class IndexController extends Controller public function export(): LaravelResponse|RedirectResponse { if (auth()->user()->hasRole('demo')) { - session()->flash('info', (string) trans('firefly.demo_user_export')); + session()->flash('info', (string)trans('firefly.demo_user_export')); return redirect(route('export.index')); } diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index f7068f2751..f07a6d3874 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -144,7 +144,6 @@ class JavascriptController extends Controller return response() ->view('v2.javascript.variables', $data) - ->header('Content-Type', 'text/javascript') - ; + ->header('Content-Type', 'text/javascript'); } } diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index 1bb6a89536..9b29489e83 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -129,7 +129,7 @@ class BoxController extends Controller app('log')->debug('Left to spend is positive or zero!'); $boxTitle = (string)trans('firefly.left_to_spend'); $activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description. - $display = 1; // not overspent + $display = 1; // not overspent $leftPerDayAmount = bcdiv($leftToSpendAmount, (string)$activeDaysLeft); app('log')->debug(sprintf('Left to spend per day is %s', $leftPerDayAmount)); } diff --git a/app/Http/Controllers/Json/RecurrenceController.php b/app/Http/Controllers/Json/RecurrenceController.php index 939ae56e84..6bd19c6013 100644 --- a/app/Http/Controllers/Json/RecurrenceController.php +++ b/app/Http/Controllers/Json/RecurrenceController.php @@ -71,13 +71,13 @@ class RecurrenceController extends Controller $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); $firstDate = Carbon::createFromFormat('Y-m-d', $request->get('first_date')); - $endDate = '' !== (string) $request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; - $endsAt = (string) $request->get('ends'); + $endDate = '' !== (string)$request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; + $endsAt = (string)$request->get('ends'); $repetitionType = explode(',', $request->get('type'))[0]; - $repetitions = (int) $request->get('reps'); - $weekend = (int) $request->get('weekend'); + $repetitions = (int)$request->get('reps'); + $weekend = (int)$request->get('weekend'); $repetitionMoment = ''; - $skip = (int) $request->get('skip'); + $skip = (int)$request->get('skip'); $skip = $skip < 0 || $skip > 31 ? 0 : $skip; $weekend = $weekend < 1 || $weekend > 4 ? 1 : $weekend; @@ -147,7 +147,7 @@ class RecurrenceController extends Controller */ public function suggest(Request $request): JsonResponse { - $string = '' === (string) $request->get('date') ? date('Y-m-d') : (string) $request->get('date'); + $string = '' === (string)$request->get('date') ? date('Y-m-d') : (string)$request->get('date'); $today = today(config('app.timezone'))->startOfDay(); try { @@ -159,37 +159,37 @@ class RecurrenceController extends Controller return response()->json(); } $date->startOfDay(); - $preSelected = (string) $request->get('pre_select'); + $preSelected = (string)$request->get('pre_select'); $locale = app('steam')->getLocale(); app('log')->debug(sprintf('date = %s, today = %s. date > today? %s', $date->toAtomString(), $today->toAtomString(), var_export($date > $today, true))); - app('log')->debug(sprintf('past = true? %s', var_export('true' === (string) $request->get('past'), true))); + app('log')->debug(sprintf('past = true? %s', var_export('true' === (string)$request->get('past'), true))); $result = []; - if ($date > $today || 'true' === (string) $request->get('past')) { + if ($date > $today || 'true' === (string)$request->get('past')) { app('log')->debug('Will fill dropdown.'); $weekly = sprintf('weekly,%s', $date->dayOfWeekIso); $monthly = sprintf('monthly,%s', $date->day); - $dayOfWeek = (string) trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); + $dayOfWeek = (string)trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); $ndom = sprintf('ndom,%s,%s', $date->weekOfMonth, $date->dayOfWeekIso); $yearly = sprintf('yearly,%s', $date->format('Y-m-d')); - $yearlyDate = $date->isoFormat((string) trans('config.month_and_day_no_year_js', [], $locale)); + $yearlyDate = $date->isoFormat((string)trans('config.month_and_day_no_year_js', [], $locale)); $result = [ - 'daily' => ['label' => (string) trans('firefly.recurring_daily'), 'selected' => str_starts_with($preSelected, 'daily')], + 'daily' => ['label' => (string)trans('firefly.recurring_daily'), 'selected' => str_starts_with($preSelected, 'daily')], $weekly => [ - 'label' => (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), + 'label' => (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), 'selected' => str_starts_with($preSelected, 'weekly'), ], $monthly => [ - 'label' => (string) trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), + 'label' => (string)trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), 'selected' => str_starts_with($preSelected, 'monthly'), ], $ndom => [ - 'label' => (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), + 'label' => (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), 'selected' => str_starts_with($preSelected, 'ndom'), ], $yearly => [ - 'label' => (string) trans('firefly.recurring_yearly', ['date' => $yearlyDate]), + 'label' => (string)trans('firefly.recurring_yearly', ['date' => $yearlyDate]), 'selected' => str_starts_with($preSelected, 'yearly'), ], ]; diff --git a/app/Http/Controllers/PiggyBank/IndexController.php b/app/Http/Controllers/PiggyBank/IndexController.php index 8f909ab59f..72396eb20d 100644 --- a/app/Http/Controllers/PiggyBank/IndexController.php +++ b/app/Http/Controllers/PiggyBank/IndexController.php @@ -141,24 +141,6 @@ class IndexController extends Controller return view('piggy-banks.index', compact('piggyBanks', 'accounts')); } - /** - * Set the order of a piggy bank. - */ - 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']); - } - private function makeSums(array $piggyBanks): array { $sums = []; @@ -193,4 +175,22 @@ class IndexController extends Controller return $piggyBanks; } + + /** + * Set the order of a piggy bank. + */ + 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 f0a587a190..e5d1974824 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -49,7 +49,7 @@ class PreferencesController extends Controller $this->middleware( static function ($request, $next) { - app('view')->share('title', (string) trans('firefly.preferences')); + app('view')->share('title', (string)trans('firefly.preferences')); app('view')->share('mainTitleIcon', 'fa-gear'); return $next($request); @@ -72,8 +72,8 @@ class PreferencesController extends Controller /** @var Account $account */ foreach ($accounts as $account) { - $type = $account->accountType->type; - $role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role')); + $type = $account->accountType->type; + $role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role')); if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) { $role = sprintf('opt_group_l_%s', $type); @@ -82,7 +82,7 @@ class PreferencesController extends Controller if ('opt_group_' === $role) { $role = 'opt_group_defaultAsset'; } - $groupedAccounts[(string) trans(sprintf('firefly.%s', $role))][$account->id] = $account->name; + $groupedAccounts[(string)trans(sprintf('firefly.%s', $role))][$account->id] = $account->name; } ksort($groupedAccounts); @@ -105,7 +105,7 @@ class PreferencesController extends Controller if (is_array($fiscalYearStartStr)) { $fiscalYearStartStr = '01-01'; } - $fiscalYearStart = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr); + $fiscalYearStart = sprintf('%s-%s', date('Y'), (string)$fiscalYearStartStr); $tjOptionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; $availableDarkModes = config('firefly.available_dark_modes'); @@ -120,12 +120,12 @@ class PreferencesController extends Controller // list of locales also has "equal" which makes it equal to whatever the language is. try { - $locales = json_decode((string) file_get_contents(resource_path(sprintf('lang/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR); + $locales = json_decode((string)file_get_contents(resource_path(sprintf('lang/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $e) { app('log')->error($e->getMessage()); $locales = []; } - $locales = ['equal' => (string) trans('firefly.equal_to_language')] + $locales; + $locales = ['equal' => (string)trans('firefly.equal_to_language')] + $locales; // an important fallback is that the frontPageAccount array gets refilled automatically // when it turns up empty. if (0 === count($frontPageAccounts)) { @@ -158,7 +158,7 @@ class PreferencesController extends Controller $frontPageAccounts = []; if (is_array($request->get('frontPageAccounts')) && count($request->get('frontPageAccounts')) > 0) { foreach ($request->get('frontPageAccounts') as $id) { - $frontPageAccounts[] = (int) $id; + $frontPageAccounts[] = (int)$id; } app('preferences')->set('frontPageAccounts', $frontPageAccounts); } @@ -184,7 +184,7 @@ class PreferencesController extends Controller // slack URL: if (!auth()->user()->hasRole('demo')) { - $url = (string) $request->get('slackUrl'); + $url = (string)$request->get('slackUrl'); if (UrlValidator::isValidWebhookURL($url)) { app('preferences')->set('slack_webhook_url', $url); } @@ -194,8 +194,8 @@ class PreferencesController extends Controller } // custom fiscal year - $customFiscalYear = 1 === (int) $request->get('customFiscalYear'); - $string = strtotime((string) $request->get('fiscalYearStart')); + $customFiscalYear = 1 === (int)$request->get('customFiscalYear'); + $string = strtotime((string)$request->get('fiscalYearStart')); if (false !== $string) { $fiscalYearStart = date('m-d', $string); app('preferences')->set('customFiscalYear', $customFiscalYear); @@ -204,7 +204,7 @@ class PreferencesController extends Controller // save page size: app('preferences')->set('listPageSize', 50); - $listPageSize = (int) $request->get('listPageSize'); + $listPageSize = (int)$request->get('listPageSize'); if ($listPageSize > 0 && $listPageSize < 1337) { app('preferences')->set('listPageSize', $listPageSize); } @@ -252,7 +252,7 @@ class PreferencesController extends Controller app('preferences')->set('darkMode', $darkMode); } - session()->flash('success', (string) trans('firefly.saved_preferences')); + session()->flash('success', (string)trans('firefly.saved_preferences')); app('preferences')->mark(); return redirect(route('preferences.index')); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 6301ceb1de..858114a198 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -497,6 +497,47 @@ class ProfileController extends Controller return redirect(route('profile.index')); } + /** + * TODO duplicate code. + * + * @throws FireflyException + */ + 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. * @@ -631,45 +672,4 @@ class ProfileController extends Controller return redirect(route('login')); } - - /** - * TODO duplicate code. - * - * @throws FireflyException - */ - 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/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index 907525f85f..c3560c2219 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -283,8 +283,8 @@ class CategoryController extends Controller ]; ++$result[$key]['transactions']; $result[$key]['sum'] = bcadd($journal['amount'], $result[$key]['sum']); - $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); - $result[$key]['avg_float'] = (float) $result[$key]['avg']; // intentional float + $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); + $result[$key]['avg_float'] = (float)$result[$key]['avg']; // intentional float } } } @@ -333,8 +333,8 @@ class CategoryController extends Controller ]; ++$result[$key]['transactions']; $result[$key]['sum'] = bcadd($journal['amount'], $result[$key]['sum']); - $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); - $result[$key]['avg_float'] = (float) $result[$key]['avg']; + $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); + $result[$key]['avg_float'] = (float)$result[$key]['avg']; } } } @@ -664,7 +664,7 @@ class CategoryController extends Controller $result[] = [ 'description' => $journal['description'], 'transaction_group_id' => $journal['transaction_group_id'], - 'amount_float' => (float) $journal['amount'], + 'amount_float' => (float)$journal['amount'], 'amount' => $journal['amount'], 'date' => $journal['date']->isoFormat($this->monthAndDayFormat), 'date_sort' => $journal['date']->format('Y-m-d'), @@ -712,7 +712,7 @@ class CategoryController extends Controller $result[] = [ 'description' => $journal['description'], 'transaction_group_id' => $journal['transaction_group_id'], - 'amount_float' => (float) $journal['amount'], + 'amount_float' => (float)$journal['amount'], 'amount' => $journal['amount'], 'date' => $journal['date']->isoFormat($this->monthAndDayFormat), 'date_sort' => $journal['date']->format('Y-m-d'), diff --git a/app/Http/Controllers/Report/DoubleController.php b/app/Http/Controllers/Report/DoubleController.php index ce00226bbe..9477589868 100644 --- a/app/Http/Controllers/Report/DoubleController.php +++ b/app/Http/Controllers/Report/DoubleController.php @@ -41,7 +41,7 @@ class DoubleController extends Controller { use AugumentData; - protected AccountRepositoryInterface $accountRepository; + protected AccountRepositoryInterface $accountRepository; private OperationsRepositoryInterface $opsRepository; /** @@ -278,6 +278,24 @@ class DoubleController extends Controller return view('reports.double.partials.accounts', compact('sums', 'report')); } + /** + * TODO this method is duplicated. + */ + 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; + } + /** * @return Factory|View */ @@ -468,22 +486,4 @@ class DoubleController extends Controller return $result; } - - /** - * TODO this method is duplicated. - */ - 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 e9b488bcfd..a245845f82 100644 --- a/app/Http/Controllers/Report/TagController.php +++ b/app/Http/Controllers/Report/TagController.php @@ -279,8 +279,8 @@ class TagController extends Controller ]; ++$result[$key]['transactions']; $result[$key]['sum'] = bcadd($journal['amount'], $result[$key]['sum']); - $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); - $result[$key]['avg_float'] = (float) $result[$key]['avg']; + $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); + $result[$key]['avg_float'] = (float)$result[$key]['avg']; } } } @@ -329,8 +329,8 @@ class TagController extends Controller ]; ++$result[$key]['transactions']; $result[$key]['sum'] = bcadd($journal['amount'], $result[$key]['sum']); - $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); - $result[$key]['avg_float'] = (float) $result[$key]['avg']; + $result[$key]['avg'] = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); + $result[$key]['avg_float'] = (float)$result[$key]['avg']; } } } @@ -466,7 +466,7 @@ class TagController extends Controller $result[] = [ 'description' => $journal['description'], 'transaction_group_id' => $journal['transaction_group_id'], - 'amount_float' => (float) $journal['amount'], + 'amount_float' => (float)$journal['amount'], 'amount' => $journal['amount'], 'date' => $journal['date']->isoFormat($this->monthAndDayFormat), 'date_sort' => $journal['date']->format('Y-m-d'), @@ -514,7 +514,7 @@ class TagController extends Controller $result[] = [ 'description' => $journal['description'], 'transaction_group_id' => $journal['transaction_group_id'], - 'amount_float' => (float) $journal['amount'], // intentional float. + 'amount_float' => (float)$journal['amount'], // intentional float. 'amount' => $journal['amount'], 'date' => $journal['date']->isoFormat($this->monthAndDayFormat), 'date_sort' => $journal['date']->format('Y-m-d'), diff --git a/app/Http/Controllers/Rule/EditController.php b/app/Http/Controllers/Rule/EditController.php index f9e3c65bbd..a4412d17ab 100644 --- a/app/Http/Controllers/Rule/EditController.php +++ b/app/Http/Controllers/Rule/EditController.php @@ -132,29 +132,6 @@ class EditController extends Controller return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount')); } - /** - * Update the rule. - * - * @return Redirector|RedirectResponse - */ - 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; - } - /** * @throws FireflyException */ @@ -196,4 +173,27 @@ class EditController extends Controller return $renderedEntries; } + + /** + * Update the rule. + * + * @return Redirector|RedirectResponse + */ + 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/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index a00e46f58c..5c00d52a3f 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -129,26 +129,6 @@ class InstallController extends Controller return response()->json($response); } - /** - * Create specific RSA keys. - */ - public function keys(): void - { - $key = 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, (string)$key->getPublicKey()); - file_put_contents($privateKey, $key->toString('PKCS1')); - } - /** * @throws FireflyException */ @@ -173,4 +153,24 @@ class InstallController extends Controller return true; } + + /** + * Create specific RSA keys. + */ + public function keys(): void + { + $key = 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, (string)$key->getPublicKey()); + file_put_contents($privateKey, $key->toString('PKCS1')); + } } diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 9c256c1b41..7d4ceadece 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -135,38 +135,6 @@ class ConvertController extends Controller ); } - /** - * Do the conversion. - * - * @return Redirector|RedirectResponse - */ - public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group) - { - if (!$this->isEditableGroup($group)) { - return $this->redirectGroupToAccount($group); - } - - /** @var TransactionJournal $journal */ - foreach ($group->transactionJournals as $journal) { - // catch FF exception. - try { - $this->convertJournal($journal, $destinationType, $request->all()); - } catch (FireflyException $e) { - session()->flash('error', $e->getMessage()); - - return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput(); - } - } - - // correct transfers: - $group->refresh(); - - session()->flash('success', (string)trans('firefly.converted_to_'.$destinationType->type)); - event(new UpdatedTransactionGroup($group, true, true)); - - return redirect(route('transactions.show', [$group->id])); - } - private function getValidDepositSources(): array { // make repositories @@ -291,6 +259,38 @@ class ConvertController extends Controller return $grouped; } + /** + * Do the conversion. + * + * @return Redirector|RedirectResponse + */ + public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group) + { + if (!$this->isEditableGroup($group)) { + return $this->redirectGroupToAccount($group); + } + + /** @var TransactionJournal $journal */ + foreach ($group->transactionJournals as $journal) { + // catch FF exception. + try { + $this->convertJournal($journal, $destinationType, $request->all()); + } catch (FireflyException $e) { + session()->flash('error', $e->getMessage()); + + return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput(); + } + } + + // correct transfers: + $group->refresh(); + + session()->flash('success', (string)trans('firefly.converted_to_'.$destinationType->type)); + event(new UpdatedTransactionGroup($group, true, true)); + + return redirect(route('transactions.show', [$group->id])); + } + /** * @throws FireflyException */ diff --git a/app/Http/Controllers/Transaction/CreateController.php b/app/Http/Controllers/Transaction/CreateController.php index 3f270ed40f..efbeca2252 100644 --- a/app/Http/Controllers/Transaction/CreateController.php +++ b/app/Http/Controllers/Transaction/CreateController.php @@ -50,7 +50,7 @@ class CreateController extends Controller $this->middleware( function ($request, $next) { - app('view')->share('title', (string) trans('firefly.transactions')); + app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-exchange'); $this->repository = app(TransactionGroupRepositoryInterface::class); @@ -61,7 +61,7 @@ class CreateController extends Controller public function cloneGroup(Request $request): JsonResponse { - $groupId = (int) $request->get('id'); + $groupId = (int)$request->get('id'); if (0 !== $groupId) { $group = $this->repository->find($groupId); if (null !== $group) { @@ -101,14 +101,14 @@ class CreateController extends Controller { app('preferences')->mark(); - $sourceId = (int) request()->get('source'); - $destinationId = (int) request()->get('destination'); + $sourceId = (int)request()->get('source'); + $destinationId = (int)request()->get('destination'); /** @var AccountRepositoryInterface $accountRepository */ $accountRepository = app(AccountRepositoryInterface::class); $cash = $accountRepository->getCashAccount(); $preFilled = session()->has('preFilled') ? session('preFilled') : []; - $subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType))); + $subTitle = (string)trans(sprintf('breadcrumbs.create_%s', strtolower((string)$objectType))); $subTitleIcon = 'fa-plus'; $optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; $allowedOpposingTypes = config('firefly.allowed_opposing_types'); diff --git a/app/Http/Controllers/Transaction/EditController.php b/app/Http/Controllers/Transaction/EditController.php index 91dff1c221..e2fe8ba23d 100644 --- a/app/Http/Controllers/Transaction/EditController.php +++ b/app/Http/Controllers/Transaction/EditController.php @@ -51,7 +51,7 @@ class EditController extends Controller // translations: $this->middleware( function ($request, $next) { - app('view')->share('title', (string) trans('firefly.transactions')); + app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-exchange'); $this->repository = app(JournalRepositoryInterface::class); @@ -79,7 +79,7 @@ class EditController extends Controller $expectedSourceTypes = config('firefly.expected_source_types'); $allowedSourceDests = config('firefly.source_dests'); $title = $transactionGroup->transactionJournals()->count() > 1 ? $transactionGroup->title : $transactionGroup->transactionJournals()->first()->description; - $subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]); + $subTitle = (string)trans('firefly.edit_transaction_title', ['description' => $title]); $subTitleIcon = 'fa-plus'; $defaultCurrency = app('amount')->getDefaultCurrency(); $cash = $repository->getCashAccount(); diff --git a/app/Http/Middleware/Installer.php b/app/Http/Middleware/Installer.php index 4b7c7126c5..8fc6ee1c77 100644 --- a/app/Http/Middleware/Installer.php +++ b/app/Http/Middleware/Installer.php @@ -73,22 +73,6 @@ class Installer return $next($request); } - /** - * Is access denied error. - */ - protected function isAccessDenied(string $message): bool - { - return false !== stripos($message, 'Access denied'); - } - - /** - * Is no tables exist error. - */ - 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. * @@ -125,6 +109,22 @@ class Installer return false; } + /** + * Is access denied error. + */ + protected function isAccessDenied(string $message): bool + { + return false !== stripos($message, 'Access denied'); + } + + /** + * Is no tables exist error. + */ + 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/Requests/JournalLinkRequest.php b/app/Http/Requests/JournalLinkRequest.php index f03e2bf323..6e2e083f11 100644 --- a/app/Http/Requests/JournalLinkRequest.php +++ b/app/Http/Requests/JournalLinkRequest.php @@ -46,7 +46,7 @@ class JournalLinkRequest extends FormRequest $return = []; $linkType = $this->get('link_type'); $parts = explode('_', $linkType); - $return['link_type_id'] = (int) $parts[0]; + $return['link_type_id'] = (int)$parts[0]; $return['transaction_journal_id'] = $this->convertInteger('opposing'); $return['notes'] = $this->convertString('notes'); $return['direction'] = $parts[1]; diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index f4323eeec5..0bdaef8ef1 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -150,6 +150,38 @@ class RecurrenceFormRequest extends FormRequest return $return; } + /** + * Parses repetition data. + */ + 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. */ @@ -278,18 +310,18 @@ class RecurrenceFormRequest extends FormRequest $throwError = true; if ('withdrawal' === $type) { $throwError = false; - $sourceId = (int) $data['source_id']; - $destinationId = (int) $data['withdrawal_destination_id']; + $sourceId = (int)$data['source_id']; + $destinationId = (int)$data['withdrawal_destination_id']; } if ('deposit' === $type) { $throwError = false; - $sourceId = (int) $data['deposit_source_id']; - $destinationId = (int) ($data['destination_id'] ?? 0); + $sourceId = (int)$data['deposit_source_id']; + $destinationId = (int)($data['destination_id'] ?? 0); } if ('transfer' === $type) { $throwError = false; - $sourceId = (int) $data['source_id']; - $destinationId = (int) ($data['destination_id'] ?? 0); + $sourceId = (int)$data['source_id']; + $destinationId = (int)($data['destination_id'] ?? 0); } if (true === $throwError) { throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->convertString('transaction_type'))); @@ -300,7 +332,7 @@ class RecurrenceFormRequest extends FormRequest // do something with result: if (false === $validSource) { - $message = (string) trans('validation.generic_invalid_source'); + $message = (string)trans('validation.generic_invalid_source'); $validator->errors()->add('source_id', $message); $validator->errors()->add('deposit_source_id', $message); @@ -311,41 +343,9 @@ class RecurrenceFormRequest extends FormRequest $validDestination = $accountValidator->validateDestination(['id' => $destinationId]); // do something with result: if (false === $validDestination) { - $message = (string) trans('validation.generic_invalid_destination'); + $message = (string)trans('validation.generic_invalid_destination'); $validator->errors()->add('destination_id', $message); $validator->errors()->add('withdrawal_destination_id', $message); } } - - /** - * Parses repetition data. - */ - 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/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php index da6c5e4417..3a0977bbe7 100644 --- a/app/Http/Requests/ReportFormRequest.php +++ b/app/Http/Requests/ReportFormRequest.php @@ -55,7 +55,7 @@ class ReportFormRequest extends FormRequest $collection = new Collection(); if (is_array($set)) { foreach ($set as $accountId) { - $account = $repository->find((int) $accountId); + $account = $repository->find((int)$accountId); if (null !== $account) { $collection->push($account); } @@ -76,7 +76,7 @@ class ReportFormRequest extends FormRequest $collection = new Collection(); if (is_array($set)) { foreach ($set as $budgetId) { - $budget = $repository->find((int) $budgetId); + $budget = $repository->find((int)$budgetId); if (null !== $budget) { $collection->push($budget); } @@ -97,7 +97,7 @@ class ReportFormRequest extends FormRequest $collection = new Collection(); if (is_array($set)) { foreach ($set as $categoryId) { - $category = $repository->find((int) $categoryId); + $category = $repository->find((int)$categoryId); if (null !== $category) { $collection->push($category); } @@ -118,7 +118,7 @@ class ReportFormRequest extends FormRequest $collection = new Collection(); if (is_array($set)) { foreach ($set as $accountId) { - $account = $repository->find((int) $accountId); + $account = $repository->find((int)$accountId); if (null !== $account) { $collection->push($account); } @@ -137,7 +137,7 @@ class ReportFormRequest extends FormRequest { $date = today(config('app.timezone')); $range = $this->get('daterange'); - $parts = explode(' - ', (string) $range); + $parts = explode(' - ', (string)$range); if (2 === count($parts)) { $string = $parts[1]; // validate as date @@ -175,7 +175,7 @@ class ReportFormRequest extends FormRequest { $date = today(config('app.timezone')); $range = $this->get('daterange'); - $parts = explode(' - ', (string) $range); + $parts = explode(' - ', (string)$range); if (2 === count($parts)) { $string = $parts[0]; // validate as date @@ -229,7 +229,7 @@ class ReportFormRequest extends FormRequest continue; } - $tag = $repository->find((int) $tagTag); + $tag = $repository->find((int)$tagTag); if (null !== $tag) { $collection->push($tag); } diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 158727dfd9..6d6f831815 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -58,6 +58,28 @@ class RuleFormRequest extends FormRequest ]; } + 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; + } + public static function replaceAmountTrigger(array $array): array { // do some sneaky search and replace. @@ -83,6 +105,24 @@ class RuleFormRequest extends FormRequest 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; + } + /** * Rules for this request. */ @@ -127,44 +167,4 @@ class RuleFormRequest extends FormRequest Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); } } - - 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; - } - - 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; - } } diff --git a/app/Jobs/CreateAutoBudgetLimits.php b/app/Jobs/CreateAutoBudgetLimits.php index ed9d24c042..ce0c15e173 100644 --- a/app/Jobs/CreateAutoBudgetLimits.php +++ b/app/Jobs/CreateAutoBudgetLimits.php @@ -77,13 +77,6 @@ class CreateAutoBudgetLimits implements ShouldQueue } } - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } - /** * @throws FireflyException */ @@ -362,4 +355,11 @@ class CreateAutoBudgetLimits implements ShouldQueue } app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); } + + 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 025116a855..4aa0520d94 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -152,23 +152,6 @@ class CreateRecurringTransactions implements ShouldQueue app('preferences')->mark(); } - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } - - public function setForce(bool $force): void - { - $this->force = $force; - } - - public function setRecurrences(Collection $recurrences): void - { - $this->recurrences = $recurrences; - } - private function filterRecurrences(Collection $recurrences): Collection { return $recurrences->filter( @@ -465,4 +448,21 @@ class CreateRecurringTransactions implements ShouldQueue return $return; } + + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } + + public function setForce(bool $force): void + { + $this->force = $force; + } + + public function setRecurrences(Collection $recurrences): void + { + $this->recurrences = $recurrences; + } } diff --git a/app/Jobs/DownloadExchangeRates.php b/app/Jobs/DownloadExchangeRates.php index 4519c4d509..723d85b8a4 100644 --- a/app/Jobs/DownloadExchangeRates.php +++ b/app/Jobs/DownloadExchangeRates.php @@ -88,13 +88,6 @@ class DownloadExchangeRates implements ShouldQueue } } - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } - /** * @throws GuzzleException */ @@ -185,4 +178,11 @@ class DownloadExchangeRates implements ShouldQueue } } } + + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } } diff --git a/app/Jobs/WarnAboutBills.php b/app/Jobs/WarnAboutBills.php index 970ef96448..e7f1915dfd 100644 --- a/app/Jobs/WarnAboutBills.php +++ b/app/Jobs/WarnAboutBills.php @@ -92,18 +92,6 @@ class WarnAboutBills implements ShouldQueue app('preferences')->mark(); } - public function setDate(Carbon $date): void - { - $newDate = clone $date; - $newDate->startOfDay(); - $this->date = $newDate; - } - - public function setForce(bool $force): void - { - $this->force = $force; - } - private function hasDateFields(Bill $bill): bool { if (false === $bill->active) { @@ -149,4 +137,16 @@ class WarnAboutBills implements ShouldQueue app('log')->debug('Will now send warning!'); event(new WarnUserAboutBill($bill, $field, $diff)); } + + public function setDate(Carbon $date): void + { + $newDate = clone $date; + $newDate->startOfDay(); + $this->date = $newDate; + } + + public function setForce(bool $force): void + { + $this->force = $force; + } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 9736462044..c6ed9908e7 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -122,13 +122,13 @@ class Account extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'user_id' => 'integer', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'encrypted' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'user_id' => 'integer', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'encrypted' => 'boolean', + ]; protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban']; diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 799543776c..a3c37ed271 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -59,9 +59,9 @@ class AccountMeta extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; protected $fillable = ['account_id', 'name', 'data']; diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 54036c23ab..c2a942460a 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -72,9 +72,9 @@ class AccountType extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; protected $fillable = ['type']; diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index c0dedb7db3..e2ef680150 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -97,11 +97,11 @@ class Attachment extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'uploaded' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'uploaded' => 'boolean', + ]; protected $fillable = ['attachable_id', 'attachable_type', 'user_id', 'md5', 'filename', 'mime', 'title', 'description', 'size', 'uploaded']; diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 95bdaa6120..8a3d8c7e91 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -80,13 +80,13 @@ class AvailableBudget extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'start_date' => 'date', - 'end_date' => 'date', - 'transaction_currency_id' => 'int', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'start_date' => 'date', + 'end_date' => 'date', + 'transaction_currency_id' => 'int', + ]; protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; diff --git a/app/Models/Bill.php b/app/Models/Bill.php index ff50cc7aa6..5b7e65ce61 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -114,36 +114,36 @@ class Bill extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'date' => 'date', - 'end_date' => 'date', - 'extension_date' => 'date', - 'skip' => 'int', - 'automatch' => 'boolean', - 'active' => 'boolean', - 'name_encrypted' => 'boolean', - 'match_encrypted' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'date' => 'date', + 'end_date' => 'date', + 'extension_date' => 'date', + 'skip' => 'int', + 'automatch' => 'boolean', + 'active' => 'boolean', + 'name_encrypted' => 'boolean', + 'match_encrypted' => 'boolean', + ]; protected $fillable = [ - 'name', - 'match', - 'amount_min', - 'user_id', - 'user_group_id', - 'amount_max', - 'date', - 'repeat_freq', - 'skip', - 'automatch', - 'active', - 'transaction_currency_id', - 'end_date', - 'extension_date', - ]; + 'name', + 'match', + 'amount_min', + 'user_id', + 'user_group_id', + 'amount_max', + 'date', + 'repeat_freq', + 'skip', + 'automatch', + 'active', + 'transaction_currency_id', + 'end_date', + 'extension_date', + ]; protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted']; diff --git a/app/Models/Budget.php b/app/Models/Budget.php index dd7f9a5a6b..3c13dba221 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -97,12 +97,12 @@ class Budget extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'encrypted' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'encrypted' => 'boolean', + ]; protected $fillable = ['user_id', 'name', 'active', 'order', 'user_group_id']; diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index e8528e5e40..58bc881614 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -74,18 +74,18 @@ class BudgetLimit extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'start_date' => 'date', - 'end_date' => 'date', - 'auto_budget' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'start_date' => 'date', + 'end_date' => 'date', + 'auto_budget' => 'boolean', + ]; protected $dispatchesEvents = [ - 'created' => Created::class, - 'updated' => Updated::class, - 'deleted' => Deleted::class, - ]; + 'created' => Created::class, + 'updated' => Updated::class, + 'deleted' => Deleted::class, + ]; protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id']; diff --git a/app/Models/Category.php b/app/Models/Category.php index 5901af1e7c..007d95d0f5 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -86,11 +86,11 @@ class Category extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'encrypted' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'encrypted' => 'boolean', + ]; protected $fillable = ['user_id', 'user_group_id', 'name']; diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 815e3aaa63..9d07d927f0 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -62,10 +62,10 @@ class Configuration extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; /** @var string The table to store the data in */ protected $table = 'configuration'; diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index f08edd46c8..eb3f5f9f15 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -72,11 +72,11 @@ class LinkType extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'editable' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'editable' => 'boolean', + ]; protected $fillable = ['name', 'inward', 'outward', 'editable']; diff --git a/app/Models/Location.php b/app/Models/Location.php index 5ab9e58d06..10962cb375 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -63,8 +63,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo; * @method static Builder|Location whereUpdatedAt($value) * @method static Builder|Location whereZoomLevel($value) * - * @property Collection $transactionJournals - * @property null|int $transaction_journals_count + * @property Collection $transactionJournals + * @property null|int $transaction_journals_count * * @mixin Eloquent */ @@ -74,13 +74,13 @@ class Location extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'zoomLevel' => 'int', - 'latitude' => 'float', - 'longitude' => 'float', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'zoomLevel' => 'int', + 'latitude' => 'float', + 'longitude' => 'float', + ]; protected $fillable = ['locatable_id', 'locatable_type', 'latitude', 'longitude', 'zoom_level']; @@ -101,11 +101,6 @@ class Location extends Model return $this->morphMany(Account::class, 'locatable'); } - public function transactionJournals(): MorphMany - { - return $this->morphMany(TransactionJournal::class, 'locatable'); - } - /** * Get all the owning attachable models. */ @@ -114,6 +109,11 @@ class Location extends Model return $this->morphTo(); } + public function transactionJournals(): MorphMany + { + return $this->morphMany(TransactionJournal::class, 'locatable'); + } + protected function locatableId(): Attribute { return Attribute::make( diff --git a/app/Models/Note.php b/app/Models/Note.php index d3238150da..d1fb218940 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -69,10 +69,10 @@ class Note extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; protected $fillable = ['title', 'text', 'noteable_id', 'noteable_type']; diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 912748415f..0ae5b7432d 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -92,15 +92,15 @@ class PiggyBank extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'startdate' => 'date', - 'targetdate' => 'date', - 'order' => 'int', - 'active' => 'boolean', - 'encrypted' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'startdate' => 'date', + 'targetdate' => 'date', + 'order' => 'int', + 'active' => 'boolean', + 'encrypted' => 'boolean', + ]; protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'targetdate', 'active']; diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index 75eef73b50..0fc4268c03 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -63,10 +63,10 @@ class PiggyBankEvent extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'date' => 'date', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'date' => 'date', + ]; protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'amount']; diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 5926e1a7bc..5ef34f96ad 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -64,11 +64,11 @@ class PiggyBankRepetition extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'startdate' => 'date', - 'targetdate' => 'date', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'startdate' => 'date', + 'targetdate' => 'date', + ]; protected $fillable = ['piggy_bank_id', 'startdate', 'targetdate', 'currentamount']; diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 005c9769f8..82758bf0b8 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -63,10 +63,10 @@ class Preference extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'data' => 'array', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'data' => 'array', + ]; protected $fillable = ['user_id', 'data', 'name']; diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index 7b9756c31b..e6a1d8fed1 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -104,19 +104,19 @@ class Recurrence extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'title' => 'string', - 'id' => 'int', - 'description' => 'string', - 'first_date' => 'date', - 'repeat_until' => 'date', - 'latest_date' => 'date', - 'repetitions' => 'int', - 'active' => 'bool', - 'apply_rules' => 'bool', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'title' => 'string', + 'id' => 'int', + 'description' => 'string', + 'first_date' => 'date', + 'repeat_until' => 'date', + 'latest_date' => 'date', + 'repetitions' => 'int', + 'active' => 'bool', + 'apply_rules' => 'bool', + ]; protected $fillable = ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active']; diff --git a/app/Models/RecurrenceMeta.php b/app/Models/RecurrenceMeta.php index d7bd0c060d..d3d34abf10 100644 --- a/app/Models/RecurrenceMeta.php +++ b/app/Models/RecurrenceMeta.php @@ -67,12 +67,12 @@ class RecurrenceMeta extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'name' => 'string', - 'value' => 'string', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'name' => 'string', + 'value' => 'string', + ]; protected $fillable = ['recurrence_id', 'name', 'value']; diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php index efcb09c1b4..c63a456434 100644 --- a/app/Models/RecurrenceRepetition.php +++ b/app/Models/RecurrenceRepetition.php @@ -76,14 +76,14 @@ class RecurrenceRepetition extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'repetition_type' => 'string', - 'repetition_moment' => 'string', - 'repetition_skip' => 'int', - 'weekend' => 'int', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'repetition_type' => 'string', + 'repetition_moment' => 'string', + 'repetition_skip' => 'int', + 'weekend' => 'int', + ]; protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip']; diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index f3bf0a5791..0d4364e8c7 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -91,25 +91,25 @@ class RecurrenceTransaction extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'amount' => 'string', - 'foreign_amount' => 'string', - 'description' => 'string', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'amount' => 'string', + 'foreign_amount' => 'string', + 'description' => 'string', + ]; protected $fillable = [ - 'recurrence_id', - 'transaction_currency_id', - 'foreign_currency_id', - 'source_id', - 'destination_id', - 'amount', - 'foreign_amount', - 'description', - ]; + 'recurrence_id', + 'transaction_currency_id', + 'foreign_currency_id', + 'source_id', + 'destination_id', + 'amount', + 'foreign_amount', + 'description', + ]; /** @var string The table to store the data in */ protected $table = 'recurrences_transactions'; diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php index e0a60d4163..6bb4eea372 100644 --- a/app/Models/RecurrenceTransactionMeta.php +++ b/app/Models/RecurrenceTransactionMeta.php @@ -67,12 +67,12 @@ class RecurrenceTransactionMeta extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'name' => 'string', - 'value' => 'string', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'name' => 'string', + 'value' => 'string', + ]; protected $fillable = ['rt_id', 'name', 'value']; diff --git a/app/Models/Role.php b/app/Models/Role.php index b356c6fc15..39a6216f1f 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -62,9 +62,9 @@ class Role extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; protected $fillable = ['name', 'display_name', 'description']; diff --git a/app/Models/Rule.php b/app/Models/Rule.php index 8ccc7c44bc..befb9640b1 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -95,15 +95,15 @@ class Rule extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'order' => 'int', - 'stop_processing' => 'boolean', - 'id' => 'int', - 'strict' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'order' => 'int', + 'stop_processing' => 'boolean', + 'id' => 'int', + 'strict' => 'boolean', + ]; protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id', 'strict']; diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index f8f8e5fe12..a8756a5d50 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -66,12 +66,12 @@ class RuleAction extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'active' => 'boolean', - 'order' => 'int', - 'stop_processing' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'active' => 'boolean', + 'order' => 'int', + 'stop_processing' => 'boolean', + ]; protected $fillable = ['rule_id', 'action_type', 'action_value', 'order', 'active', 'stop_processing']; diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 67e6c42f23..a363e72919 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -85,13 +85,13 @@ class RuleGroup extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'stop_processing' => 'boolean', - 'order' => 'int', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'stop_processing' => 'boolean', + 'order' => 'int', + ]; protected $fillable = ['user_id', 'user_group_id', 'stop_processing', 'order', 'title', 'description', 'active']; diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index e617ee8b21..6d14358ebd 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -66,12 +66,12 @@ class RuleTrigger extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'active' => 'boolean', - 'order' => 'int', - 'stop_processing' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'active' => 'boolean', + 'order' => 'int', + 'stop_processing' => 'boolean', + ]; protected $fillable = ['rule_id', 'trigger_type', 'trigger_value', 'order', 'active', 'stop_processing']; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 9ba92bc9f3..c5fa90814f 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -93,14 +93,14 @@ class Tag extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'date' => 'date', - 'zoomLevel' => 'int', - 'latitude' => 'float', - 'longitude' => 'float', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'date' => 'date', + 'zoomLevel' => 'int', + 'latitude' => 'float', + 'longitude' => 'float', + ]; protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'description', 'tagMode']; diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 974cc3d7a7..4626848073 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -99,28 +99,28 @@ class Transaction extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'identifier' => 'int', - 'encrypted' => 'boolean', // model does not have these fields though - 'bill_name_encrypted' => 'boolean', - 'reconciled' => 'boolean', - 'date' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'identifier' => 'int', + 'encrypted' => 'boolean', // model does not have these fields though + 'bill_name_encrypted' => 'boolean', + 'reconciled' => 'boolean', + 'date' => 'datetime', + ]; protected $fillable = [ - 'account_id', - 'transaction_journal_id', - 'description', - 'amount', - 'identifier', - 'transaction_currency_id', - 'foreign_currency_id', - 'foreign_amount', - 'reconciled', - ]; + 'account_id', + 'transaction_journal_id', + 'description', + 'amount', + 'identifier', + 'transaction_currency_id', + 'foreign_currency_id', + 'foreign_amount', + 'reconciled', + ]; protected $hidden = ['encrypted']; diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 886bc8a4c8..8d0c58d319 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -89,12 +89,12 @@ class TransactionCurrency extends Model public ?bool $userGroupEnabled; protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'decimal_places' => 'int', - 'enabled' => 'bool', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'decimal_places' => 'int', + 'enabled' => 'bool', + ]; protected $fillable = ['name', 'code', 'symbol', 'decimal_places', 'enabled']; diff --git a/app/Models/TransactionGroup.php b/app/Models/TransactionGroup.php index 76e4d64a82..6922513c49 100644 --- a/app/Models/TransactionGroup.php +++ b/app/Models/TransactionGroup.php @@ -78,13 +78,13 @@ class TransactionGroup extends Model protected $casts = [ - 'id' => 'integer', - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'title' => 'string', - 'date' => 'datetime', - ]; + 'id' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'title' => 'string', + 'date' => 'datetime', + ]; protected $fillable = ['user_id', 'user_group_id', 'title']; diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index b5e77ee825..fd26fbcc05 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -138,32 +138,32 @@ class TransactionJournal extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'date' => 'datetime', - 'interest_date' => 'date', - 'book_date' => 'date', - 'process_date' => 'date', - 'order' => 'int', - 'tag_count' => 'int', - 'encrypted' => 'boolean', - 'completed' => 'boolean', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'date' => 'datetime', + 'interest_date' => 'date', + 'book_date' => 'date', + 'process_date' => 'date', + 'order' => 'int', + 'tag_count' => 'int', + 'encrypted' => 'boolean', + 'completed' => 'boolean', + ]; protected $fillable = [ - 'user_id', - 'user_group_id', - 'transaction_type_id', - 'bill_id', - 'tag_count', - 'transaction_currency_id', - 'description', - 'completed', - 'order', - 'date', - ]; + 'user_id', + 'user_group_id', + 'transaction_type_id', + 'bill_id', + 'tag_count', + 'transaction_currency_id', + 'description', + 'completed', + 'order', + 'date', + ]; protected $hidden = ['encrypted']; diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index f3237f4664..01923e8318 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -71,9 +71,9 @@ class TransactionJournalLink extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; /** @var string The table to store the data in */ protected $table = 'journal_links'; diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index 2b94815cfa..767eccd314 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -69,10 +69,10 @@ class TransactionJournalMeta extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; protected $fillable = ['transaction_journal_id', 'name', 'data', 'hash']; diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 7e9c4551bc..e888294824 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -73,10 +73,10 @@ class TransactionType extends Model protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - ]; + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; protected $fillable = ['type']; /** diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index e173d47270..90ea3ceaf5 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -389,7 +389,7 @@ class AccountRepository implements AccountRepositoryInterface if (!in_array($type, $list, true)) { return null; } - $currencyId = (int) $this->getMetaValue($account, 'currency_id'); + $currencyId = (int)$this->getMetaValue($account, 'currency_id'); if ($currencyId > 0) { return TransactionCurrency::find($currencyId); } @@ -411,7 +411,7 @@ class AccountRepository implements AccountRepositoryInterface return null; } if (1 === $result->count()) { - return (string) $result->first()->data; + return (string)$result->first()->data; } return null; @@ -432,8 +432,8 @@ class AccountRepository implements AccountRepositoryInterface $info = $account->transactions()->get(['transaction_currency_id', 'foreign_currency_id'])->toArray(); $currencyIds = []; foreach ($info as $entry) { - $currencyIds[] = (int) $entry['transaction_currency_id']; - $currencyIds[] = (int) $entry['foreign_currency_id']; + $currencyIds[] = (int)$entry['transaction_currency_id']; + $currencyIds[] = (int)$entry['foreign_currency_id']; } $currencyIds = array_unique($currencyIds); @@ -456,14 +456,14 @@ class AccountRepository implements AccountRepositoryInterface AccountType::MORTGAGE => [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], ]; if (array_key_exists(ucfirst($type), $sets)) { - $order = (int) $this->getAccountsByType($sets[ucfirst($type)])->max('order'); + $order = (int)$this->getAccountsByType($sets[ucfirst($type)])->max('order'); app('log')->debug(sprintf('Return max order of "%s" set: %d', $type, $order)); return $order; } $specials = [AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION]; - $order = (int) $this->getAccountsByType($specials)->max('order'); + $order = (int)$this->getAccountsByType($specials)->max('order'); app('log')->debug(sprintf('Return max order of "%s" set (specials!): %d', $type, $order)); return $order; @@ -544,7 +544,7 @@ class AccountRepository implements AccountRepositoryInterface continue; } - if ($index !== (int) $account->order) { + if ($index !== (int)$account->order) { app('log')->debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order)); $account->order = $index; $account->save(); diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php index a4d3201ad9..000d4a0589 100644 --- a/app/Repositories/Account/AccountTasker.php +++ b/app/Repositories/Account/AccountTasker.php @@ -138,41 +138,6 @@ class AccountTasker implements AccountTaskerInterface return $report; } - /** - * @throws FireflyException - */ - 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; - } - - public function setUser(null|Authenticatable|User $user): void - { - if ($user instanceof User) { - $this->user = $user; - } - } - /** * @throws FireflyException */ @@ -233,6 +198,34 @@ class AccountTasker implements AccountTaskerInterface return $report; } + /** + * @throws FireflyException + */ + 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; + } + /** * @throws FireflyException */ @@ -291,4 +284,11 @@ class AccountTasker implements AccountTaskerInterface return $report; } + + public function setUser(null|Authenticatable|User $user): void + { + if ($user instanceof User) { + $this->user = $user; + } + } } diff --git a/app/Repositories/Account/OperationsRepository.php b/app/Repositories/Account/OperationsRepository.php index 71d33fb175..c4b8002d85 100644 --- a/app/Repositories/Account/OperationsRepository.php +++ b/app/Repositories/Account/OperationsRepository.php @@ -50,122 +50,6 @@ class OperationsRepository implements OperationsRepositoryInterface return $this->sortByCurrency($journals, 'negative'); } - public function setUser(null|Authenticatable|User $user): void - { - if ($user instanceof User) { - $this->user = $user; - } - } - - /** - * 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 - * as possible. Amounts are always positive. - */ - public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null): array - { - $journals = $this->getTransactions($start, $end, $accounts, TransactionType::DEPOSIT); - - return $this->sortByCurrency($journals, 'positive'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - public function sumExpenses( - 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->groupByCurrency($journals, 'negative'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - 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'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - 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'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - 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'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - 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'); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - 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'); - } - - 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 */ @@ -180,6 +64,13 @@ class OperationsRepository implements OperationsRepositoryInterface return $collector->getExtractedJournals(); } + public function setUser(null|Authenticatable|User $user): void + { + if ($user instanceof User) { + $this->user = $user; + } + } + private function sortByCurrency(array $journals, string $direction): array { $array = []; @@ -216,6 +107,33 @@ class OperationsRepository implements OperationsRepositoryInterface 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 + * as possible. Amounts are always positive. + */ + public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null): array + { + $journals = $this->getTransactions($start, $end, $accounts, TransactionType::DEPOSIT); + + return $this->sortByCurrency($journals, 'positive'); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function sumExpenses( + 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->groupByCurrency($journals, 'negative'); + } + /** * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -329,6 +247,21 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + 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'); + } + private function groupByDirection(array $journals, string $direction, string $method): array { $array = []; @@ -369,6 +302,73 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + 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'); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + 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'); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + 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'); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + 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'); + } + + 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); + } + private function groupByEither(array $journals): array { $return = []; diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 59d04874ab..d078d79432 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -240,7 +240,7 @@ class BillRepository implements BillRepositoryInterface /** @var null|Note $note */ $note = $bill->notes()->first(); - return (string) $note?->text; + return (string)$note?->text; } public function getOverallAverage(Bill $bill): array @@ -257,7 +257,7 @@ class BillRepository implements BillRepositoryInterface foreach ($journals as $journal) { /** @var Transaction $transaction */ $transaction = $journal->transactions()->where('amount', '<', 0)->first(); - $currencyId = (int) $journal->transaction_currency_id; + $currencyId = (int)$journal->transaction_currency_id; $currency = $journal->transactionCurrency; $result[$currencyId] ??= [ 'sum' => '0', @@ -278,7 +278,7 @@ class BillRepository implements BillRepositoryInterface * @var array $arr */ foreach ($result as $currencyId => $arr) { - $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string)$arr['count']); } return $result; @@ -380,7 +380,7 @@ class BillRepository implements BillRepositoryInterface if (null === $transaction) { continue; } - $currencyId = (int) $journal->transaction_currency_id; + $currencyId = (int)$journal->transaction_currency_id; $currency = $journal->transactionCurrency; $result[$currencyId] ??= [ 'sum' => '0', @@ -401,7 +401,7 @@ class BillRepository implements BillRepositoryInterface * @var array $arr */ foreach ($result as $currencyId => $arr) { - $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string)$arr['count']); } return $result; @@ -414,7 +414,7 @@ class BillRepository implements BillRepositoryInterface { /** @var Transaction $transaction */ foreach ($transactions as $transaction) { - $journal = $bill->user->transactionJournals()->find((int) $transaction['transaction_journal_id']); + $journal = $bill->user->transactionJournals()->find((int)$transaction['transaction_journal_id']); $journal->bill_id = $bill->id; $journal->save(); app('log')->debug(sprintf('Linked journal #%d to bill #%d', $journal->id, $bill->id)); @@ -516,7 +516,7 @@ class BillRepository implements BillRepositoryInterface $currency = $bill->transactionCurrency; $return[$currency->id] ??= [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => $currency->name, 'symbol' => $currency->symbol, 'code' => $currency->code, @@ -530,9 +530,9 @@ class BillRepository implements BillRepositoryInterface $sourceTransaction = $transactionJournal->transactions()->where('amount', '<', 0)->first(); if (null !== $sourceTransaction) { $amount = $sourceTransaction->amount; - if ((int) $sourceTransaction->foreign_currency_id === $currency->id) { + if ((int)$sourceTransaction->foreign_currency_id === $currency->id) { // use foreign amount instead! - $amount = (string) $sourceTransaction->foreign_amount; + $amount = (string)$sourceTransaction->foreign_amount; } $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount); } @@ -570,14 +570,14 @@ class BillRepository implements BillRepositoryInterface $currency = $bill->transactionCurrency; $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); $return[$currency->id] ??= [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => $currency->name, 'symbol' => $currency->symbol, 'code' => $currency->code, 'decimal_places' => $currency->decimal_places, 'sum' => '0', ]; - $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], bcmul($average, (string) $total)); + $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], bcmul($average, (string)$total)); } } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 8cc0b7b851..6f8fcf45b5 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -39,7 +39,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Services\Internal\Destroy\BudgetDestroyService; -use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Http\Api\ExchangeRateConverter; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; @@ -104,12 +103,12 @@ class BudgetRepository implements BudgetRepositoryInterface $rate = $converter->getCurrencyRate($currency, $defaultCurrency, $end); $currencyCode = $currency->code; $return[$currencyCode] ??= [ - 'currency_id' => (string) $currency->id, + 'currency_id' => (string)$currency->id, 'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol, 'currency_code' => $currency->code, 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => (string) $defaultCurrency->id, + 'native_currency_id' => (string)$defaultCurrency->id, 'native_currency_name' => $defaultCurrency->name, 'native_currency_symbol' => $defaultCurrency->symbol, 'native_currency_code' => $defaultCurrency->code, @@ -135,13 +134,13 @@ class BudgetRepository implements BudgetRepositoryInterface } $total = $limit->start_date->diffInDays($limit->end_date) + 1; // include the day itself. $days = $this->daysInOverlap($limit, $start, $end); - $amount = bcmul(bcdiv($limit->amount, (string) $total), (string) $days); + $amount = bcmul(bcdiv($limit->amount, (string)$total), (string)$days); $return[$currencyCode]['sum'] = bcadd($return[$currencyCode]['sum'], $amount); $return[$currencyCode]['native_sum'] = bcmul($rate, $return[$currencyCode]['sum']); app('log')->debug( sprintf( 'Amount per day: %s (%s over %d days). Total amount for %d days: %s', - bcdiv($limit->amount, (string) $total), + bcdiv($limit->amount, (string)$total), $limit->amount, $total, $days, @@ -170,6 +169,40 @@ class BudgetRepository implements BudgetRepositoryInterface ; } + /** + * How many days of this budget limit are between start and end? + */ + 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; + } + public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array { app('log')->debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d'))); @@ -187,7 +220,7 @@ class BudgetRepository implements BudgetRepositoryInterface app('log')->debug(sprintf('Budget limit #%d', $limit->id)); $currency = $limit->transactionCurrency; $return[$currency->id] ??= [ - 'id' => (string) $currency->id, + 'id' => (string)$currency->id, 'name' => $currency->name, 'symbol' => $currency->symbol, 'code' => $currency->code, @@ -210,12 +243,12 @@ class BudgetRepository implements BudgetRepositoryInterface } $total = $limit->start_date->diffInDays($limit->end_date) + 1; // include the day itself. $days = $this->daysInOverlap($limit, $start, $end); - $amount = bcmul(bcdiv($limit->amount, (string) $total), (string) $days); + $amount = bcmul(bcdiv($limit->amount, (string)$total), (string)$days); $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount); app('log')->debug( sprintf( 'Amount per day: %s (%s over %d days). Total amount for %d days: %s', - bcdiv($limit->amount, (string) $total), + bcdiv($limit->amount, (string)$total), $limit->amount, $total, $days, @@ -264,7 +297,7 @@ class BudgetRepository implements BudgetRepositoryInterface $budget->active = $data['active']; } if (array_key_exists('notes', $data)) { - $this->setNoteText($budget, (string) $data['notes']); + $this->setNoteText($budget, (string)$data['notes']); } $budget->save(); @@ -291,11 +324,115 @@ class BudgetRepository implements BudgetRepositoryInterface return $budget; } + 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.*']) + ; + app('log')->debug(sprintf('Found %d actions to update.', $actions->count())); + + /** @var RuleAction $action */ + foreach ($actions as $action) { + $action->action_value = $newName; + $action->save(); + app('log')->debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); + } + } + + 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.*']) + ; + app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count())); + + /** @var RuleTrigger $trigger */ + foreach ($triggers as $trigger) { + $trigger->trigger_value = $newName; + $trigger->save(); + app('log')->debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); + } + } + + 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(); + } + } + public function getAutoBudget(Budget $budget): ?AutoBudget { return $budget->autoBudgets()->first(); } + /** + * @throws FireflyException + */ + private function updateAutoBudget(Budget $budget, array $data): void + { + // update or create auto-budget: + $autoBudget = $this->getAutoBudget($budget); + + // grab default currency: + $currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); + + 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)) { + /** @var CurrencyRepositoryInterface $repos */ + $repos = app(CurrencyRepositoryInterface::class); + $currencyId = (int)($data['currency_id'] ?? 0); + $currencyCode = (string)($data['currency_code'] ?? ''); + $currency = $repos->find($currencyId); + if (null === $currency) { + $currency = $repos->findByCode($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 * @@ -326,8 +463,8 @@ class BudgetRepository implements BudgetRepositoryInterface foreach ($budgets as $budget) { \DB::table('budget_transaction')->where('budget_id', $budget->id)->delete(); \DB::table('budget_transaction_journal')->where('budget_id', $budget->id)->delete(); - RecurrenceTransactionMeta::where('name', 'budget_id')->where('value', (string) $budget->id)->delete(); - RuleAction::where('action_type', 'set_budget')->where('action_value', (string) $budget->id)->delete(); + RecurrenceTransactionMeta::where('name', 'budget_id')->where('value', (string)$budget->id)->delete(); + RuleAction::where('action_type', 'set_budget')->where('action_value', (string)$budget->id)->delete(); $budget->delete(); } Log::channel('audit')->info('Delete all budgets through destroyAll'); @@ -352,7 +489,7 @@ class BudgetRepository implements BudgetRepositoryInterface { app('log')->debug('Now in findBudget()'); app('log')->debug(sprintf('Searching for budget with ID #%d...', $budgetId)); - $result = $this->find((int) $budgetId); + $result = $this->find((int)$budgetId); if (null === $result && null !== $budgetName && '' !== $budgetName) { app('log')->debug(sprintf('Searching for budget with name %s...', $budgetName)); $result = $this->findByName($budgetName); @@ -488,9 +625,9 @@ class BudgetRepository implements BudgetRepositoryInterface $array = []; foreach ($journals as $journal) { - $currencyId = (int) $journal['currency_id']; + $currencyId = (int)$journal['currency_id']; $array[$currencyId] ??= [ - 'id' => (string) $currencyId, + 'id' => (string)$currencyId, 'name' => $journal['currency_name'], 'symbol' => $journal['currency_symbol'], 'code' => $journal['currency_code'], @@ -500,10 +637,10 @@ class BudgetRepository implements BudgetRepositoryInterface $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'])); // also do foreign amount: - $foreignId = (int) $journal['foreign_currency_id']; + $foreignId = (int)$journal['foreign_currency_id']; if (0 !== $foreignId) { $array[$foreignId] ??= [ - 'id' => (string) $foreignId, + 'id' => (string)$foreignId, 'name' => $journal['foreign_currency_name'], 'symbol' => $journal['foreign_currency_symbol'], 'code' => $journal['foreign_currency_code'], @@ -550,9 +687,9 @@ class BudgetRepository implements BudgetRepositoryInterface $array = []; foreach ($journals as $journal) { - $currencyId = (int) $journal['currency_id']; + $currencyId = (int)$journal['currency_id']; $array[$currencyId] ??= [ - 'id' => (string) $currencyId, + 'id' => (string)$currencyId, 'name' => $journal['currency_name'], 'symbol' => $journal['currency_symbol'], 'code' => $journal['currency_code'], @@ -562,10 +699,10 @@ class BudgetRepository implements BudgetRepositoryInterface $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'])); // also do foreign amount: - $foreignId = (int) $journal['foreign_currency_id']; + $foreignId = (int)$journal['foreign_currency_id']; if (0 !== $foreignId) { $array[$foreignId] ??= [ - 'id' => (string) $foreignId, + 'id' => (string)$foreignId, 'name' => $journal['foreign_currency_name'], 'symbol' => $journal['foreign_currency_symbol'], 'code' => $journal['foreign_currency_code'], @@ -607,7 +744,7 @@ class BudgetRepository implements BudgetRepositoryInterface // set notes if (array_key_exists('notes', $data)) { - $this->setNoteText($newBudget, (string) $data['notes']); + $this->setNoteText($newBudget, (string)$data['notes']); } if (!array_key_exists('auto_budget_type', $data) || !array_key_exists('auto_budget_amount', $data) || !array_key_exists('auto_budget_period', $data)) { @@ -635,10 +772,10 @@ class BudgetRepository implements BudgetRepositoryInterface $repos = app(CurrencyRepositoryInterface::class); $currency = null; if (array_key_exists('currency_id', $data)) { - $currency = $repos->find((int) $data['currency_id']); + $currency = $repos->find((int)$data['currency_id']); } if (array_key_exists('currency_code', $data)) { - $currency = $repos->findByCode((string) $data['currency_code']); + $currency = $repos->findByCode((string)$data['currency_code']); } if (null === $currency) { $currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); @@ -674,144 +811,6 @@ class BudgetRepository implements BudgetRepositoryInterface public function getMaxOrder(): int { - return (int) $this->user->budgets()->max('order'); - } - - /** - * How many days of this budget limit are between start and end? - */ - 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; - } - - 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.*']) - ; - app('log')->debug(sprintf('Found %d actions to update.', $actions->count())); - - /** @var RuleAction $action */ - foreach ($actions as $action) { - $action->action_value = $newName; - $action->save(); - app('log')->debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); - } - } - - 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.*']) - ; - app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count())); - - /** @var RuleTrigger $trigger */ - foreach ($triggers as $trigger) { - $trigger->trigger_value = $newName; - $trigger->save(); - app('log')->debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); - } - } - - 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(); - } - } - - /** - * @throws FireflyException - */ - private function updateAutoBudget(Budget $budget, array $data): void - { - // update or create auto-budget: - $autoBudget = $this->getAutoBudget($budget); - - // grab default currency: - $currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); - - 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)) { - /** @var CurrencyRepositoryInterface $repos */ - $repos = app(CurrencyRepositoryInterface::class); - $currencyId = (int) ($data['currency_id'] ?? 0); - $currencyCode = (string) ($data['currency_code'] ?? ''); - $currency = $repos->find($currencyId); - if (null === $currency) { - $currency = $repos->findByCode($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(); + return (int)$this->user->budgets()->max('order'); } } diff --git a/app/Repositories/Budget/OperationsRepository.php b/app/Repositories/Budget/OperationsRepository.php index f0e3fc132a..b33ff50748 100644 --- a/app/Repositories/Budget/OperationsRepository.php +++ b/app/Repositories/Budget/OperationsRepository.php @@ -189,6 +189,14 @@ class OperationsRepository implements OperationsRepositoryInterface } } + private function getBudgets(): Collection + { + /** @var BudgetRepositoryInterface $repos */ + $repos = app(BudgetRepositoryInterface::class); + + return $repos->getActiveBudgets(); + } + /** * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -288,12 +296,4 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - - 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 f9161c4735..c4312c833c 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -210,6 +210,34 @@ class CategoryRepository implements CategoryRepositoryInterface return $firstJournalDate; } + 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; + } + + 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; + } + public function getAttachments(Category $category): Collection { $set = $category->attachments()->get(); @@ -271,56 +299,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $lastJournalDate; } - 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(); - } - - /** - * @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); - } - - 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; - } - - 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; - } - private function getLastJournalDate(Category $category, Collection $accounts): ?Carbon { $query = $category->transactionJournals()->orderBy('date', 'DESC'); @@ -361,4 +339,26 @@ class CategoryRepository implements CategoryRepositoryInterface return null; } + + 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(); + } + + /** + * @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 1da423c8b0..d74749f2df 100644 --- a/app/Repositories/Category/NoCategoryRepository.php +++ b/app/Repositories/Category/NoCategoryRepository.php @@ -75,9 +75,9 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface $journalId = (int)$journal['transaction_journal_id']; $array[$currencyId]['categories'][0]['transaction_journals'][$journalId] = [ - 'amount' => app('steam')->negative($journal['amount']), - 'date' => $journal['date'], - ]; + 'amount' => app('steam')->negative($journal['amount']), + 'date' => $journal['date'], + ]; } return $array; @@ -128,9 +128,9 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface $journalId = (int)$journal['transaction_journal_id']; $array[$currencyId]['categories'][0]['transaction_journals'][$journalId] = [ - 'amount' => app('steam')->positive($journal['amount']), - 'date' => $journal['date'], - ]; + 'amount' => app('steam')->positive($journal['amount']), + 'date' => $journal['date'], + ]; } return $array; diff --git a/app/Repositories/Category/OperationsRepository.php b/app/Repositories/Category/OperationsRepository.php index f814f027d1..b1f17d184f 100644 --- a/app/Repositories/Category/OperationsRepository.php +++ b/app/Repositories/Category/OperationsRepository.php @@ -115,6 +115,14 @@ class OperationsRepository implements OperationsRepositoryInterface } } + /** + * Returns a list of all the categories belonging to a user. + */ + 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 @@ -420,12 +428,4 @@ class OperationsRepository implements OperationsRepositoryInterface return $array; } - - /** - * Returns a list of all the categories belonging to a user. - */ - private function getCategories(): Collection - { - return $this->user->categories()->get(); - } } diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index a84b87db97..7a438ee7fd 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -245,6 +245,25 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface ; } + /** + * @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(); + } + public function switchLinkById(int $linkId): bool { /** @var null|TransactionJournalLink $link */ @@ -293,23 +312,4 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $journalLink; } - - /** - * @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/PiggyBank/ModifiesPiggyBanks.php b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php index 3bde670619..0fbf31de71 100644 --- a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php +++ b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php @@ -279,6 +279,25 @@ trait ModifiesPiggyBanks return true; } + 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; + } + public function update(PiggyBank $piggyBank, array $data): PiggyBank { $piggyBank = $this->updateProperties($piggyBank, $data); @@ -339,25 +358,6 @@ trait ModifiesPiggyBanks return $piggyBank; } - 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; - } - private function updateProperties(PiggyBank $piggyBank, array $data): PiggyBank { if (array_key_exists('name', $data) && '' !== $data['name']) { diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index ec301f9134..efd4f9eaa3 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -400,6 +400,21 @@ class RecurringRepository implements RecurringRepositoryInterface return $this->filterMaxDate($repeatUntil, $occurrences); } + 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. * @@ -555,19 +570,4 @@ class RecurringRepository implements RecurringRepositoryInterface return $service->update($recurrence, $data); } - - 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 7cf48e232a..2d3ef6d7d9 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -280,6 +280,26 @@ class RuleRepository implements RuleRepositoryInterface return $this->user->rules()->find($ruleId); } + private function setRuleTrigger(string $moment, Rule $rule): void + { + /** @var null|RuleTrigger $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(); + } + public function resetRuleOrder(RuleGroup $ruleGroup): bool { $groupRepository = app(RuleGroupRepositoryInterface::class); @@ -336,6 +356,59 @@ class RuleRepository implements RuleRepositoryInterface return (int)$ruleGroup->rules()->max('order'); } + 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); + } + + // empty the value in case the rule needs no context + // TODO create a helper to automatically return these. + $needTrue = [ + 'reconciled', + 'has_attachments', + 'has_any_category', + 'has_any_budget', + 'has_any_bill', + 'has_any_tag', + 'any_notes', + 'any_external_url', + 'has_no_attachments', + 'has_no_category', + 'has_no_budget', + 'has_no_bill', + 'has_no_tag', + 'no_notes', + 'no_external_url', + 'source_is_cash', + 'destination_is_cash', + 'account_is_cash', + 'exists', + 'no_external_id', + 'any_external_id', + ]; + if (in_array($type, $needTrue, true)) { + $value = ''; + } + + $triggerValues = [ + 'action' => $type, + 'value' => $value, + 'stop_processing' => $stopProcessing, + 'order' => $order, + 'active' => $active, + ]; + $this->storeTrigger($rule, $triggerValues); + ++$order; + } + } + public function storeTrigger(Rule $rule, array $values): RuleTrigger { $ruleTrigger = new RuleTrigger(); @@ -350,6 +423,25 @@ class RuleRepository implements RuleRepositoryInterface return $ruleTrigger; } + 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; + } + } + public function storeAction(Rule $rule, array $values): RuleAction { $ruleAction = new RuleAction(); @@ -426,96 +518,4 @@ class RuleRepository implements RuleRepositoryInterface return $rule; } - - private function setRuleTrigger(string $moment, Rule $rule): void - { - /** @var null|RuleTrigger $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(); - } - - 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); - } - - // empty the value in case the rule needs no context - // TODO create a helper to automatically return these. - $needTrue = [ - 'reconciled', - 'has_attachments', - 'has_any_category', - 'has_any_budget', - 'has_any_bill', - 'has_any_tag', - 'any_notes', - 'any_external_url', - 'has_no_attachments', - 'has_no_category', - 'has_no_budget', - 'has_no_bill', - 'has_no_tag', - 'no_notes', - 'no_external_url', - 'source_is_cash', - 'destination_is_cash', - 'account_is_cash', - 'exists', - 'no_external_id', - 'any_external_id', - ]; - if (in_array($type, $needTrue, true)) { - $value = ''; - } - - $triggerValues = [ - 'action' => $type, - 'value' => $value, - 'stop_processing' => $stopProcessing, - 'order' => $order, - 'active' => $active, - ]; - $this->storeTrigger($rule, $triggerValues); - ++$order; - } - } - - 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; - } - } } diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index eb1d3961e1..dcfc689bfd 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -151,6 +151,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return true; } + 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 ($action->order !== $index) { + $action->order = $index; + $action->save(); + app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); + } + ++$index; + } + } + + 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 = $trigger->order; + if ($order !== $index) { + $trigger->order = $index; + $trigger->save(); + app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); + } + ++$index; + } + } + public function destroyAll(): void { Log::channel('audit')->info('Delete all rule groups through destroyAll'); @@ -412,47 +455,4 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $ruleGroup; } - - 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 ($action->order !== $index) { - $action->order = $index; - $action->save(); - app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); - } - ++$index; - } - } - - 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 = $trigger->order; - if ($order !== $index) { - $trigger->order = $index; - $trigger->save(); - app('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 e369b9df46..334bf8009d 100644 --- a/app/Repositories/Tag/OperationsRepository.php +++ b/app/Repositories/Tag/OperationsRepository.php @@ -120,6 +120,14 @@ class OperationsRepository implements OperationsRepositoryInterface } } + private function getTags(): Collection + { + /** @var TagRepositoryInterface $repository */ + $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 @@ -215,12 +223,4 @@ class OperationsRepository implements OperationsRepositoryInterface { throw new FireflyException(sprintf('%s is not yet implemented.', __METHOD__)); } - - private function getTags(): Collection - { - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - - return $repository->get(); - } } diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 4a9b73f028..052487081d 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -259,7 +259,7 @@ class TagRepository implements TagRepositoryInterface if (false === $found) { continue; } - $currencyId = (int) $journal['currency_id']; + $currencyId = (int)$journal['currency_id']; $sums[$currencyId] ??= [ 'currency_id' => $currencyId, 'currency_name' => $journal['currency_name'], @@ -273,7 +273,7 @@ class TagRepository implements TagRepositoryInterface ]; // add amount to correct type: - $amount = app('steam')->positive((string) $journal['amount']); + $amount = app('steam')->positive((string)$journal['amount']); $type = $journal['transaction_type_type']; if (TransactionType::WITHDRAWAL === $type) { $amount = bcmul($amount, '-1'); @@ -294,7 +294,7 @@ class TagRepository implements TagRepositoryInterface TransactionType::OPENING_BALANCE => '0', ]; // add foreign amount to correct type: - $amount = app('steam')->positive((string) $journal['foreign_amount']); + $amount = app('steam')->positive((string)$journal['foreign_amount']); if (TransactionType::WITHDRAWAL === $type) { $amount = bcmul($amount, '-1'); } diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index 3f056789aa..0d79d53085 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -90,6 +90,46 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $result; } + 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; + } + + 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; + } + /** * Return all attachments for all journals in the group. */ @@ -201,6 +241,48 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $return; } + 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; + } + + 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; + } + public function getLocation(int $journalId): ?Location { /** @var TransactionJournal $journal */ @@ -351,86 +433,4 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $service->update($transactionGroup, $data); } - - 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; - } - - 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; - } - - 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; - } - - 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/UserGroup/UserGroupRepository.php b/app/Repositories/UserGroup/UserGroupRepository.php index eae9cd69b7..e27805fc2c 100644 --- a/app/Repositories/UserGroup/UserGroupRepository.php +++ b/app/Repositories/UserGroup/UserGroupRepository.php @@ -112,6 +112,34 @@ class UserGroupRepository implements UserGroupRepositoryInterface return $collection; } + /** + * Because there is the chance that a group with this name already exists, + * Firefly III runs a little loop of combinations to make sure the group name is unique. + */ + private function createNewUserGroup(User $user): UserGroup + { + $loop = 0; + $groupName = $user->email; + $exists = true; + $existingGroup = null; + while ($exists && $loop < 10) { + $existingGroup = $this->findByName($groupName); + if (null === $existingGroup) { + $exists = false; + + /** @var null|UserGroup $existingGroup */ + $existingGroup = $this->store(['user' => $user, 'title' => $groupName]); + } + if (null !== $existingGroup) { + // group already exists + $groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999).microtime()), 0, 4)); + } + ++$loop; + } + + return $existingGroup; + } + public function findByName(string $title): ?UserGroup { return UserGroup::whereTitle($title)->first(); @@ -213,8 +241,8 @@ class UserGroupRepository implements UserGroupRepositoryInterface if ( 0 === $ownerCount && (0 === count($data['roles']) - || (count($data['roles']) > 0 // @phpstan-ignore-line - && !in_array(UserRoleEnum::OWNER->value, $data['roles'], true)))) { + || (count($data['roles']) > 0 // @phpstan-ignore-line + && !in_array(UserRoleEnum::OWNER->value, $data['roles'], true)))) { app('log')->debug('User needs to keep owner role in this group, refuse to act'); throw new FireflyException('The last owner in this user group must keep the "owner" role.'); @@ -239,34 +267,6 @@ class UserGroupRepository implements UserGroupRepositoryInterface return $userGroup; } - /** - * Because there is the chance that a group with this name already exists, - * Firefly III runs a little loop of combinations to make sure the group name is unique. - */ - private function createNewUserGroup(User $user): UserGroup - { - $loop = 0; - $groupName = $user->email; - $exists = true; - $existingGroup = null; - while ($exists && $loop < 10) { - $existingGroup = $this->findByName($groupName); - if (null === $existingGroup) { - $exists = false; - - /** @var null|UserGroup $existingGroup */ - $existingGroup = $this->store(['user' => $user, 'title' => $groupName]); - } - if (null !== $existingGroup) { - // group already exists - $groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999).microtime()), 0, 4)); - } - ++$loop; - } - - return $existingGroup; - } - private function simplifyListByName(array $roles): array { if (in_array(UserRoleEnum::OWNER->value, $roles, true)) { diff --git a/app/Repositories/UserGroups/Bill/BillRepository.php b/app/Repositories/UserGroups/Bill/BillRepository.php index 6e36453002..a6d2be535b 100644 --- a/app/Repositories/UserGroups/Bill/BillRepository.php +++ b/app/Repositories/UserGroups/Bill/BillRepository.php @@ -81,18 +81,18 @@ class BillRepository implements BillRepositoryInterface $currencyId = $bill->transaction_currency_id; $return[$currencyId] ??= [ - 'currency_id' => (string)$currency->id, - 'currency_name' => $currency->name, - 'currency_symbol' => $currency->symbol, - 'currency_code' => $currency->code, - 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => (string)$default->id, - 'native_currency_name' => $default->name, - 'native_currency_symbol' => $default->symbol, - 'native_currency_code' => $default->code, - 'native_currency_decimal_places' => $default->decimal_places, - 'sum' => '0', - 'native_sum' => '0', + 'currency_id' => (string)$currency->id, + 'currency_name' => $currency->name, + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + 'native_currency_id' => (string)$default->id, + 'native_currency_name' => $default->name, + 'native_currency_symbol' => $default->symbol, + 'native_currency_code' => $default->code, + 'native_currency_decimal_places' => $default->decimal_places, + 'sum' => '0', + 'native_sum' => '0', ]; /** @var TransactionJournal $transactionJournal */ @@ -154,18 +154,18 @@ class BillRepository implements BillRepositoryInterface $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); $nativeAverage = $converter->convert($currency, $default, $start, $average); $return[$currencyId] ??= [ - 'currency_id' => (string)$currency->id, - 'currency_name' => $currency->name, - 'currency_symbol' => $currency->symbol, - 'currency_code' => $currency->code, - 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => (string)$default->id, - 'native_currency_name' => $default->name, - 'native_currency_symbol' => $default->symbol, - 'native_currency_code' => $default->code, - 'native_currency_decimal_places' => $default->decimal_places, - 'sum' => '0', - 'native_sum' => '0', + 'currency_id' => (string)$currency->id, + 'currency_name' => $currency->name, + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + 'native_currency_id' => (string)$default->id, + 'native_currency_name' => $default->name, + 'native_currency_symbol' => $default->symbol, + 'native_currency_code' => $default->code, + 'native_currency_decimal_places' => $default->decimal_places, + 'sum' => '0', + 'native_sum' => '0', ]; $return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], bcmul($average, (string)$total)); $return[$currencyId]['native_sum'] = bcadd($return[$currencyId]['native_sum'], bcmul($nativeAverage, (string)$total)); diff --git a/app/Repositories/UserGroups/Currency/CurrencyRepository.php b/app/Repositories/UserGroups/Currency/CurrencyRepository.php index b0a8199ffb..5f66f4dd0d 100644 --- a/app/Repositories/UserGroups/Currency/CurrencyRepository.php +++ b/app/Repositories/UserGroups/Currency/CurrencyRepository.php @@ -150,6 +150,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface return null; } + private 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(); + } + /** * Returns ALL currencies, regardless of whether they are enabled or not. */ @@ -372,12 +380,4 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $service->update($currency, $data); } - - private 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(); - } } diff --git a/app/Rules/BelongsUser.php b/app/Rules/BelongsUser.php index 8957d4f141..06e250cf63 100644 --- a/app/Rules/BelongsUser.php +++ b/app/Rules/BelongsUser.php @@ -65,32 +65,6 @@ class BelongsUser implements ValidationRule } } - protected function countField(string $class, string $field, string $value): int - { - $value = trim($value); - $objects = []; - // get all objects belonging to user: - if (PiggyBank::class === $class) { - $objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') - ->where('accounts.user_id', '=', auth()->user()->id)->get(['piggy_banks.*']) - ; - } - if (PiggyBank::class !== $class) { - $objects = $class::where('user_id', '=', auth()->user()->id)->get(); - } - $count = 0; - foreach ($objects as $object) { - $objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line - app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value)); - if ($objectValue === $value) { - ++$count; - app('log')->debug(sprintf('Hit! Count is now %d', $count)); - } - } - - return $count; - } - private function parseAttribute(string $attribute): string { $parts = explode('.', $attribute); @@ -121,6 +95,32 @@ class BelongsUser implements ValidationRule return 1 === $count; } + protected function countField(string $class, string $field, string $value): int + { + $value = trim($value); + $objects = []; + // get all objects belonging to user: + if (PiggyBank::class === $class) { + $objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') + ->where('accounts.user_id', '=', auth()->user()->id)->get(['piggy_banks.*']) + ; + } + if (PiggyBank::class !== $class) { + $objects = $class::where('user_id', '=', auth()->user()->id)->get(); + } + $count = 0; + foreach ($objects as $object) { + $objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line + app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value)); + if ($objectValue === $value) { + ++$count; + app('log')->debug(sprintf('Hit! Count is now %d', $count)); + } + } + + return $count; + } + private function validateBillId(int $value): bool { if (0 === $value) { diff --git a/app/Rules/BelongsUserGroup.php b/app/Rules/BelongsUserGroup.php index e43d2ce426..5c0459705b 100644 --- a/app/Rules/BelongsUserGroup.php +++ b/app/Rules/BelongsUserGroup.php @@ -78,32 +78,6 @@ class BelongsUserGroup implements ValidationRule } } - protected function countField(string $class, string $field, string $value): int - { - $value = trim($value); - $objects = []; - // get all objects belonging to user: - if (PiggyBank::class === $class) { - $objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') - ->where('accounts.user_group_id', '=', $this->userGroup->id)->get(['piggy_banks.*']) - ; - } - if (PiggyBank::class !== $class) { - $objects = $class::where('user_group_id', '=', $this->userGroup->id)->get(); - } - $count = 0; - foreach ($objects as $object) { - $objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line - app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value)); - if ($objectValue === $value) { - ++$count; - app('log')->debug(sprintf('Hit! Count is now %d', $count)); - } - } - - return $count; - } - private function parseAttribute(string $attribute): string { $parts = explode('.', $attribute); @@ -134,6 +108,32 @@ class BelongsUserGroup implements ValidationRule return 1 === $count; } + protected function countField(string $class, string $field, string $value): int + { + $value = trim($value); + $objects = []; + // get all objects belonging to user: + if (PiggyBank::class === $class) { + $objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') + ->where('accounts.user_group_id', '=', $this->userGroup->id)->get(['piggy_banks.*']) + ; + } + if (PiggyBank::class !== $class) { + $objects = $class::where('user_group_id', '=', $this->userGroup->id)->get(); + } + $count = 0; + foreach ($objects as $object) { + $objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line + app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value)); + if ($objectValue === $value) { + ++$count; + app('log')->debug(sprintf('Hit! Count is now %d', $count)); + } + } + + return $count; + } + private function validateBillId(int $value): bool { if (0 === $value) { diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php index 1456c386e9..566737e86e 100644 --- a/app/Rules/IsValidAttachmentModel.php +++ b/app/Rules/IsValidAttachmentModel.php @@ -57,6 +57,15 @@ class IsValidAttachmentModel implements ValidationRule $this->model = $model; } + private function normalizeModel(string $model): string + { + $search = ['FireflyIII\Models\\']; + $replace = ''; + $model = str_replace($search, $replace, $model); + + return sprintf('FireflyIII\Models\%s', $model); + } + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -84,15 +93,6 @@ class IsValidAttachmentModel implements ValidationRule } } - private function normalizeModel(string $model): string - { - $search = ['FireflyIII\Models\\']; - $replace = ''; - $model = str_replace($search, $replace, $model); - - return sprintf('FireflyIII\Models\%s', $model); - } - private function validateAccount(int $value): bool { /** @var AccountRepositoryInterface $repository */ diff --git a/app/Rules/UniqueAccountNumber.php b/app/Rules/UniqueAccountNumber.php index 7b6e8b4cc4..1b5cb68d8e 100644 --- a/app/Rules/UniqueAccountNumber.php +++ b/app/Rules/UniqueAccountNumber.php @@ -64,7 +64,7 @@ class UniqueAccountNumber implements ValidationRule */ public function message(): string { - return (string) trans('validation.unique_account_number_for_user'); + return (string)trans('validation.unique_account_number_for_user'); } /** diff --git a/app/Rules/UniqueIban.php b/app/Rules/UniqueIban.php index 3bc00be090..6df894dfd4 100644 --- a/app/Rules/UniqueIban.php +++ b/app/Rules/UniqueIban.php @@ -146,10 +146,10 @@ class UniqueIban implements ValidationRule } $query = auth()->user() - ->accounts() - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where('accounts.iban', $iban) - ->whereIn('account_types.type', $typesArray) + ->accounts() + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->where('accounts.iban', $iban) + ->whereIn('account_types.type', $typesArray) ; if (null !== $this->account) { diff --git a/app/Services/FireflyIIIOrg/Update/UpdateRequest.php b/app/Services/FireflyIIIOrg/Update/UpdateRequest.php index 770a177e5b..d45a9ff781 100644 --- a/app/Services/FireflyIIIOrg/Update/UpdateRequest.php +++ b/app/Services/FireflyIIIOrg/Update/UpdateRequest.php @@ -135,8 +135,8 @@ class UpdateRequest implements UpdateRequestInterface private function parseResult(array $information): array { app('log')->debug('Now in parseResult()', $information); - $current = (string)config('firefly.version'); - $latest = (string)$information['version']; + $current = (string)config('firefly.version'); + $latest = (string)$information['version']; // strip the 'v' from the version if it's there. if (str_starts_with($latest, 'v')) { @@ -146,7 +146,7 @@ class UpdateRequest implements UpdateRequestInterface return $this->parseResultDevelop($current, $latest, $information); } - $compare = version_compare($latest, $current); + $compare = version_compare($latest, $current); app('log')->debug(sprintf('Current version is "%s", latest is "%s", result is: %d', $current, $latest, $compare)); @@ -161,9 +161,9 @@ class UpdateRequest implements UpdateRequestInterface // a newer version is available! /** @var Carbon $released */ - $released = $information['date']; - $isBeta = $information['is_beta'] ?? false; - $isAlpha = $information['is_alpha'] ?? false; + $released = $information['date']; + $isBeta = $information['is_beta'] ?? false; + $isAlpha = $information['is_alpha'] ?? false; // it's new but alpha: if (true === $isAlpha) { diff --git a/app/Services/Internal/Destroy/AccountDestroyService.php b/app/Services/Internal/Destroy/AccountDestroyService.php index 2aefdf29eb..c7ab06163b 100644 --- a/app/Services/Internal/Destroy/AccountDestroyService.php +++ b/app/Services/Internal/Destroy/AccountDestroyService.php @@ -61,36 +61,6 @@ class AccountDestroyService $account->delete(); } - public function moveTransactions(Account $account, Account $moveTo): void - { - app('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')]) // @phpstan-ignore-line - ; - 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 = $row->transaction_journal_id; - $journal = $user->transactionJournals()->find($journalId); - if (null !== $journal) { - app('log')->debug(sprintf('Deleted journal #%d because it has the same source as destination.', $journal->id)); - $service->destroy($journal); - } - } - } - } - private function destroyOpeningBalance(Account $account): void { app('log')->debug(sprintf('Searching for opening balance for account #%d "%s"', $account->id, $account->name)); @@ -129,6 +99,36 @@ class AccountDestroyService } } + public function moveTransactions(Account $account, Account $moveTo): void + { + app('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')]) // @phpstan-ignore-line + ; + 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 = $row->transaction_journal_id; + $journal = $user->transactionJournals()->find($journalId); + if (null !== $journal) { + app('log')->debug(sprintf('Deleted journal #%d because it has the same source as destination.', $journal->id)); + $service->destroy($journal); + } + } + } + } + private function updateRecurrences(Account $account, Account $moveTo): void { \DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index 3c10ead36e..2920bef1c2 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -507,6 +507,52 @@ trait AccountServiceTrait return $group; } + /** + * TODO refactor to "getfirstjournal" + * + * @throws FireflyException + */ + private function getObJournal(TransactionGroup $group): TransactionJournal + { + /** @var null|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 + * + * @throws FireflyException + */ + private function getOBTransaction(TransactionJournal $journal, Account $account): Transaction + { + /** @var null|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; + } + + /** + * @throws FireflyException + */ + private function getNotOBTransaction(TransactionJournal $journal, Account $account): Transaction + { + /** @var null|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. @@ -657,50 +703,4 @@ trait AccountServiceTrait return $group; } - - /** - * TODO refactor to "getfirstjournal" - * - * @throws FireflyException - */ - private function getObJournal(TransactionGroup $group): TransactionJournal - { - /** @var null|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 - * - * @throws FireflyException - */ - private function getOBTransaction(TransactionJournal $journal, Account $account): Transaction - { - /** @var null|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; - } - - /** - * @throws FireflyException - */ - private function getNotOBTransaction(TransactionJournal $journal, Account $account): Transaction - { - /** @var null|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; - } } diff --git a/app/Services/Internal/Support/CreditRecalculateService.php b/app/Services/Internal/Support/CreditRecalculateService.php index b4123a9a10..1ca08e58f4 100644 --- a/app/Services/Internal/Support/CreditRecalculateService.php +++ b/app/Services/Internal/Support/CreditRecalculateService.php @@ -73,16 +73,6 @@ class CreditRecalculateService $this->processWork(); } - public function setAccount(?Account $account): void - { - $this->account = $account; - } - - public function setGroup(TransactionGroup $group): void - { - $this->group = $group; - } - private function processGroup(): void { /** @var TransactionJournal $journal */ @@ -171,7 +161,7 @@ class CreditRecalculateService app('log')->debug(sprintf('Now processing account #%d ("%s"). All amounts with 2 decimals!', $account->id, $account->name)); // get opening balance (if present) $this->repository->setUser($account->user); - $direction = (string) $this->repository->getMetaValue($account, 'liability_direction'); + $direction = (string)$this->repository->getMetaValue($account, 'liability_direction'); $openingBalance = $this->repository->getOpeningBalance($account); if (null !== $openingBalance) { app('log')->debug(sprintf('Found opening balance transaction journal #%d', $openingBalance->id)); @@ -461,4 +451,14 @@ class CreditRecalculateService { return TransactionType::TRANSFER === $transactionType && -1 === bccomp($amount, '0'); } + + public function setAccount(?Account $account): void + { + $this->account = $account; + } + + public function setGroup(TransactionGroup $group): void + { + $this->group = $group; + } } diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index 1a1688d801..b6f716ece5 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -110,124 +110,6 @@ trait JournalServiceTrait return $result; } - /** - * @throws FireflyException - */ - protected function getAmount(string $amount): string - { - if ('' === $amount) { - throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount)); - } - app('log')->debug(sprintf('Now in getAmount("%s")', $amount)); - if (0 === bccomp('0', $amount)) { - throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount)); - } - - return $amount; - } - - protected function getForeignAmount(?string $amount): ?string - { - if (null === $amount) { - app('log')->debug('No foreign amount info in array. Return NULL'); - - return null; - } - if ('' === $amount) { - app('log')->debug('Foreign amount is empty string, return NULL.'); - - return null; - } - if (0 === bccomp('0', $amount)) { - app('log')->debug('Foreign amount is 0.0, return NULL.'); - - return null; - } - app('log')->debug(sprintf('Foreign amount is %s', $amount)); - - return $amount; - } - - protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void - { - if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { - $journal->budgets()->sync([]); - - return; - } - $budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']); - if (null !== $budget) { - app('log')->debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id)); - $journal->budgets()->sync([$budget->id]); - - return; - } - // if the budget is NULL, sync empty. - $journal->budgets()->sync([]); - } - - protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void - { - $category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']); - if (null !== $category) { - app('log')->debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id)); - $journal->categories()->sync([$category->id]); - - return; - } - // if the category is NULL, sync empty. - $journal->categories()->sync([]); - } - - protected function storeNotes(TransactionJournal $journal, ?string $notes): void - { - $notes = (string)$notes; - $note = $journal->notes()->first(); - if ('' !== $notes) { - if (null === $note) { - $note = new Note(); - $note->noteable()->associate($journal); - } - $note->text = $notes; - $note->save(); - app('log')->debug(sprintf('Stored notes for journal #%d', $journal->id)); - - return; - } - // try to delete existing notes. - $note?->delete(); - } - - /** - * Link tags to journal. - */ - protected function storeTags(TransactionJournal $journal, ?array $tags): void - { - app('log')->debug('Now in storeTags()', $tags ?? []); - $this->tagFactory->setUser($journal->user); - $set = []; - if (!is_array($tags)) { - app('log')->debug('Tags is not an array, break.'); - - return; - } - app('log')->debug('Start of loop.'); - foreach ($tags as $string) { - $string = (string)$string; - app('log')->debug(sprintf('Now at tag "%s"', $string)); - if ('' !== $string) { - $tag = $this->tagFactory->findOrCreate($string); - if (null !== $tag) { - $set[] = $tag->id; - } - } - } - $set = array_unique($set); - app('log')->debug('End of loop.'); - app('log')->debug(sprintf('Total nr. of tags: %d', count($tags)), $tags); - $journal->tags()->sync($set); - } - private function findAccountById(array $data, array $types): ?Account { // first attempt, find by ID. @@ -435,4 +317,122 @@ trait JournalServiceTrait return $account; } + + /** + * @throws FireflyException + */ + protected function getAmount(string $amount): string + { + if ('' === $amount) { + throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount)); + } + app('log')->debug(sprintf('Now in getAmount("%s")', $amount)); + if (0 === bccomp('0', $amount)) { + throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount)); + } + + return $amount; + } + + protected function getForeignAmount(?string $amount): ?string + { + if (null === $amount) { + app('log')->debug('No foreign amount info in array. Return NULL'); + + return null; + } + if ('' === $amount) { + app('log')->debug('Foreign amount is empty string, return NULL.'); + + return null; + } + if (0 === bccomp('0', $amount)) { + app('log')->debug('Foreign amount is 0.0, return NULL.'); + + return null; + } + app('log')->debug(sprintf('Foreign amount is %s', $amount)); + + return $amount; + } + + protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void + { + if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { + $journal->budgets()->sync([]); + + return; + } + $budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']); + if (null !== $budget) { + app('log')->debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id)); + $journal->budgets()->sync([$budget->id]); + + return; + } + // if the budget is NULL, sync empty. + $journal->budgets()->sync([]); + } + + protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void + { + $category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']); + if (null !== $category) { + app('log')->debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id)); + $journal->categories()->sync([$category->id]); + + return; + } + // if the category is NULL, sync empty. + $journal->categories()->sync([]); + } + + protected function storeNotes(TransactionJournal $journal, ?string $notes): void + { + $notes = (string)$notes; + $note = $journal->notes()->first(); + if ('' !== $notes) { + if (null === $note) { + $note = new Note(); + $note->noteable()->associate($journal); + } + $note->text = $notes; + $note->save(); + app('log')->debug(sprintf('Stored notes for journal #%d', $journal->id)); + + return; + } + // try to delete existing notes. + $note?->delete(); + } + + /** + * Link tags to journal. + */ + protected function storeTags(TransactionJournal $journal, ?array $tags): void + { + app('log')->debug('Now in storeTags()', $tags ?? []); + $this->tagFactory->setUser($journal->user); + $set = []; + if (!is_array($tags)) { + app('log')->debug('Tags is not an array, break.'); + + return; + } + app('log')->debug('Start of loop.'); + foreach ($tags as $string) { + $string = (string)$string; + app('log')->debug(sprintf('Now at tag "%s"', $string)); + if ('' !== $string) { + $tag = $this->tagFactory->findOrCreate($string); + if (null !== $tag) { + $set[] = $tag->id; + } + } + } + $set = array_unique($set); + app('log')->debug('End of loop.'); + app('log')->debug(sprintf('Total nr. of tags: %d', count($tags)), $tags); + $journal->tags()->sync($set); + } } diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index dea51b3445..443448e330 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -125,7 +125,7 @@ trait RecurringTransactionTrait if (!$validator->validateDestination(['id' => $destination->id])) { throw new FireflyException(sprintf('Destination invalid: %s', $validator->destError)); } - if (array_key_exists('foreign_amount', $array) && '' === (string) $array['foreign_amount']) { + if (array_key_exists('foreign_amount', $array) && '' === (string)$array['foreign_amount']) { unset($array['foreign_amount']); } // TODO typeOverrule. The account validator may have a different opinion on the type of the transaction. @@ -137,25 +137,25 @@ trait RecurringTransactionTrait 'source_id' => $source->id, 'destination_id' => $destination->id, 'amount' => $array['amount'], - 'foreign_amount' => array_key_exists('foreign_amount', $array) ? (string) $array['foreign_amount'] : null, + 'foreign_amount' => array_key_exists('foreign_amount', $array) ? (string)$array['foreign_amount'] : null, 'description' => $array['description'], ] ); $transaction->save(); if (array_key_exists('budget_id', $array)) { - $this->setBudget($transaction, (int) $array['budget_id']); + $this->setBudget($transaction, (int)$array['budget_id']); } if (array_key_exists('bill_id', $array)) { - $this->setBill($transaction, (int) $array['bill_id']); + $this->setBill($transaction, (int)$array['bill_id']); } if (array_key_exists('category_id', $array)) { - $this->setCategory($transaction, (int) $array['category_id']); + $this->setCategory($transaction, (int)$array['category_id']); } // same for piggy bank if (array_key_exists('piggy_bank_id', $array)) { - $this->updatePiggyBank($transaction, (int) $array['piggy_bank_id']); + $this->updatePiggyBank($transaction, (int)$array['piggy_bank_id']); } if (array_key_exists('tags', $array) && is_array($array['tags'])) { @@ -167,8 +167,8 @@ trait RecurringTransactionTrait protected function findAccount(array $expectedTypes, ?int $accountId, ?string $accountName): Account { $result = null; - $accountId = (int) $accountId; - $accountName = (string) $accountName; + $accountId = (int)$accountId; + $accountName = (string)$accountName; /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); @@ -210,61 +210,6 @@ trait RecurringTransactionTrait return $result ?? $repository->getCashAccount(); } - protected function updatePiggyBank(RecurrenceTransaction $transaction, int $piggyId): void - { - /** @var PiggyBankFactory $factory */ - $factory = app(PiggyBankFactory::class); - $factory->setUser($transaction->recurrence->user); - $piggyBank = $factory->find($piggyId, null); - if (null !== $piggyBank) { - /** @var null|RecurrenceMeta $entry */ - $entry = $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->first(); - if (null === $entry) { - $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'piggy_bank_id', 'value' => $piggyBank->id]); - } - $entry->value = $piggyBank->id; - $entry->save(); - } - if (null === $piggyBank) { - // delete if present - $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->delete(); - } - } - - protected function updateTags(RecurrenceTransaction $transaction, array $tags): void - { - if (0 !== count($tags)) { - /** @var null|RecurrenceMeta $entry */ - $entry = $transaction->recurrenceTransactionMeta()->where('name', 'tags')->first(); - if (null === $entry) { - $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'tags', 'value' => json_encode($tags)]); - } - $entry->value = json_encode($tags); - $entry->save(); - } - if (0 === count($tags)) { - // delete if present - $transaction->recurrenceTransactionMeta()->where('name', 'tags')->delete(); - } - } - - protected function deleteRepetitions(Recurrence $recurrence): void - { - $recurrence->recurrenceRepetitions()->delete(); - } - - protected function deleteTransactions(Recurrence $recurrence): void - { - app('log')->debug('deleteTransactions()'); - - /** @var RecurrenceTransaction $transaction */ - foreach ($recurrence->recurrenceTransactions as $transaction) { - $transaction->recurrenceTransactionMeta()->delete(); - - $transaction->delete(); - } - } - private function setBudget(RecurrenceTransaction $transaction, int $budgetId): void { $budgetFactory = app(BudgetFactory::class); @@ -325,4 +270,59 @@ trait RecurringTransactionTrait $meta->value = $category->id; $meta->save(); } + + protected function updatePiggyBank(RecurrenceTransaction $transaction, int $piggyId): void + { + /** @var PiggyBankFactory $factory */ + $factory = app(PiggyBankFactory::class); + $factory->setUser($transaction->recurrence->user); + $piggyBank = $factory->find($piggyId, null); + if (null !== $piggyBank) { + /** @var null|RecurrenceMeta $entry */ + $entry = $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->first(); + if (null === $entry) { + $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'piggy_bank_id', 'value' => $piggyBank->id]); + } + $entry->value = $piggyBank->id; + $entry->save(); + } + if (null === $piggyBank) { + // delete if present + $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->delete(); + } + } + + protected function updateTags(RecurrenceTransaction $transaction, array $tags): void + { + if (0 !== count($tags)) { + /** @var null|RecurrenceMeta $entry */ + $entry = $transaction->recurrenceTransactionMeta()->where('name', 'tags')->first(); + if (null === $entry) { + $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'tags', 'value' => json_encode($tags)]); + } + $entry->value = json_encode($tags); + $entry->save(); + } + if (0 === count($tags)) { + // delete if present + $transaction->recurrenceTransactionMeta()->where('name', 'tags')->delete(); + } + } + + protected function deleteRepetitions(Recurrence $recurrence): void + { + $recurrence->recurrenceRepetitions()->delete(); + } + + protected function deleteTransactions(Recurrence $recurrence): void + { + app('log')->debug('deleteTransactions()'); + + /** @var RecurrenceTransaction $transaction */ + foreach ($recurrence->recurrenceTransactions as $transaction) { + $transaction->recurrenceTransactionMeta()->delete(); + + $transaction->delete(); + } + } } diff --git a/app/Services/Internal/Update/AccountUpdateService.php b/app/Services/Internal/Update/AccountUpdateService.php index 1ee69ae2bf..6e38975ea6 100644 --- a/app/Services/Internal/Update/AccountUpdateService.php +++ b/app/Services/Internal/Update/AccountUpdateService.php @@ -109,55 +109,6 @@ class AccountUpdateService $this->user = $user; } - public function updateAccountOrder(Account $account, array $data): Account - { - // skip if no order info - if (!array_key_exists('order', $data) || $data['order'] === $account->order) { - app('log')->debug(sprintf('Account order will not be touched because its not set or already at %d.', $account->order)); - - return $account; - } - // skip if not of orderable type. - $type = $account->accountType->type; - if (!in_array($type, [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], true)) { - app('log')->debug('Will not change order of this account.'); - - return $account; - } - // get account type ID's because a join and an update is hard: - $oldOrder = $account->order; - $newOrder = $data['order']; - app('log')->debug(sprintf('Order is set to be updated from %s to %s', $oldOrder, $newOrder)); - $list = $this->getTypeIds([AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT]); - if (AccountType::ASSET === $type) { - $list = $this->getTypeIds([AccountType::ASSET]); - } - - if ($newOrder > $oldOrder) { - $this->user->accounts()->where('accounts.order', '<=', $newOrder)->where('accounts.order', '>', $oldOrder) - ->where('accounts.id', '!=', $account->id) - ->whereIn('accounts.account_type_id', $list) - ->decrement('order') - ; - $account->order = $newOrder; - app('log')->debug(sprintf('Order of account #%d ("%s") is now %d', $account->id, $account->name, $newOrder)); - $account->save(); - - return $account; - } - - $this->user->accounts()->where('accounts.order', '>=', $newOrder)->where('accounts.order', '<', $oldOrder) - ->where('accounts.id', '!=', $account->id) - ->whereIn('accounts.account_type_id', $list) - ->increment('order') - ; - $account->order = $newOrder; - app('log')->debug(sprintf('Order of account #%d ("%s") is now %d', $account->id, $account->name, $newOrder)); - $account->save(); - - return $account; - } - private function updateAccount(Account $account, array $data): Account { // update the account itself: @@ -209,6 +160,55 @@ class AccountUpdateService return AccountType::whereType(ucfirst($type))->first(); } + public function updateAccountOrder(Account $account, array $data): Account + { + // skip if no order info + if (!array_key_exists('order', $data) || $data['order'] === $account->order) { + app('log')->debug(sprintf('Account order will not be touched because its not set or already at %d.', $account->order)); + + return $account; + } + // skip if not of orderable type. + $type = $account->accountType->type; + if (!in_array($type, [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], true)) { + app('log')->debug('Will not change order of this account.'); + + return $account; + } + // get account type ID's because a join and an update is hard: + $oldOrder = $account->order; + $newOrder = $data['order']; + app('log')->debug(sprintf('Order is set to be updated from %s to %s', $oldOrder, $newOrder)); + $list = $this->getTypeIds([AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT]); + if (AccountType::ASSET === $type) { + $list = $this->getTypeIds([AccountType::ASSET]); + } + + if ($newOrder > $oldOrder) { + $this->user->accounts()->where('accounts.order', '<=', $newOrder)->where('accounts.order', '>', $oldOrder) + ->where('accounts.id', '!=', $account->id) + ->whereIn('accounts.account_type_id', $list) + ->decrement('order') + ; + $account->order = $newOrder; + app('log')->debug(sprintf('Order of account #%d ("%s") is now %d', $account->id, $account->name, $newOrder)); + $account->save(); + + return $account; + } + + $this->user->accounts()->where('accounts.order', '>=', $newOrder)->where('accounts.order', '<', $oldOrder) + ->where('accounts.id', '!=', $account->id) + ->whereIn('accounts.account_type_id', $list) + ->increment('order') + ; + $account->order = $newOrder; + app('log')->debug(sprintf('Order of account #%d ("%s") is now %d', $account->id, $account->name, $newOrder)); + $account->save(); + + return $account; + } + private function getTypeIds(array $array): array { $return = []; diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index 63898994dd..1567867575 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -53,7 +53,7 @@ class BillUpdateService if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) { $factory = app(TransactionCurrencyFactory::class); - $currency = $factory->find((int) ($data['currency_id'] ?? null), $data['currency_code'] ?? null) ?? + $currency = $factory->find((int)($data['currency_id'] ?? null), $data['currency_code'] ?? null) ?? app('amount')->getDefaultCurrencyByUserGroup($bill->user->userGroup); // enable the currency if it isn't. @@ -75,14 +75,14 @@ class BillUpdateService ]; // update note: if (array_key_exists('notes', $data)) { - $this->updateNote($bill, (string) $data['notes']); + $this->updateNote($bill, (string)$data['notes']); } // update order. if (array_key_exists('order', $data)) { // update the order of the piggy bank: $oldOrder = $bill->order; - $newOrder = (int) ($data['order'] ?? $oldOrder); + $newOrder = (int)($data['order'] ?? $oldOrder); if ($oldOrder !== $newOrder) { $this->updateOrder($bill, $oldOrder, $newOrder); } @@ -112,7 +112,7 @@ class BillUpdateService } if (array_key_exists('object_group_id', $data)) { // try also with ID: - $objectGroupId = (int) ($data['object_group_id'] ?? 0); + $objectGroupId = (int)($data['object_group_id'] ?? 0); if (0 !== $objectGroupId) { $objectGroup = $this->findObjectGroupById($objectGroupId); if (null !== $objectGroup) { @@ -134,20 +134,20 @@ class BillUpdateService */ private function updateBillProperties(Bill $bill, array $data): Bill { - if (array_key_exists('name', $data) && '' !== (string) $data['name']) { + if (array_key_exists('name', $data) && '' !== (string)$data['name']) { $bill->name = $data['name']; } - if (array_key_exists('amount_min', $data) && '' !== (string) $data['amount_min']) { + if (array_key_exists('amount_min', $data) && '' !== (string)$data['amount_min']) { $bill->amount_min = $data['amount_min']; } - if (array_key_exists('amount_max', $data) && '' !== (string) $data['amount_max']) { + if (array_key_exists('amount_max', $data) && '' !== (string)$data['amount_max']) { $bill->amount_max = $data['amount_max']; } - if (array_key_exists('date', $data) && '' !== (string) $data['date']) { + if (array_key_exists('date', $data) && '' !== (string)$data['date']) { $bill->date = $data['date']; } - if (array_key_exists('repeat_freq', $data) && '' !== (string) $data['repeat_freq']) { + if (array_key_exists('repeat_freq', $data) && '' !== (string)$data['repeat_freq']) { $bill->repeat_freq = $data['repeat_freq']; } if (array_key_exists('skip', $data)) { diff --git a/app/Services/Internal/Update/RecurrenceUpdateService.php b/app/Services/Internal/Update/RecurrenceUpdateService.php index e5859d7d30..13119a36cb 100644 --- a/app/Services/Internal/Update/RecurrenceUpdateService.php +++ b/app/Services/Internal/Update/RecurrenceUpdateService.php @@ -71,7 +71,7 @@ class RecurrenceUpdateService $recurrence->repetitions = 0; } if (array_key_exists('nr_of_repetitions', $info)) { - if (0 !== (int) $info['nr_of_repetitions']) { + if (0 !== (int)$info['nr_of_repetitions']) { $recurrence->repeat_until = null; } $recurrence->repetitions = $info['nr_of_repetitions']; @@ -209,7 +209,7 @@ class RecurrenceUpdateService // First, make sure to loop all existing transactions and match them to a counterpart in the submitted transactions array. foreach ($originalTransactions as $i => $originalTransaction) { foreach ($transactions as $ii => $submittedTransaction) { - if (array_key_exists('id', $submittedTransaction) && (int) $originalTransaction['id'] === (int) $submittedTransaction['id']) { + if (array_key_exists('id', $submittedTransaction) && (int)$originalTransaction['id'] === (int)$submittedTransaction['id']) { app('log')->debug(sprintf('Match original transaction #%d with an entry in the submitted array.', $originalTransaction['id'])); $combinations[] = [ 'original' => $originalTransaction, @@ -238,7 +238,7 @@ class RecurrenceUpdateService // anything left in the original transactions array can be deleted. foreach ($originalTransactions as $original) { app('log')->debug(sprintf('Original transaction #%d is unmatched, delete it!', $original['id'])); - $this->deleteTransaction($recurrence, (int) $original['id']); + $this->deleteTransaction($recurrence, (int)$original['id']); } // anything left is new. $this->createTransactions($recurrence, $transactions); @@ -265,7 +265,7 @@ class RecurrenceUpdateService $foreignCurrency = null; if (array_key_exists('currency_id', $submitted) || array_key_exists('currency_code', $submitted)) { $currency = $currencyFactory->find( - array_key_exists('currency_id', $submitted) ? (int) $submitted['currency_id'] : null, + array_key_exists('currency_id', $submitted) ? (int)$submitted['currency_id'] : null, array_key_exists('currency_code', $submitted) ? $submitted['currency_code'] : null ); } @@ -277,7 +277,7 @@ class RecurrenceUpdateService } if (array_key_exists('foreign_currency_id', $submitted) || array_key_exists('foreign_currency_code', $submitted)) { $foreignCurrency = $currencyFactory->find( - array_key_exists('foreign_currency_id', $submitted) ? (int) $submitted['foreign_currency_id'] : null, + array_key_exists('foreign_currency_id', $submitted) ? (int)$submitted['foreign_currency_id'] : null, array_key_exists('foreign_currency_code', $submitted) ? $submitted['foreign_currency_code'] : null ); } @@ -306,29 +306,29 @@ class RecurrenceUpdateService } // update meta data if (array_key_exists('budget_id', $submitted)) { - $this->setBudget($transaction, (int) $submitted['budget_id']); + $this->setBudget($transaction, (int)$submitted['budget_id']); } if (array_key_exists('bill_id', $submitted)) { - $this->setBill($transaction, (int) $submitted['bill_id']); + $this->setBill($transaction, (int)$submitted['bill_id']); } // reset category if name is set but empty: // can be removed when v1 is retired. - if (array_key_exists('category_name', $submitted) && '' === (string) $submitted['category_name']) { + if (array_key_exists('category_name', $submitted) && '' === (string)$submitted['category_name']) { app('log')->debug('Category name is submitted but is empty. Set category to be empty.'); $submitted['category_name'] = null; $submitted['category_id'] = 0; } if (array_key_exists('category_id', $submitted)) { - app('log')->debug(sprintf('Category ID is submitted, set category to be %d.', (int) $submitted['category_id'])); - $this->setCategory($transaction, (int) $submitted['category_id']); + app('log')->debug(sprintf('Category ID is submitted, set category to be %d.', (int)$submitted['category_id'])); + $this->setCategory($transaction, (int)$submitted['category_id']); } if (array_key_exists('tags', $submitted) && is_array($submitted['tags'])) { $this->updateTags($transaction, $submitted['tags']); } if (array_key_exists('piggy_bank_id', $submitted)) { - $this->updatePiggyBank($transaction, (int) $submitted['piggy_bank_id']); + $this->updatePiggyBank($transaction, (int)$submitted['piggy_bank_id']); } } diff --git a/app/Support/Amount.php b/app/Support/Amount.php index cac56dd2d7..3a58e52b0d 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -63,7 +63,7 @@ class Amount $fmt->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $symbol); $fmt->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces); $fmt->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces); - $result = (string) $fmt->format((float) $rounded); // intentional float + $result = (string)$fmt->format((float)$rounded); // intentional float if (true === $coloured) { if (1 === bccomp($rounded, '0')) { @@ -157,6 +157,41 @@ class Amount ]; } + /** + * @throws FireflyException + * + * @SuppressWarnings(PHPMD.MissingImport) + */ + private function getLocaleInfo(): array + { + // get config from preference, not from translation: + $locale = app('steam')->getLocale(); + $array = app('steam')->getLocaleArray($locale); + + setlocale(LC_MONETARY, $array); + $info = localeconv(); + + // correct variables + $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); + $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); + + $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); + $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); + + $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); + + $info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL); + $info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); + + return $info; + } + + 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'] @@ -230,39 +265,4 @@ class Amount return $format; } - - /** - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.MissingImport) - */ - private function getLocaleInfo(): array - { - // get config from preference, not from translation: - $locale = app('steam')->getLocale(); - $array = app('steam')->getLocaleArray($locale); - - setlocale(LC_MONETARY, $array); - $info = localeconv(); - - // correct variables - $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); - $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); - - $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); - $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); - - $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); - - $info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL); - $info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); - - return $info; - } - - private function getLocaleField(array $info, string $field): bool - { - return (is_bool($info[$field]) && true === $info[$field]) - || (is_int($info[$field]) && 1 === $info[$field]); - } } diff --git a/app/Support/Binder/UserGroupTransaction.php b/app/Support/Binder/UserGroupTransaction.php index d9131400f3..fbbf5c1f43 100644 --- a/app/Support/Binder/UserGroupTransaction.php +++ b/app/Support/Binder/UserGroupTransaction.php @@ -38,7 +38,7 @@ class UserGroupTransaction implements BinderInterface if (auth()->check()) { /** @var User $user */ $user = auth()->user(); - $group = TransactionGroup::where('id', (int) $value) + $group = TransactionGroup::where('id', (int)$value) ->where('user_group_id', $user->user_group_id) ->first() ; diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index fdba09b8dc..dc1619bddf 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -73,14 +73,6 @@ class CacheProperties return \Cache::has($this->hash); } - /** - * @param mixed $data - */ - public function store($data): void - { - \Cache::forever($this->hash, $data); - } - private function hash(): void { $content = ''; @@ -94,4 +86,12 @@ 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/Calendar/Calculator.php b/app/Support/Calendar/Calculator.php index bae3f12142..c8fc629934 100644 --- a/app/Support/Calendar/Calculator.php +++ b/app/Support/Calendar/Calculator.php @@ -34,7 +34,7 @@ class Calculator { public const int DEFAULT_INTERVAL = 1; private static ?\SplObjectStorage $intervalMap = null; - private static array $intervals = []; + private static array $intervals = []; /** * @throws IntervalException diff --git a/app/Support/Chart/Budget/FrontpageChartGenerator.php b/app/Support/Chart/Budget/FrontpageChartGenerator.php index c08e56dd1b..2e09e909a0 100644 --- a/app/Support/Chart/Budget/FrontpageChartGenerator.php +++ b/app/Support/Chart/Budget/FrontpageChartGenerator.php @@ -78,29 +78,6 @@ class FrontpageChartGenerator return $data; } - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - public function setStart(Carbon $start): void - { - $this->start = $start; - } - - /** - * A basic setter for the user. Also updates the repositories with the right 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); - } - /** * 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. @@ -196,4 +173,27 @@ class FrontpageChartGenerator return $data; } + + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * A basic setter for the user. Also updates the repositories with the right 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/Export/ExportDataGenerator.php b/app/Support/Export/ExportDataGenerator.php index 3d570e13a3..2ac986eb7f 100644 --- a/app/Support/Export/ExportDataGenerator.php +++ b/app/Support/Export/ExportDataGenerator.php @@ -134,87 +134,6 @@ class ExportDataGenerator return $return; } - public function setUser(User $user): void - { - $this->user = $user; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function get(string $key, mixed $default = null): mixed - { - return null; - } - - public function setAccounts(Collection $accounts): void - { - $this->accounts = $accounts; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function has(mixed $key): mixed - { - return null; - } - - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - public function setExportAccounts(bool $exportAccounts): void - { - $this->exportAccounts = $exportAccounts; - } - - public function setExportBills(bool $exportBills): void - { - $this->exportBills = $exportBills; - } - - public function setExportBudgets(bool $exportBudgets): void - { - $this->exportBudgets = $exportBudgets; - } - - public function setExportCategories(bool $exportCategories): void - { - $this->exportCategories = $exportCategories; - } - - public function setExportPiggies(bool $exportPiggies): void - { - $this->exportPiggies = $exportPiggies; - } - - public function setExportRecurring(bool $exportRecurring): void - { - $this->exportRecurring = $exportRecurring; - } - - public function setExportRules(bool $exportRules): void - { - $this->exportRules = $exportRules; - } - - public function setExportTags(bool $exportTags): void - { - $this->exportTags = $exportTags; - } - - public function setExportTransactions(bool $exportTransactions): void - { - $this->exportTransactions = $exportTransactions; - } - - public function setStart(Carbon $start): void - { - $this->start = $start; - } - /** * @throws CannotInsertRecord * @throws Exception @@ -296,6 +215,11 @@ class ExportDataGenerator return $string; } + public function setUser(User $user): void + { + $this->user = $user; + } + /** * @throws CannotInsertRecord * @throws Exception @@ -788,6 +712,14 @@ class ExportDataGenerator return $string; } + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function get(string $key, mixed $default = null): mixed + { + return null; + } + /** * @throws CannotInsertRecord * @throws Exception @@ -864,6 +796,11 @@ class ExportDataGenerator return $string; } + public function setAccounts(Collection $accounts): void + { + $this->accounts = $accounts; + } + private function mergeTags(array $tags): string { if (0 === count($tags)) { @@ -876,4 +813,67 @@ class ExportDataGenerator return implode(',', $smol); } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function has(mixed $key): mixed + { + return null; + } + + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + public function setExportAccounts(bool $exportAccounts): void + { + $this->exportAccounts = $exportAccounts; + } + + public function setExportBills(bool $exportBills): void + { + $this->exportBills = $exportBills; + } + + public function setExportBudgets(bool $exportBudgets): void + { + $this->exportBudgets = $exportBudgets; + } + + public function setExportCategories(bool $exportCategories): void + { + $this->exportCategories = $exportCategories; + } + + public function setExportPiggies(bool $exportPiggies): void + { + $this->exportPiggies = $exportPiggies; + } + + public function setExportRecurring(bool $exportRecurring): void + { + $this->exportRecurring = $exportRecurring; + } + + public function setExportRules(bool $exportRules): void + { + $this->exportRules = $exportRules; + } + + public function setExportTags(bool $exportTags): void + { + $this->exportTags = $exportTags; + } + + public function setExportTransactions(bool $exportTransactions): void + { + $this->exportTransactions = $exportTransactions; + } + + public function setStart(Carbon $start): void + { + $this->start = $start; + } } diff --git a/app/Support/Form/AccountForm.php b/app/Support/Form/AccountForm.php index 6f3838a539..4a379117cb 100644 --- a/app/Support/Form/AccountForm.php +++ b/app/Support/Form/AccountForm.php @@ -55,6 +55,37 @@ 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); + } + if ('' === $role) { + $role = 'no_account_type'; + if (AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; + } + if (AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; + } + } + $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. */ @@ -127,35 +158,4 @@ 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); - } - if ('' === $role) { - $role = 'no_account_type'; - if (AccountType::EXPENSE === $account->accountType->type) { - $role = 'expense_account'; - } - if (AccountType::REVENUE === $account->accountType->type) { - $role = 'revenue_account'; - } - } - $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 0a85b876c1..dd04390c74 100644 --- a/app/Support/Form/CurrencyForm.php +++ b/app/Support/Form/CurrencyForm.php @@ -47,64 +47,6 @@ class CurrencyForm return $this->currencyField($name, 'amount', $value, $options); } - /** - * TODO describe and cleanup. - * - * @param mixed $value - * - * @throws FireflyException - */ - public function balanceAll(string $name, $value = null, array $options = null): string - { - return $this->allCurrencyField($name, 'balance', $value, $options); - } - - /** - * TODO cleanup and describe - * - * @param mixed $value - */ - 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 mixed $value - */ - 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); - } - /** * @throws FireflyException */ @@ -157,6 +99,18 @@ class CurrencyForm return $html; } + /** + * TODO describe and cleanup. + * + * @param mixed $value + * + * @throws FireflyException + */ + public function balanceAll(string $name, $value = null, array $options = null): string + { + return $this->allCurrencyField($name, 'balance', $value, $options); + } + /** * TODO describe and cleanup * @@ -213,4 +167,50 @@ class CurrencyForm return $html; } + + /** + * TODO cleanup and describe + * + * @param mixed $value + */ + 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 mixed $value + */ + 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); + } } diff --git a/app/Support/Http/Api/AccountBalanceGrouped.php b/app/Support/Http/Api/AccountBalanceGrouped.php index a166317143..286620deef 100644 --- a/app/Support/Http/Api/AccountBalanceGrouped.php +++ b/app/Support/Http/Api/AccountBalanceGrouped.php @@ -58,11 +58,11 @@ class AccountBalanceGrouped // income and expense array prepped: $income = [ 'label' => 'earned', - 'currency_id' => (string) $currency['currency_id'], + 'currency_id' => (string)$currency['currency_id'], 'currency_symbol' => $currency['currency_symbol'], 'currency_code' => $currency['currency_code'], 'currency_decimal_places' => $currency['currency_decimal_places'], - 'native_currency_id' => (string) $currency['native_currency_id'], + 'native_currency_id' => (string)$currency['native_currency_id'], 'native_currency_symbol' => $currency['native_currency_symbol'], 'native_currency_code' => $currency['native_currency_code'], 'native_currency_decimal_places' => $currency['native_currency_decimal_places'], @@ -74,11 +74,11 @@ class AccountBalanceGrouped ]; $expense = [ 'label' => 'spent', - 'currency_id' => (string) $currency['currency_id'], + 'currency_id' => (string)$currency['currency_id'], 'currency_symbol' => $currency['currency_symbol'], 'currency_code' => $currency['currency_code'], 'currency_decimal_places' => $currency['currency_decimal_places'], - 'native_currency_id' => (string) $currency['native_currency_id'], + 'native_currency_id' => (string)$currency['native_currency_id'], 'native_currency_symbol' => $currency['native_currency_symbol'], 'native_currency_code' => $currency['native_currency_code'], 'native_currency_decimal_places' => $currency['native_currency_decimal_places'], @@ -126,19 +126,19 @@ class AccountBalanceGrouped foreach ($this->journals as $journal) { // format the date according to the period $period = $journal['date']->format($this->carbonFormat); - $currencyId = (int) $journal['currency_id']; + $currencyId = (int)$journal['currency_id']; $currency = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId); $this->currencies[$currencyId] = $currency; // may just re-assign itself, don't mind. // set the array with monetary info, if it does not exist. $this->data[$currencyId] ??= [ - 'currency_id' => (string) $currencyId, + 'currency_id' => (string)$currencyId, 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code'], 'currency_name' => $journal['currency_name'], 'currency_decimal_places' => $journal['currency_decimal_places'], // native currency info (could be the same) - 'native_currency_id' => (string) $this->default->id, + 'native_currency_id' => (string)$this->default->id, 'native_currency_code' => $this->default->code, 'native_currency_symbol' => $this->default->symbol, 'native_currency_decimal_places' => $this->default->decimal_places, @@ -183,7 +183,7 @@ class AccountBalanceGrouped $amountConverted = bcmul($amount, $rate); // perhaps transaction already has the foreign amount in the native currency. - if ((int) $journal['foreign_currency_id'] === $this->default->id) { + if ((int)$journal['foreign_currency_id'] === $this->default->id) { $amountConverted = $journal['foreign_amount'] ?? '0'; $amountConverted = 'earned' === $key ? app('steam')->positive($amountConverted) : app('steam')->negative($amountConverted); } @@ -209,12 +209,12 @@ class AccountBalanceGrouped $defaultCurrencyId = $default->id; $this->currencies = [$default->id => $default]; // currency cache $this->data[$defaultCurrencyId] = [ - 'currency_id' => (string) $defaultCurrencyId, + 'currency_id' => (string)$defaultCurrencyId, 'currency_symbol' => $default->symbol, 'currency_code' => $default->code, 'currency_name' => $default->name, 'currency_decimal_places' => $default->decimal_places, - 'native_currency_id' => (string) $defaultCurrencyId, + 'native_currency_id' => (string)$defaultCurrencyId, 'native_currency_symbol' => $default->symbol, 'native_currency_code' => $default->code, 'native_currency_name' => $default->name, diff --git a/app/Support/Http/Api/ConvertsExchangeRates.php b/app/Support/Http/Api/ConvertsExchangeRates.php index a742d5a01f..465de66dc8 100644 --- a/app/Support/Http/Api/ConvertsExchangeRates.php +++ b/app/Support/Http/Api/ConvertsExchangeRates.php @@ -76,6 +76,27 @@ trait ConvertsExchangeRates return $set; } + /** + * @deprecated + */ + private function getPreference(): void + { + $this->enabled = config('cer.currency_conversion'); + } + + /** + * @deprecated + */ + private function getCurrency(int $currencyId): TransactionCurrency + { + $result = TransactionCurrency::find($currencyId); + if (null === $result) { + return app('amount')->getDefaultCurrency(); + } + + return $result; + } + /** * For a sum of entries, get the exchange rate to the native currency of * the user. @@ -133,27 +154,6 @@ trait ConvertsExchangeRates return $return; } - /** - * @deprecated - */ - private function getPreference(): void - { - $this->enabled = config('cer.currency_conversion'); - } - - /** - * @deprecated - */ - private function getCurrency(int $currencyId): TransactionCurrency - { - $result = TransactionCurrency::find($currencyId); - if (null === $result) { - return app('amount')->getDefaultCurrency(); - } - - return $result; - } - /** * @deprecated */ diff --git a/app/Support/Http/Api/ExchangeRateConverter.php b/app/Support/Http/Api/ExchangeRateConverter.php index f73de03a6f..05fa2e001d 100644 --- a/app/Support/Http/Api/ExchangeRateConverter.php +++ b/app/Support/Http/Api/ExchangeRateConverter.php @@ -37,11 +37,11 @@ use Illuminate\Support\Facades\Log; class ExchangeRateConverter { // use ConvertsExchangeRates; - private int $queryCount = 0; - private array $prepared = []; private array $fallback = []; private bool $isPrepared = false; private bool $noPreparedRates = false; + private array $prepared = []; + private int $queryCount = 0; /** * @throws FireflyException @@ -54,61 +54,6 @@ class ExchangeRateConverter return bcmul($amount, $rate); } - /** - * @throws FireflyException - */ - public function prepare(TransactionCurrency $from, TransactionCurrency $to, Carbon $start, Carbon $end): void - { - Log::debug('prepare()'); - $start->startOfDay(); - $end->endOfDay(); - Log::debug(sprintf('Preparing for %s to %s between %s and %s', $from->code, $to->code, $start->format('Y-m-d'), $end->format('Y-m-d'))); - $set = auth()->user() - ->currencyExchangeRates() - ->where('from_currency_id', $from->id) - ->where('to_currency_id', $to->id) - ->where('date', '<=', $end->format('Y-m-d')) - ->where('date', '>=', $start->format('Y-m-d')) - ->orderBy('date', 'DESC')->get() - ; - ++$this->queryCount; - if (0 === $set->count()) { - Log::debug('No prepared rates found in this period, use the fallback'); - $this->fallback($from, $to, $start); - $this->noPreparedRates = true; - $this->isPrepared = true; - Log::debug('prepare DONE()'); - - return; - } - $this->isPrepared = true; - - // so there is a fallback just in case. Now loop the set of rates we DO have. - $temp = []; - $count = 0; - foreach ($set as $rate) { - $date = $rate->date->format('Y-m-d'); - $temp[$date] ??= [ - $from->id => [ - $to->id => $rate->rate, - ], - ]; - ++$count; - } - Log::debug(sprintf('Found %d rates in this period.', $count)); - $currentStart = clone $start; - while ($currentStart->lte($end)) { - $currentDate = $currentStart->format('Y-m-d'); - $this->prepared[$currentDate] ??= []; - $fallback = $temp[$currentDate][$from->id][$to->id] ?? $this->fallback[$from->id][$to->id] ?? '0'; - if (0 === count($this->prepared[$currentDate]) && 0 !== bccomp('0', $fallback)) { - // fill from temp or fallback or from temp (see before) - $this->prepared[$currentDate][$from->id][$to->id] = $fallback; - } - $currentStart->addDay(); - } - } - /** * @throws FireflyException */ @@ -120,11 +65,6 @@ class ExchangeRateConverter return '0' === $rate ? '1' : $rate; } - public function summarize(): void - { - Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount)); - } - /** * @throws FireflyException */ @@ -202,7 +142,7 @@ class ExchangeRateConverter ->first() ; ++$this->queryCount; - $rate = (string) $result?->rate; + $rate = (string)$result?->rate; if ('' === $rate) { app('log')->debug(sprintf('Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date)); @@ -258,7 +198,7 @@ class ExchangeRateConverter // grab backup values from config file: $backup = config(sprintf('cer.rates.%s', $currency->code)); if (null !== $backup) { - return bcdiv('1', (string) $backup); + return bcdiv('1', (string)$backup); // app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup)); // return $backup; } @@ -276,7 +216,7 @@ class ExchangeRateConverter $cache = new CacheProperties(); $cache->addProperty('cer-euro-id'); if ($cache->has()) { - return (int) $cache->get(); + return (int)$cache->get(); } $euro = TransactionCurrency::whereCode('EUR')->first(); ++$this->queryCount; @@ -288,6 +228,61 @@ class ExchangeRateConverter return $euro->id; } + /** + * @throws FireflyException + */ + public function prepare(TransactionCurrency $from, TransactionCurrency $to, Carbon $start, Carbon $end): void + { + Log::debug('prepare()'); + $start->startOfDay(); + $end->endOfDay(); + Log::debug(sprintf('Preparing for %s to %s between %s and %s', $from->code, $to->code, $start->format('Y-m-d'), $end->format('Y-m-d'))); + $set = auth()->user() + ->currencyExchangeRates() + ->where('from_currency_id', $from->id) + ->where('to_currency_id', $to->id) + ->where('date', '<=', $end->format('Y-m-d')) + ->where('date', '>=', $start->format('Y-m-d')) + ->orderBy('date', 'DESC')->get() + ; + ++$this->queryCount; + if (0 === $set->count()) { + Log::debug('No prepared rates found in this period, use the fallback'); + $this->fallback($from, $to, $start); + $this->noPreparedRates = true; + $this->isPrepared = true; + Log::debug('prepare DONE()'); + + return; + } + $this->isPrepared = true; + + // so there is a fallback just in case. Now loop the set of rates we DO have. + $temp = []; + $count = 0; + foreach ($set as $rate) { + $date = $rate->date->format('Y-m-d'); + $temp[$date] ??= [ + $from->id => [ + $to->id => $rate->rate, + ], + ]; + ++$count; + } + Log::debug(sprintf('Found %d rates in this period.', $count)); + $currentStart = clone $start; + while ($currentStart->lte($end)) { + $currentDate = $currentStart->format('Y-m-d'); + $this->prepared[$currentDate] ??= []; + $fallback = $temp[$currentDate][$from->id][$to->id] ?? $this->fallback[$from->id][$to->id] ?? '0'; + if (0 === count($this->prepared[$currentDate]) && 0 !== bccomp('0', $fallback)) { + // fill from temp or fallback or from temp (see before) + $this->prepared[$currentDate][$from->id][$to->id] = $fallback; + } + $currentStart->addDay(); + } + } + /** * If there are no exchange rate in the "prepare" array, future searches for any exchange rate * will result in nothing: otherwise the preparation had been unnecessary. So, to fix this Firefly III @@ -307,4 +302,9 @@ class ExchangeRateConverter Log::debug(sprintf('Fallback rate %s > %s = %s', $from->code, $to->code, $fallback)); Log::debug(sprintf('Fallback rate %s > %s = %s', $to->code, $from->code, bcdiv('1', $fallback))); } + + public function summarize(): void + { + Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount)); + } } diff --git a/app/Support/Http/Api/SummaryBalanceGrouped.php b/app/Support/Http/Api/SummaryBalanceGrouped.php index c78acf3485..ff807803b9 100644 --- a/app/Support/Http/Api/SummaryBalanceGrouped.php +++ b/app/Support/Http/Api/SummaryBalanceGrouped.php @@ -29,12 +29,12 @@ use Illuminate\Support\Facades\Log; class SummaryBalanceGrouped { - private const string SUM = 'sum'; - private TransactionCurrency $default; - private array $amounts = []; - private array $keys; - private array $currencies; + private const string SUM = 'sum'; + private array $amounts = []; + private array $currencies; private CurrencyRepositoryInterface $currencyRepository; + private TransactionCurrency $default; + private array $keys; public function __construct() { @@ -43,42 +43,6 @@ class SummaryBalanceGrouped $this->currencyRepository = app(CurrencyRepositoryInterface::class); } - public function groupTransactions(string $key, array $journals): void - { - Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); - Log::debug(sprintf('Now in groupTransactions with key "%s" and %d journal(s)', $key, count($journals))); - $converter = new ExchangeRateConverter(); - $this->keys[] = $key; - $multiplier = 'income' === $key ? '-1' : '1'; - - /** @var array $journal */ - foreach ($journals as $journal) { - // transaction info: - $currencyId = (int)$journal['currency_id']; - $amount = bcmul($journal['amount'], $multiplier); - $currency = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId); - $this->currencies[$currencyId] = $currency; - $nativeAmount = $converter->convert($currency, $this->default, $journal['date'], $amount); - if ((int)$journal['foreign_currency_id'] === $this->default->id) { - // use foreign amount instead - $nativeAmount = $journal['foreign_amount']; - } - // prep the arrays - $this->amounts[$key] ??= []; - $this->amounts[$key][$currencyId] ??= '0'; - $this->amounts[$key]['native'] ??= '0'; - $this->amounts[self::SUM][$currencyId] ??= '0'; - $this->amounts[self::SUM]['native'] ??= '0'; - - // add values: - $this->amounts[$key][$currencyId] = bcadd($this->amounts[$key][$currencyId], $amount); - $this->amounts[self::SUM][$currencyId] = bcadd($this->amounts[self::SUM][$currencyId], $amount); - $this->amounts[$key]['native'] = bcadd($this->amounts[$key]['native'], $nativeAmount); - $this->amounts[self::SUM]['native'] = bcadd($this->amounts[self::SUM]['native'], $nativeAmount); - } - $converter->summarize(); - } - public function groupData(): array { Log::debug('Now going to group data.'); @@ -132,6 +96,42 @@ class SummaryBalanceGrouped return $return; } + public function groupTransactions(string $key, array $journals): void + { + Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); + Log::debug(sprintf('Now in groupTransactions with key "%s" and %d journal(s)', $key, count($journals))); + $converter = new ExchangeRateConverter(); + $this->keys[] = $key; + $multiplier = 'income' === $key ? '-1' : '1'; + + /** @var array $journal */ + foreach ($journals as $journal) { + // transaction info: + $currencyId = (int)$journal['currency_id']; + $amount = bcmul($journal['amount'], $multiplier); + $currency = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId); + $this->currencies[$currencyId] = $currency; + $nativeAmount = $converter->convert($currency, $this->default, $journal['date'], $amount); + if ((int)$journal['foreign_currency_id'] === $this->default->id) { + // use foreign amount instead + $nativeAmount = $journal['foreign_amount']; + } + // prep the arrays + $this->amounts[$key] ??= []; + $this->amounts[$key][$currencyId] ??= '0'; + $this->amounts[$key]['native'] ??= '0'; + $this->amounts[self::SUM][$currencyId] ??= '0'; + $this->amounts[self::SUM]['native'] ??= '0'; + + // add values: + $this->amounts[$key][$currencyId] = bcadd($this->amounts[$key][$currencyId], $amount); + $this->amounts[self::SUM][$currencyId] = bcadd($this->amounts[self::SUM][$currencyId], $amount); + $this->amounts[$key]['native'] = bcadd($this->amounts[$key]['native'], $nativeAmount); + $this->amounts[self::SUM]['native'] = bcadd($this->amounts[self::SUM]['native'], $nativeAmount); + } + $converter->summarize(); + } + public function setDefault(TransactionCurrency $default): void { $this->default = $default; diff --git a/app/Support/Http/Controllers/ModelInformation.php b/app/Support/Http/Controllers/ModelInformation.php index 5e9217bad3..68a31bcc7a 100644 --- a/app/Support/Http/Controllers/ModelInformation.php +++ b/app/Support/Http/Controllers/ModelInformation.php @@ -84,9 +84,9 @@ trait ModelInformation /** @var AccountType $mortgage */ $mortgage = $repository->getAccountTypeByType(AccountType::MORTGAGE); $liabilityTypes = [ - $debt->id => (string) trans(sprintf('firefly.account_type_%s', AccountType::DEBT)), - $loan->id => (string) trans(sprintf('firefly.account_type_%s', AccountType::LOAN)), - $mortgage->id => (string) trans(sprintf('firefly.account_type_%s', AccountType::MORTGAGE)), + $debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::DEBT)), + $loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::LOAN)), + $mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::MORTGAGE)), ]; asort($liabilityTypes); @@ -97,7 +97,7 @@ trait ModelInformation { $roles = []; foreach (config('firefly.accountRoles') as $role) { - $roles[$role] = (string) trans(sprintf('firefly.account_role_%s', $role)); + $roles[$role] = (string)trans(sprintf('firefly.account_role_%s', $role)); } return $roles; @@ -115,7 +115,7 @@ trait ModelInformation $triggers = []; foreach ($operators as $key => $operator) { if ('user_action' !== $key && false === $operator['alias']) { - $triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key)); + $triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key)); } } asort($triggers); @@ -166,7 +166,7 @@ trait ModelInformation $triggers = []; foreach ($operators as $key => $operator) { if ('user_action' !== $key && false === $operator['alias']) { - $triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key)); + $triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key)); } } asort($triggers); diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index c11ee90e7c..15d8d43ac8 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -139,6 +139,99 @@ trait PeriodOverview return $entries; } + /** + * Filter a list of journals by a set of dates, and then group them by currency. + */ + 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. + */ + 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. + */ + 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; + } + + 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. * @@ -406,6 +499,27 @@ trait PeriodOverview return $entries; } + private function filterJournalsByTag(array $set, Tag $tag): array + { + $return = []; + foreach ($set as $entry) { + $found = false; + + /** @var array $localTag */ + foreach ($entry['tags'] as $localTag) { + if ($localTag['id'] === $tag->id) { + $found = true; + } + } + if (false === $found) { + continue; + } + $return[] = $entry; + } + + return $return; + } + /** * @throws FireflyException */ @@ -452,129 +566,15 @@ trait PeriodOverview } $entries[] = [ - 'title' => $title, - 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - 'total_transactions' => count($spent) + count($earned) + count($transferred), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred' => $this->groupByCurrency($transferred), - ]; + 'title' => $title, + 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } return $entries; } - - /** - * Filter a list of journals by a set of dates, and then group them by currency. - */ - 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. - */ - 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. - */ - 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; - } - - 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; - } - - private function filterJournalsByTag(array $set, Tag $tag): array - { - $return = []; - foreach ($set as $entry) { - $found = false; - - /** @var array $localTag */ - foreach ($entry['tags'] as $localTag) { - if ($localTag['id'] === $tag->id) { - $found = true; - } - } - if (false === $found) { - continue; - } - $return[] = $entry; - } - - return $return; - } } diff --git a/app/Support/ParseDateString.php b/app/Support/ParseDateString.php index d529c2f430..3140b93c76 100644 --- a/app/Support/ParseDateString.php +++ b/app/Support/ParseDateString.php @@ -111,58 +111,13 @@ class ParseDateString return new Carbon('1984-09-17'); } // maybe a year, nothing else? - if (4 === strlen($date) && is_numeric($date) && (int) $date > 1000 && (int) $date <= 3000) { + if (4 === strlen($date) && is_numeric($date) && (int)$date > 1000 && (int)$date <= 3000) { return new Carbon(sprintf('%d-01-01', $date)); } throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date)); } - 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; - } - protected function parseKeyword(string $keyword): Carbon { $today = today(config('app.timezone'))->startOfDay(); @@ -234,7 +189,7 @@ class ParseDateString } $direction = str_starts_with($part, '+') ? 1 : 0; $period = $part[strlen($part) - 1]; - $number = (int) substr($part, 1, -1); + $number = (int)substr($part, 1, -1); if (!array_key_exists($period, $functions[$direction])) { app('log')->error(sprintf('No method for direction %d and period "%s".', $direction, $period)); @@ -249,6 +204,51 @@ class ParseDateString return $today; } + 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; + } + /** * Returns true if this matches regex for xxxx-xx-DD: */ @@ -349,6 +349,20 @@ class ParseDateString return false; } + /** + * format of string is xxxx-MM-DD + */ + private function parseMonthDayRange(string $date): array + { + app('log')->debug(sprintf('parseMonthDayRange: Parsed "%s".', $date)); + $parts = explode('-', $date); + + return [ + 'month' => $parts[1], + 'day' => $parts[2], + ]; + } + protected function isDayYearRange(string $date): bool { // if regex for YYYY-xx-DD: @@ -364,6 +378,20 @@ class ParseDateString return false; } + /** + * format of string is YYYY-xx-DD + */ + private function parseDayYearRange(string $date): array + { + app('log')->debug(sprintf('parseDayYearRange: Parsed "%s".', $date)); + $parts = explode('-', $date); + + return [ + 'year' => $parts[0], + 'day' => $parts[2], + ]; + } + protected function isMonthYearRange(string $date): bool { // if regex for YYYY-MM-xx: @@ -392,32 +420,4 @@ class ParseDateString 'month' => $parts[1], ]; } - - /** - * format of string is xxxx-MM-DD - */ - private function parseMonthDayRange(string $date): array - { - app('log')->debug(sprintf('parseMonthDayRange: Parsed "%s".', $date)); - $parts = explode('-', $date); - - return [ - 'month' => $parts[1], - 'day' => $parts[2], - ]; - } - - /** - * format of string is YYYY-xx-DD - */ - private function parseDayYearRange(string $date): array - { - app('log')->debug(sprintf('parseDayYearRange: Parsed "%s".', $date)); - $parts = explode('-', $date); - - return [ - 'year' => $parts[0], - 'day' => $parts[2], - ]; - } } diff --git a/app/Support/Report/Budget/BudgetReportGenerator.php b/app/Support/Report/Budget/BudgetReportGenerator.php index 62a7cde382..f2836bcca4 100644 --- a/app/Support/Report/Budget/BudgetReportGenerator.php +++ b/app/Support/Report/Budget/BudgetReportGenerator.php @@ -91,59 +91,6 @@ class BudgetReportGenerator } } - /** - * Generates the data necessary to create the card that displays - * the budget overview in the general report. - */ - public function general(): void - { - $this->report = [ - 'budgets' => [], - 'sums' => [], - ]; - - $this->generalBudgetReport(); - $this->noBudgetReport(); - $this->percentageReport(); - } - - public function getReport(): array - { - return $this->report; - } - - public function setAccounts(Collection $accounts): void - { - $this->accounts = $accounts; - } - - public function setBudgets(Collection $budgets): void - { - $this->budgets = $budgets; - } - - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - public function setStart(Carbon $start): void - { - $this->start = $start; - } - - /** - * @throws FireflyException - */ - 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')->getDefaultCurrencyByUserGroup($user->userGroup); - } - /** * Process each row of expenses collected for the "Account per budget" partial */ @@ -181,6 +128,22 @@ class BudgetReportGenerator } } + /** + * Generates the data necessary to create the card that displays + * the budget overview in the general report. + */ + public function general(): void + { + $this->report = [ + 'budgets' => [], + 'sums' => [], + ]; + + $this->generalBudgetReport(); + $this->noBudgetReport(); + $this->percentageReport(); + } + /** * Start the budgets block on the default report by processing every budget. */ @@ -250,16 +213,16 @@ class BudgetReportGenerator // make sum information: $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, - ]; + '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)); @@ -349,4 +312,41 @@ class BudgetReportGenerator } } } + + public function getReport(): array + { + return $this->report; + } + + public function setAccounts(Collection $accounts): void + { + $this->accounts = $accounts; + } + + public function setBudgets(Collection $budgets): void + { + $this->budgets = $budgets; + } + + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + /** + * @throws FireflyException + */ + 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')->getDefaultCurrencyByUserGroup($user->userGroup); + } } diff --git a/app/Support/Report/Category/CategoryReportGenerator.php b/app/Support/Report/Category/CategoryReportGenerator.php index ea7971191a..67a3d872f6 100644 --- a/app/Support/Report/Category/CategoryReportGenerator.php +++ b/app/Support/Report/Category/CategoryReportGenerator.php @@ -82,27 +82,6 @@ class CategoryReportGenerator } } - public function setAccounts(Collection $accounts): void - { - $this->accounts = $accounts; - } - - public function setEnd(Carbon $end): void - { - $this->end = $end; - } - - public function setStart(Carbon $start): void - { - $this->start = $start; - } - - public function setUser(User $user): void - { - $this->noCatRepository->setUser($user); - $this->opsRepository->setUser($user); - } - /** * Process one of the spent arrays from the operations method. */ @@ -183,4 +162,25 @@ class CategoryReportGenerator ) : $this->report['categories'][$key]['earned']; } } + + public function setAccounts(Collection $accounts): void + { + $this->accounts = $accounts; + } + + public function setEnd(Carbon $end): void + { + $this->end = $end; + } + + public function setStart(Carbon $start): void + { + $this->start = $start; + } + + public function setUser(User $user): void + { + $this->noCatRepository->setUser($user); + $this->opsRepository->setUser($user); + } } diff --git a/app/Support/Request/AppendsLocationData.php b/app/Support/Request/AppendsLocationData.php index 99d26cc2e3..eeba807830 100644 --- a/app/Support/Request/AppendsLocationData.php +++ b/app/Support/Request/AppendsLocationData.php @@ -28,43 +28,6 @@ namespace FireflyIII\Support\Request; */ trait AppendsLocationData { - /** - * Abstract method. - * - * @param mixed $key - * - * @return bool - */ - 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); - - /** - * Abstract method stolen from "InteractsWithInput". - * - * @param null $key - * @param bool $default - * - * @return mixed - * - * @SuppressWarnings(PHPMD.BooleanArgumentFlag) - */ - abstract public function boolean($key = null, $default = false); - public function addFromromTransactionStore(array $information, array $return): array { $return['store_location'] = false; @@ -82,6 +45,29 @@ trait AppendsLocationData return $return; } + private function validLongitude(string $longitude): bool + { + $number = (float)$longitude; + + return $number >= -180 && $number <= 180; + } + + private function validLatitude(string $latitude): bool + { + $number = (float)$latitude; + + return $number >= -90 && $number <= 90; + } + + /** + * Abstract method. + * + * @param mixed $key + * + * @return bool + */ + abstract public function has($key); + /** * Read the submitted Request data and add new or updated Location data to the array. */ @@ -136,20 +122,6 @@ trait AppendsLocationData return $data; } - private function validLongitude(string $longitude): bool - { - $number = (float) $longitude; - - return $number >= -180 && $number <= 180; - } - - private function validLatitude(string $latitude): bool - { - $number = (float) $latitude; - - return $number >= -90 && $number <= 90; - } - private function getLocationKey(?string $prefix, string $key): string { if (null === $prefix) { @@ -197,6 +169,34 @@ 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 + * + * @SuppressWarnings(PHPMD.BooleanArgumentFlag) + */ + abstract public function boolean($key = null, $default = false); + private function isValidPUT(?string $prefix): bool { $longitudeKey = $this->getLocationKey($prefix, 'longitude'); diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index b98ff19295..0e1db9ab00 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -189,16 +189,6 @@ trait ConvertsDataTypes return (string)$this->clearStringKeepNewlines((string)($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 mixed $key - * - * @return mixed - */ - abstract public function has($key); - /** * @param mixed $array */ @@ -340,6 +330,16 @@ 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/Request/GetRecurrenceData.php b/app/Support/Request/GetRecurrenceData.php index 4c956a66cf..a687cf3215 100644 --- a/app/Support/Request/GetRecurrenceData.php +++ b/app/Support/Request/GetRecurrenceData.php @@ -37,12 +37,12 @@ trait GetRecurrenceData foreach ($stringKeys as $key) { if (array_key_exists($key, $transaction)) { - $return[$key] = (string) $transaction[$key]; + $return[$key] = (string)$transaction[$key]; } } foreach ($intKeys as $key) { if (array_key_exists($key, $transaction)) { - $return[$key] = (int) $transaction[$key]; + $return[$key] = (int)$transaction[$key]; } } foreach ($keys as $key) { diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 8d78fdd9c7..dcac60ceda 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -160,87 +160,6 @@ class OperatorQuerySearch implements SearchInterface $this->collector->excludeSearchWords($this->prohibitedWords); } - /** - * @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']); - } - app('log')->debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return)); - - return $return; - } - app('log')->debug(sprintf('"%s" is not an alias.', $operator)); - - return $original; - } - - public function searchTime(): float - { - return microtime(true) - $this->startTime; - } - - public function searchTransactions(): LengthAwarePaginator - { - $this->parseTagInstructions(); - if (0 === count($this->getWords()) && 0 === count($this->getOperators())) { - return new LengthAwarePaginator([], 0, 5, 1); - } - - return $this->collector->getPaginatedGroups(); - } - - public function getWords(): array - { - return $this->words; - } - - public function setDate(Carbon $date): void - { - $this->date = $date; - } - - public function setPage(int $page): void - { - $this->page = $page; - $this->collector->setPage($this->page); - } - - 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); - } - - public function setLimit(int $limit): void - { - $this->limit = $limit; - $this->collector->setLimit($this->limit); - } - /** * @throws FireflyException * @@ -1899,6 +1818,36 @@ class OperatorQuerySearch implements SearchInterface return true; } + /** + * @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']); + } + app('log')->debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return)); + + return $return; + } + app('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 @@ -2731,6 +2680,21 @@ class OperatorQuerySearch implements SearchInterface } } + public function searchTime(): float + { + return microtime(true) - $this->startTime; + } + + public function searchTransactions(): LengthAwarePaginator + { + $this->parseTagInstructions(); + if (0 === count($this->getWords()) && 0 === count($this->getOperators())) { + return new LengthAwarePaginator([], 0, 5, 1); + } + + return $this->collector->getPaginatedGroups(); + } + private function parseTagInstructions(): void { app('log')->debug('Now in parseTagInstructions()'); @@ -2762,4 +2726,40 @@ class OperatorQuerySearch implements SearchInterface $this->collector->setAllTags($collection); } } + + public function getWords(): array + { + return $this->words; + } + + public function setDate(Carbon $date): void + { + $this->date = $date; + } + + public function setPage(int $page): void + { + $this->page = $page; + $this->collector->setPage($this->page); + } + + 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); + } + + 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 0e08bedaea..b08ccb6389 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index 8b6e9697d7..6f96f42448 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -43,15 +43,6 @@ class AmountFormat extends AbstractExtension ]; } - public function getFunctions(): array - { - return [ - $this->formatAmountByAccount(), - $this->formatAmountBySymbol(), - $this->formatAmountByCurrency(), - ]; - } - protected function formatAmount(): TwigFilter { return new TwigFilter( @@ -78,6 +69,15 @@ class AmountFormat extends AbstractExtension ); } + public function getFunctions(): array + { + return [ + $this->formatAmountByAccount(), + $this->formatAmountBySymbol(), + $this->formatAmountByCurrency(), + ]; + } + /** * Will format the amount by the currency related to the given account. * diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index d95837dc55..4186ef6f15 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -29,6 +29,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Support\Search\OperatorQuerySearch; use League\CommonMark\GithubFlavoredMarkdownConverter; +use Route; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; @@ -49,22 +50,6 @@ class General extends AbstractExtension ]; } - 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(), - ]; - } - /** * Show account balance. Only used on the front page of Firefly III. */ @@ -206,7 +191,7 @@ class General extends AbstractExtension ] ); - return (string) $converter->convert($text); + return (string)$converter->convert($text); }, ['is_safe' => ['html']] ); @@ -220,14 +205,30 @@ class General extends AbstractExtension return new TwigFilter( 'phphost', static function (string $string): string { - $proto = (string) parse_url($string, PHP_URL_SCHEME); - $host = (string) parse_url($string, PHP_URL_HOST); + $proto = (string)parse_url($string, PHP_URL_SCHEME); + $host = (string)parse_url($string, PHP_URL_HOST); return e(sprintf('%s://%s', $proto, $host)); } ); } + 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. */ diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php index e3c26adaba..8dda66844c 100644 --- a/app/Support/Twig/TransactionGroupTwig.php +++ b/app/Support/Twig/TransactionGroupTwig.php @@ -70,86 +70,6 @@ class TransactionGroupTwig extends AbstractExtension ); } - /** - * Shows the amount for a single journal object. - */ - 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']] - ); - } - - 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; - } - ); - } - - public function journalGetMetaDate(): TwigFunction - { - return new TwigFunction( - 'journalGetMetaDate', - static function (int $journalId, string $metaField) { - /** @var null|TransactionJournalMeta $entry */ - $entry = \DB::table('journal_meta') - ->where('name', $metaField) - ->where('transaction_journal_id', $journalId) - ->whereNull('deleted_at') - ->first() - ; - if (null === $entry) { - return today(config('app.timezone')); - } - - return new Carbon(json_decode($entry->data, false)); - } - ); - } - - public function journalGetMetaField(): TwigFunction - { - return new TwigFunction( - 'journalGetMetaField', - static function (int $journalId, string $metaField) { - /** @var null|TransactionJournalMeta $entry */ - $entry = \DB::table('journal_meta') - ->where('name', $metaField) - ->where('transaction_journal_id', $journalId) - ->whereNull('deleted_at') - ->first() - ; - if (null === $entry) { - return ''; - } - - return json_decode($entry->data, true); - } - ); - } - /** * Generate normal amount for transaction from a transaction group. */ @@ -216,6 +136,27 @@ class TransactionGroupTwig extends AbstractExtension return $result; } + /** + * Shows the amount for a single journal object. + */ + 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. */ @@ -275,4 +216,63 @@ class TransactionGroupTwig extends AbstractExtension return $result; } + + 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; + } + ); + } + + public function journalGetMetaDate(): TwigFunction + { + return new TwigFunction( + 'journalGetMetaDate', + static function (int $journalId, string $metaField) { + /** @var null|TransactionJournalMeta $entry */ + $entry = \DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first() + ; + if (null === $entry) { + return today(config('app.timezone')); + } + + return new Carbon(json_decode($entry->data, false)); + } + ); + } + + public function journalGetMetaField(): TwigFunction + { + return new TwigFunction( + 'journalGetMetaField', + static function (int $journalId, string $metaField) { + /** @var null|TransactionJournalMeta $entry */ + $entry = \DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first() + ; + if (null === $entry) { + return ''; + } + + return json_decode($entry->data, true); + } + ); + } } diff --git a/app/Support/Validation/ValidatesAmountsTrait.php b/app/Support/Validation/ValidatesAmountsTrait.php index d07186f4ac..6529daa23b 100644 --- a/app/Support/Validation/ValidatesAmountsTrait.php +++ b/app/Support/Validation/ValidatesAmountsTrait.php @@ -38,30 +38,30 @@ trait ValidatesAmountsTrait return is_numeric($value); } - final protected function scientificNumber(string $value): bool - { - return str_contains(strtoupper($value), 'E'); - } - final protected function lessOrEqualToZero(string $value): bool { return -1 === bccomp($value, '0') || 0 === bccomp($value, '0'); } - final protected function zeroOrMore(string $value): bool - { - return 1 === bccomp($value, '0') || 0 === bccomp($value, '0'); - } - - final protected function moreThanLots(string $value): bool - { - return 1 === bccomp($value, self::BIG_AMOUNT) || 0 === bccomp($value, self::BIG_AMOUNT); - } - final protected function lessThanLots(string $value): bool { $amount = bcmul('-1', self::BIG_AMOUNT); return -1 === bccomp($value, $amount) || 0 === bccomp($value, $amount); } + + final protected function moreThanLots(string $value): bool + { + return 1 === bccomp($value, self::BIG_AMOUNT) || 0 === bccomp($value, self::BIG_AMOUNT); + } + + final protected function scientificNumber(string $value): bool + { + return str_contains(strtoupper($value), 'E'); + } + + final protected function zeroOrMore(string $value): bool + { + return 1 === bccomp($value, '0') || 0 === bccomp($value, '0'); + } } diff --git a/app/TransactionRules/Actions/AppendNotesToDescription.php b/app/TransactionRules/Actions/AppendNotesToDescription.php index 3cc4a16709..99caaaeb0d 100644 --- a/app/TransactionRules/Actions/AppendNotesToDescription.php +++ b/app/TransactionRules/Actions/AppendNotesToDescription.php @@ -70,7 +70,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))); + $object->description = trim(sprintf('%s %s', $object->description, (string)$this->clearString($note->text))); $object->save(); app('log')->debug(sprintf('Journal description is updated to "%s".', $object->description)); diff --git a/app/TransactionRules/Actions/ConvertToTransfer.php b/app/TransactionRules/Actions/ConvertToTransfer.php index 3472c72d07..323270f35c 100644 --- a/app/TransactionRules/Actions/ConvertToTransfer.php +++ b/app/TransactionRules/Actions/ConvertToTransfer.php @@ -166,7 +166,7 @@ class ConvertToTransfer implements ActionInterface return ''; } - return (string) $journal->transactions()->where('amount', '<', 0)->first()?->account?->accountType?->type; + return (string)$journal->transactions()->where('amount', '<', 0)->first()?->account?->accountType?->type; } private function getDestinationType(int $journalId): string @@ -179,7 +179,7 @@ class ConvertToTransfer implements ActionInterface return ''; } - return (string) $journal->transactions()->where('amount', '>', 0)->first()?->account?->accountType?->type; + return (string)$journal->transactions()->where('amount', '>', 0)->first()?->account?->accountType?->type; } /** diff --git a/app/TransactionRules/Actions/MoveNotesToDescription.php b/app/TransactionRules/Actions/MoveNotesToDescription.php index 1814370f51..5c18ab78a7 100644 --- a/app/TransactionRules/Actions/MoveNotesToDescription.php +++ b/app/TransactionRules/Actions/MoveNotesToDescription.php @@ -77,7 +77,7 @@ class MoveNotesToDescription implements ActionInterface } $before = $object->description; $beforeNote = $note->text; - $object->description = (string) $this->clearString($note->text); + $object->description = (string)$this->clearString($note->text); $object->save(); $note->delete(); diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php index d4828acdbf..45733171a4 100644 --- a/app/TransactionRules/Engine/SearchRuleEngine.php +++ b/app/TransactionRules/Engine/SearchRuleEngine.php @@ -83,85 +83,6 @@ class SearchRuleEngine implements RuleEngineInterface return $collection->unique(); } - public function setUser(User $user): void - { - $this->user = $user; - $this->operators = []; - } - - /** - * @throws FireflyException - */ - public function fire(): void - { - $this->resultCount = []; - app('log')->debug('SearchRuleEngine::fire()!'); - - // if rules and no rule groups, file each rule separately. - if (0 !== $this->rules->count()) { - app('log')->debug(sprintf('SearchRuleEngine:: found %d rule(s) to fire.', $this->rules->count())); - - /** @var Rule $rule */ - foreach ($this->rules as $rule) { - $result = $this->fireRule($rule); - if (true === $result && $rule->stop_processing) { - app('log')->debug(sprintf('Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.', $rule->id)); - } - if (false === $result && $rule->stop_processing) { - app('log')->debug(sprintf('Rule #%d has triggered and changed nothing, but calls to stop processing. Do not stop.', $rule->id)); - } - } - app('log')->debug('SearchRuleEngine:: done processing all rules!'); - - return; - } - if (0 !== $this->groups->count()) { - app('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); - } - } - app('log')->debug('SearchRuleEngine:: done processing all rules!'); - } - - /** - * Return the number of changed transactions from the previous "fire" action. - */ - public function getResults(): int - { - return count($this->resultCount); - } - - public function setRefreshTriggers(bool $refreshTriggers): void - { - $this->refreshTriggers = $refreshTriggers; - } - - public function setRuleGroups(Collection $ruleGroups): void - { - app('log')->debug(__METHOD__); - foreach ($ruleGroups as $group) { - if ($group instanceof RuleGroup) { - app('log')->debug(sprintf('Adding a rule group to the SearchRuleEngine: #%d ("%s")', $group->id, $group->title)); - $this->groups->push($group); - } - } - } - - public function setRules(Collection $rules): void - { - app('log')->debug(__METHOD__); - foreach ($rules as $rule) { - if ($rule instanceof Rule) { - app('log')->debug(sprintf('Adding a rule to the SearchRuleEngine: #%d ("%s")', $rule->id, $rule->title)); - $this->rules->push($rule); - } - } - } - /** * Finds the transactions a strict rule will execute on. */ @@ -184,7 +105,7 @@ class SearchRuleEngine implements RuleEngineInterface } // if the trigger needs no context, value is different: - $needsContext = (bool) (config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true); + $needsContext = (bool)(config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true); if (false === $needsContext) { app('log')->debug(sprintf('SearchRuleEngine:: add a rule trigger (no context): %s:true', $ruleTrigger->trigger_type)); $searchArray[$ruleTrigger->trigger_type][] = 'true'; @@ -257,7 +178,7 @@ class SearchRuleEngine implements RuleEngineInterface $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". + $journalId = (int)trim($values[0] ?? '"0"', '"'); // follows format "123". app('log')->debug(sprintf('Found journal ID #%d', $journalId)); } } @@ -277,6 +198,12 @@ class SearchRuleEngine implements RuleEngineInterface return today(config('app.timezone')); } + public function setUser(User $user): void + { + $this->user = $user; + $this->operators = []; + } + private function findNonStrictRule(Rule $rule): Collection { app('log')->debug(sprintf('findNonStrictRule(#%d)', $rule->id)); @@ -368,6 +295,44 @@ class SearchRuleEngine implements RuleEngineInterface return $unique; } + /** + * @throws FireflyException + */ + public function fire(): void + { + $this->resultCount = []; + app('log')->debug('SearchRuleEngine::fire()!'); + + // if rules and no rule groups, file each rule separately. + if (0 !== $this->rules->count()) { + app('log')->debug(sprintf('SearchRuleEngine:: found %d rule(s) to fire.', $this->rules->count())); + + /** @var Rule $rule */ + foreach ($this->rules as $rule) { + $result = $this->fireRule($rule); + if (true === $result && $rule->stop_processing) { + app('log')->debug(sprintf('Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.', $rule->id)); + } + if (false === $result && $rule->stop_processing) { + app('log')->debug(sprintf('Rule #%d has triggered and changed nothing, but calls to stop processing. Do not stop.', $rule->id)); + } + } + app('log')->debug('SearchRuleEngine:: done processing all rules!'); + + return; + } + if (0 !== $this->groups->count()) { + app('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); + } + } + app('log')->debug('SearchRuleEngine:: done processing all rules!'); + } + /** * Returns true if the rule has been triggered. * @@ -532,4 +497,39 @@ class SearchRuleEngine implements RuleEngineInterface } } } + + /** + * Return the number of changed transactions from the previous "fire" action. + */ + public function getResults(): int + { + return count($this->resultCount); + } + + public function setRefreshTriggers(bool $refreshTriggers): void + { + $this->refreshTriggers = $refreshTriggers; + } + + public function setRuleGroups(Collection $ruleGroups): void + { + app('log')->debug(__METHOD__); + foreach ($ruleGroups as $group) { + if ($group instanceof RuleGroup) { + app('log')->debug(sprintf('Adding a rule group to the SearchRuleEngine: #%d ("%s")', $group->id, $group->title)); + $this->groups->push($group); + } + } + } + + public function setRules(Collection $rules): void + { + app('log')->debug(__METHOD__); + foreach ($rules as $rule) { + if ($rule instanceof Rule) { + app('log')->debug(sprintf('Adding a rule to the SearchRuleEngine: #%d ("%s")', $rule->id, $rule->title)); + $this->rules->push($rule); + } + } + } } diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index 422231ae0e..36988b5e2a 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -138,7 +138,7 @@ class BillTransformer extends AbstractTransformer 'id' => $bill->id, 'created_at' => $bill->created_at->toAtomString(), 'updated_at' => $bill->updated_at->toAtomString(), - 'currency_id' => (string) $bill->transaction_currency_id, + 'currency_id' => (string)$bill->transaction_currency_id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, @@ -153,7 +153,7 @@ class BillTransformer extends AbstractTransformer 'active' => $bill->active, 'order' => $bill->order, 'notes' => $notes, - 'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null, + 'object_group_id' => null !== $objectGroupId ? (string)$objectGroupId : null, 'object_group_order' => $objectGroupOrder, 'object_group_title' => $objectGroupTitle, @@ -207,8 +207,8 @@ class BillTransformer extends AbstractTransformer $result = []; foreach ($set as $entry) { $result[] = [ - 'transaction_group_id' => (string) $entry->transaction_group_id, - 'transaction_journal_id' => (string) $entry->id, + 'transaction_group_id' => (string)$entry->transaction_group_id, + 'transaction_journal_id' => (string)$entry->id, 'date' => $entry->date->format('Y-m-d'), 'date_object' => $entry->date, ]; diff --git a/app/Transformers/TransactionGroupTransformer.php b/app/Transformers/TransactionGroupTransformer.php index dc3ef19c92..a0247b7314 100644 --- a/app/Transformers/TransactionGroupTransformer.php +++ b/app/Transformers/TransactionGroupTransformer.php @@ -80,10 +80,10 @@ class TransactionGroupTransformer extends AbstractTransformer $first = new NullArrayObject(reset($group['transactions'])); return [ - 'id' => (int) $first['transaction_group_id'], + 'id' => (int)$first['transaction_group_id'], 'created_at' => $first['created_at']->toAtomString(), 'updated_at' => $first['updated_at']->toAtomString(), - 'user' => (string) $data['user_id'], + 'user' => (string)$data['user_id'], 'group_title' => $data['title'], 'transactions' => $this->transformTransactions($data), 'links' => [ @@ -95,38 +95,6 @@ class TransactionGroupTransformer extends AbstractTransformer ]; } - /** - * @throws FireflyException - */ - public function transformObject(TransactionGroup $group): array - { - try { - $result = [ - 'id' => $group->id, - 'created_at' => $group->created_at->toAtomString(), - 'updated_at' => $group->updated_at->toAtomString(), - 'user' => $group->user_id, - 'group_title' => $group->title, - 'transactions' => $this->transformJournals($group->transactionJournals), - 'links' => [ - [ - 'rel' => 'self', - 'uri' => '/transactions/'.$group->id, - ], - ], - ]; - } catch (FireflyException $e) { - app('log')->error($e->getMessage()); - app('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; - } - private function transformTransactions(NullArrayObject $data): array { $result = []; @@ -146,20 +114,20 @@ class TransactionGroupTransformer extends AbstractTransformer $row = new NullArrayObject($transaction); // amount: - $amount = app('steam')->positive((string) ($row['amount'] ?? '0')); + $amount = app('steam')->positive((string)($row['amount'] ?? '0')); $foreignAmount = null; if (null !== $row['foreign_amount'] && '' !== $row['foreign_amount'] && 0 !== bccomp('0', $row['foreign_amount'])) { $foreignAmount = app('steam')->positive($row['foreign_amount']); } - $metaFieldData = $this->groupRepos->getMetaFields((int) $row['transaction_journal_id'], $this->metaFields); - $metaDateData = $this->groupRepos->getMetaDateFields((int) $row['transaction_journal_id'], $this->metaDateFields); + $metaFieldData = $this->groupRepos->getMetaFields((int)$row['transaction_journal_id'], $this->metaFields); + $metaDateData = $this->groupRepos->getMetaDateFields((int)$row['transaction_journal_id'], $this->metaDateFields); $type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionType::WITHDRAWAL); $longitude = null; $latitude = null; $zoomLevel = null; - $location = $this->getLocationById((int) $row['transaction_journal_id']); + $location = $this->getLocationById((int)$row['transaction_journal_id']); if (null !== $location) { $longitude = $location->longitude; $latitude = $location->latitude; @@ -167,17 +135,17 @@ class TransactionGroupTransformer extends AbstractTransformer } return [ - 'user' => (string) $row['user_id'], - 'transaction_journal_id' => (string) $row['transaction_journal_id'], + 'user' => (string)$row['user_id'], + 'transaction_journal_id' => (string)$row['transaction_journal_id'], 'type' => strtolower($type), 'date' => $row['date']->toAtomString(), 'order' => $row['order'], - 'currency_id' => (string) $row['currency_id'], + 'currency_id' => (string)$row['currency_id'], 'currency_code' => $row['currency_code'], 'currency_name' => $row['currency_name'], 'currency_symbol' => $row['currency_symbol'], - 'currency_decimal_places' => (int) $row['currency_decimal_places'], + 'currency_decimal_places' => (int)$row['currency_decimal_places'], 'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null), 'foreign_currency_code' => $row['foreign_currency_code'], @@ -189,12 +157,12 @@ class TransactionGroupTransformer extends AbstractTransformer 'description' => $row['description'], - 'source_id' => (string) $row['source_account_id'], + 'source_id' => (string)$row['source_account_id'], 'source_name' => $row['source_account_name'], 'source_iban' => $row['source_account_iban'], 'source_type' => $row['source_account_type'], - 'destination_id' => (string) $row['destination_account_id'], + 'destination_id' => (string)$row['destination_account_id'], 'destination_name' => $row['destination_account_name'], 'destination_iban' => $row['destination_account_iban'], 'destination_type' => $row['destination_account_type'], @@ -209,8 +177,8 @@ class TransactionGroupTransformer extends AbstractTransformer 'bill_name' => $row['bill_name'], 'reconciled' => $row['reconciled'], - 'notes' => $this->groupRepos->getNoteText((int) $row['transaction_journal_id']), - 'tags' => $this->groupRepos->getTags((int) $row['transaction_journal_id']), + 'notes' => $this->groupRepos->getNoteText((int)$row['transaction_journal_id']), + 'tags' => $this->groupRepos->getTags((int)$row['transaction_journal_id']), 'internal_reference' => $metaFieldData['internal_reference'], 'external_id' => $metaFieldData['external_id'], @@ -243,7 +211,7 @@ class TransactionGroupTransformer extends AbstractTransformer 'latitude' => $latitude, 'zoom_level' => $zoomLevel, - 'has_attachments' => $this->hasAttachments((int) $row['transaction_journal_id']), + 'has_attachments' => $this->hasAttachments((int)$row['transaction_journal_id']), ]; } @@ -260,7 +228,7 @@ class TransactionGroupTransformer extends AbstractTransformer return $default; } - return (string) $array[$key]; + return (string)$array[$key]; } if (null !== $default) { @@ -283,7 +251,7 @@ class TransactionGroupTransformer extends AbstractTransformer private function integerFromArray(array $array, string $key): ?int { if (array_key_exists($key, $array)) { - return (int) $array[$key]; + return (int)$array[$key]; } return null; @@ -303,6 +271,38 @@ class TransactionGroupTransformer extends AbstractTransformer return $this->groupRepos->countAttachments($journalId) > 0; } + /** + * @throws FireflyException + */ + public function transformObject(TransactionGroup $group): array + { + try { + $result = [ + 'id' => $group->id, + 'created_at' => $group->created_at->toAtomString(), + 'updated_at' => $group->updated_at->toAtomString(), + 'user' => $group->user_id, + 'group_title' => $group->title, + 'transactions' => $this->transformJournals($group->transactionJournals), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/transactions/'.$group->id, + ], + ], + ]; + } catch (FireflyException $e) { + app('log')->error($e->getMessage()); + app('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; + } + /** * @throws FireflyException */ @@ -434,7 +434,7 @@ class TransactionGroupTransformer extends AbstractTransformer { $result = $journal->transactions->first( static function (Transaction $transaction) { - return (float) $transaction->amount < 0; // lame but it works. + return (float)$transaction->amount < 0; // lame but it works. } ); if (null === $result) { @@ -451,7 +451,7 @@ class TransactionGroupTransformer extends AbstractTransformer { $result = $journal->transactions->first( static function (Transaction $transaction) { - return (float) $transaction->amount > 0; // lame but it works + return (float)$transaction->amount > 0; // lame but it works } ); if (null === $result) { @@ -555,7 +555,7 @@ class TransactionGroupTransformer extends AbstractTransformer if (null === $bill) { return $array; } - $array['id'] = (string) $bill->id; + $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 67cf47ffb6..c36d19f1f2 100644 --- a/app/Transformers/V2/AccountTransformer.php +++ b/app/Transformers/V2/AccountTransformer.php @@ -90,6 +90,16 @@ class AccountTransformer extends AbstractTransformer } } + 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. */ @@ -168,14 +178,4 @@ class AccountTransformer extends AbstractTransformer ], ]; } - - 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/BillTransformer.php b/app/Transformers/V2/BillTransformer.php index 170dfb84bc..d6f7a36968 100644 --- a/app/Transformers/V2/BillTransformer.php +++ b/app/Transformers/V2/BillTransformer.php @@ -85,8 +85,8 @@ class BillTransformer extends AbstractTransformer /** @var ObjectGroup $entry */ foreach ($set as $entry) { - $billId = (int) $entry->object_groupable_id; - $id = (int) $entry->object_group_id; + $billId = (int)$entry->object_groupable_id; + $id = (int)$entry->object_group_id; $order = $entry->order; $this->groups[$billId] = [ 'object_group_id' => $id, @@ -124,8 +124,8 @@ class BillTransformer extends AbstractTransformer foreach ($journals as $journal) { app('log')->debug(sprintf('Processing journal #%d', $journal->id)); $transaction = $transactions[$journal->id] ?? []; - $billId = (int) $journal->bill_id; - $currencyId = (int) ($transaction['transaction_currency_id'] ?? 0); + $billId = (int)$journal->bill_id; + $currencyId = (int)($transaction['transaction_currency_id'] ?? 0); $currencies[$currencyId] ??= TransactionCurrency::find($currencyId); // foreign currency @@ -137,7 +137,7 @@ class BillTransformer extends AbstractTransformer app('log')->debug('Foreign currency is NULL'); if (null !== $transaction['foreign_currency_id']) { app('log')->debug(sprintf('Foreign currency is #%d', $transaction['foreign_currency_id'])); - $foreignCurrencyId = (int) $transaction['foreign_currency_id']; + $foreignCurrencyId = (int)$transaction['foreign_currency_id']; $currencies[$foreignCurrencyId] ??= TransactionCurrency::find($foreignCurrencyId); $foreignCurrencyCode = $currencies[$foreignCurrencyId]->code; $foreignCurrencyName = $currencies[$foreignCurrencyId]->name; @@ -146,8 +146,8 @@ class BillTransformer extends AbstractTransformer } $this->paidDates[$billId][] = [ - 'transaction_group_id' => (string) $journal->id, - 'transaction_journal_id' => (string) $journal->transaction_group_id, + 'transaction_group_id' => (string)$journal->id, + 'transaction_journal_id' => (string)$journal->transaction_group_id, 'date' => $journal->date->toAtomString(), 'currency_id' => $currencies[$currencyId]->id, 'currency_code' => $currencies[$currencyId]->code, @@ -166,7 +166,7 @@ class BillTransformer extends AbstractTransformer 'amount' => $transaction['amount'], 'foreign_amount' => $transaction['foreign_amount'], 'native_amount' => $this->converter->convert($currencies[$currencyId], $this->default, $journal->date, $transaction['amount']), - 'foreign_native_amount' => '' === (string) $transaction['foreign_amount'] ? null : $this->converter->convert( + 'foreign_native_amount' => '' === (string)$transaction['foreign_amount'] ? null : $this->converter->convert( $currencies[$foreignCurrencyId], $this->default, $journal->date, @@ -207,7 +207,7 @@ class BillTransformer extends AbstractTransformer 'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places), 'native_amount_min' => $this->converter->convert($currency, $this->default, $date, $bill->amount_min), 'native_amount_max' => $this->converter->convert($currency, $this->default, $date, $bill->amount_max), - 'currency_id' => (string) $bill->transaction_currency_id, + 'currency_id' => (string)$bill->transaction_currency_id, 'currency_code' => $currency->code, 'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol, diff --git a/app/Transformers/V2/PiggyBankTransformer.php b/app/Transformers/V2/PiggyBankTransformer.php index 1c227931e3..bf16a99608 100644 --- a/app/Transformers/V2/PiggyBankTransformer.php +++ b/app/Transformers/V2/PiggyBankTransformer.php @@ -106,7 +106,7 @@ class PiggyBankTransformer extends AbstractTransformer $id = (int)$entry->object_group_id; $order = $entry->order; $this->groups[$piggyBankId] = [ - 'object_group_id' => (string) $id, + 'object_group_id' => (string)$id, 'object_group_title' => $entry->title, 'object_group_order' => $order, ]; diff --git a/app/Transformers/V2/TransactionGroupTransformer.php b/app/Transformers/V2/TransactionGroupTransformer.php index 6dc8716957..7838b24fca 100644 --- a/app/Transformers/V2/TransactionGroupTransformer.php +++ b/app/Transformers/V2/TransactionGroupTransformer.php @@ -47,11 +47,11 @@ use Illuminate\Support\Facades\DB; class TransactionGroupTransformer extends AbstractTransformer { private array $accountTypes = []; // account types collection. - private array $journals = []; // collection of all journals and some important meta-data. + private ExchangeRateConverter $converter; // collection of all journals and some important meta-data. + private array $currencies = []; + private TransactionCurrency $default; // collection of all currencies for this transformer. + private array $journals = []; private array $objects = []; - private array $currencies = []; // collection of all currencies for this transformer. - private TransactionCurrency $default; - private ExchangeRateConverter $converter; // private array $currencies = []; // private array $transactionTypes = []; @@ -96,17 +96,175 @@ class TransactionGroupTransformer extends AbstractTransformer } } + private function collectForArray(array $object): void + { + foreach ($object['sums'] as $sum) { + $this->currencies[(int)$sum['currency_id']] ??= TransactionCurrency::find($sum['currency_id']); + } + + /** @var array $transaction */ + foreach ($object['transactions'] as $transaction) { + $this->journals[(int)$transaction['transaction_journal_id']] = []; + } + } + + private function collectForObject(TransactionGroup $object): void + { + foreach ($object->transactionJournals as $journal) { + $this->journals[$journal->id] = []; + $this->objects[] = $journal; + } + } + + private function collectAllMetaData(): void + { + $meta = TransactionJournalMeta::whereIn('transaction_journal_id', array_keys($this->journals))->get(); + + /** @var TransactionJournalMeta $entry */ + foreach ($meta as $entry) { + $id = $entry->transaction_journal_id; + $this->journals[$id]['meta'] ??= []; + $this->journals[$id]['meta'][$entry->name] = $entry->data; + } + } + + private function collectAllNotes(): void + { + // grab all notes for all journals: + $notes = Note::whereNoteableType(TransactionJournal::class)->whereIn('noteable_id', array_keys($this->journals))->get(); + + /** @var Note $note */ + foreach ($notes as $note) { + $id = $note->noteable_id; + $this->journals[$id]['notes'] = $note->text; + } + } + + private function collectAllLocations(): void + { + // grab all locations for all journals: + $locations = Location::whereLocatableType(TransactionJournal::class)->whereIn('locatable_id', array_keys($this->journals))->get(); + + /** @var Location $location */ + foreach ($locations as $location) { + $id = $location->locatable_id; + $this->journals[$id]['location'] = [ + 'latitude' => $location->latitude, + 'longitude' => $location->longitude, + 'zoom_level' => $location->zoom_level, + ]; + } + } + + private function collectAllTags(): void + { + // grab all tags for all journals: + $tags = DB::table('tag_transaction_journal') + ->leftJoin('tags', 'tags.id', 'tag_transaction_journal.tag_id') + ->whereIn('tag_transaction_journal.transaction_journal_id', array_keys($this->journals)) + ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']) + ; + + /** @var \stdClass $tag */ + foreach ($tags as $tag) { + $id = (int)$tag->transaction_journal_id; + $this->journals[$id]['tags'][] = $tag->tag; + } + } + + private function collectAllCurrencies(): void + { + /** @var TransactionJournal $journal */ + foreach ($this->objects as $journal) { + $id = $journal->id; + $this->journals[$id]['reconciled'] = false; + $this->journals[$id]['foreign_amount'] = null; + $this->journals[$id]['foreign_currency_id'] = null; + $this->journals[$id]['amount'] = null; + $this->journals[$id]['currency_id'] = null; + $this->journals[$id]['type'] = $journal->transactionType->type; + $this->journals[$id]['budget_id'] = null; + $this->journals[$id]['budget_name'] = null; + $this->journals[$id]['category_id'] = null; + $this->journals[$id]['category_name'] = null; + $this->journals[$id]['bill_id'] = null; + $this->journals[$id]['bill_name'] = null; + + // collect budget: + /** @var null|Budget $budget */ + $budget = $journal->budgets()->first(); + if (null !== $budget) { + $this->journals[$id]['budget_id'] = (string)$budget->id; + $this->journals[$id]['budget_name'] = $budget->name; + } + + // collect category: + /** @var null|Category $category */ + $category = $journal->categories()->first(); + if (null !== $category) { + $this->journals[$id]['category_id'] = (string)$category->id; + $this->journals[$id]['category_name'] = $category->name; + } + + // collect bill: + if (null !== $journal->bill_id) { + $bill = $journal->bill; + $this->journals[$id]['bill_id'] = (string)$bill->id; + $this->journals[$id]['bill_name'] = $bill->name; + } + + /** @var Transaction $transaction */ + foreach ($journal->transactions as $transaction) { + if (-1 === bccomp($transaction->amount, '0')) { + // only collect source account info + $account = $transaction->account; + $this->accountTypes[$account->account_type_id] ??= $account->accountType->type; + $this->journals[$id]['source_account_name'] = $account->name; + $this->journals[$id]['source_account_iban'] = $account->iban; + $this->journals[$id]['source_account_type'] = $this->accountTypes[$account->account_type_id]; + $this->journals[$id]['source_account_id'] = $transaction->account_id; + $this->journals[$id]['reconciled'] = $transaction->reconciled; + + continue; + } + + // add account + $account = $transaction->account; + $this->accountTypes[$account->account_type_id] ??= $account->accountType->type; + $this->journals[$id]['destination_account_name'] = $account->name; + $this->journals[$id]['destination_account_iban'] = $account->iban; + $this->journals[$id]['destination_account_type'] = $this->accountTypes[$account->account_type_id]; + $this->journals[$id]['destination_account_id'] = $transaction->account_id; + + // find and set currency + $currencyId = $transaction->transaction_currency_id; + $this->currencies[$currencyId] ??= $transaction->transactionCurrency; + $this->journals[$id]['currency_id'] = $currencyId; + $this->journals[$id]['amount'] = $transaction->amount; + // find and set foreign currency + if (null !== $transaction->foreign_currency_id) { + $foreignCurrencyId = $transaction->foreign_currency_id; + $this->currencies[$foreignCurrencyId] ??= $transaction->foreignCurrency; + $this->journals[$id]['foreign_currency_id'] = $foreignCurrencyId; + $this->journals[$id]['foreign_amount'] = $transaction->foreign_amount; + } + + // find and set destination account info. + } + } + } + public function transform(array|TransactionGroup $group): array { if (is_array($group)) { $first = reset($group['transactions']); return [ - 'id' => (string) $group['id'], + 'id' => (string)$group['id'], 'created_at' => $group['created_at']->toAtomString(), 'updated_at' => $group['updated_at']->toAtomString(), - 'user' => (string) $first['user_id'], - 'user_group' => (string) $first['user_group_id'], + 'user' => (string)$first['user_id'], + 'user_group' => (string)$first['user_group_id'], 'group_title' => $group['title'] ?? null, 'transactions' => $this->transformTransactions($group['transactions'] ?? []), 'links' => [ @@ -119,11 +277,11 @@ class TransactionGroupTransformer extends AbstractTransformer } return [ - 'id' => (string) $group->id, + 'id' => (string)$group->id, 'created_at' => $group->created_at->toAtomString(), 'updated_at' => $group->created_at->toAtomString(), - 'user' => (string) $group->user_id, - 'user_group' => (string) $group->user_group_id, + 'user' => (string)$group->user_id, + 'user_group' => (string)$group->user_group_id, 'group_title' => $group->title ?? null, 'transactions' => $this->transformJournals($group), 'links' => [ @@ -135,137 +293,6 @@ class TransactionGroupTransformer extends AbstractTransformer ]; } - private function transformJournals(TransactionGroup $group): array - { - $return = []; - - /** @var TransactionJournal $journal */ - foreach ($group->transactionJournals as $journal) { - $return[] = $this->transformJournal($journal); - } - - return $return; - } - - /** - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - private function transformJournal(TransactionJournal $journal): array - { - $id = $journal->id; - - /** @var null|TransactionCurrency $foreignCurrency */ - $foreignCurrency = null; - - /** @var TransactionCurrency $currency */ - $currency = $this->currencies[$this->journals[$id]['currency_id']]; - $nativeForeignAmount = null; - $amount = $this->journals[$journal->id]['amount']; - $foreignAmount = $this->journals[$journal->id]['foreign_amount']; - $meta = new NullArrayObject($this->meta[$id] ?? []); - - // has foreign amount? - if (null !== $foreignAmount) { - $foreignCurrency = $this->currencies[$this->journals[$id]['foreign_currency_id']]; - $nativeForeignAmount = $this->converter->convert($this->default, $foreignCurrency, $journal->date, $foreignAmount); - } - - $nativeAmount = $this->converter->convert($this->default, $currency, $journal->date, $amount); - - $longitude = null; - $latitude = null; - $zoomLevel = null; - if (array_key_exists('location', $this->journals[$id])) { - $latitude = (string) $this->journals[$id]['location']['latitude']; - $longitude = (string) $this->journals[$id]['location']['longitude']; - $zoomLevel = $this->journals[$id]['location']['zoom_level']; - } - - return [ - 'user' => (string) $journal->user_id, - 'user_group' => (string) $journal->user_group_id, - 'transaction_journal_id' => (string) $journal->id, - 'type' => $this->journals[$journal->id]['type'], - 'date' => $journal->date->toAtomString(), - 'order' => $journal->order, - 'amount' => $amount, - 'native_amount' => $nativeAmount, - 'foreign_amount' => $foreignAmount, - 'native_foreign_amount' => $nativeForeignAmount, - 'currency_id' => (string) $currency->id, - 'currency_code' => $currency->code, - 'currency_name' => $currency->name, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - - // converted to native currency - 'native_currency_id' => (string) $this->default->id, - 'native_currency_code' => $this->default->code, - 'native_currency_name' => $this->default->name, - 'native_currency_symbol' => $this->default->symbol, - 'native_currency_decimal_places' => $this->default->decimal_places, - - // foreign currency amount: - 'foreign_currency_id' => $foreignCurrency?->id, - 'foreign_currency_code' => $foreignCurrency?->code, - 'foreign_currency_name' => $foreignCurrency?->name, - 'foreign_currency_symbol' => $foreignCurrency?->symbol, - 'foreign_currency_decimal_places' => $foreignCurrency?->decimal_places, - - 'description' => $journal->description, - 'source_id' => (string) $this->journals[$id]['source_account_id'], - 'source_name' => $this->journals[$id]['source_account_name'], - 'source_iban' => $this->journals[$id]['source_account_iban'], - 'source_type' => $this->journals[$id]['source_account_type'], - - 'destination_id' => (string) $this->journals[$id]['destination_account_id'], - 'destination_name' => $this->journals[$id]['destination_account_name'], - 'destination_iban' => $this->journals[$id]['destination_account_iban'], - 'destination_type' => $this->journals[$id]['destination_account_type'], - - 'budget_id' => $this->journals[$id]['budget_id'], - 'budget_name' => $this->journals[$id]['budget_name'], - 'category_id' => $this->journals[$id]['category_id'], - 'category_name' => $this->journals[$id]['category_name'], - 'bill_id' => $this->journals[$id]['bill_id'], - 'bill_name' => $this->journals[$id]['bill_name'], - 'reconciled' => $this->journals[$id]['reconciled'], - 'notes' => $this->journals[$id]['notes'] ?? null, - 'tags' => $this->journals[$id]['tags'] ?? [], - 'internal_reference' => $meta['internal_reference'], - 'external_id' => $meta['external_id'], - 'original_source' => $meta['original_source'], - 'recurrence_id' => $meta['recurrence_id'], - 'recurrence_total' => $meta['recurrence_total'], - 'recurrence_count' => $meta['recurrence_count'], - 'external_url' => $meta['external_url'], - 'import_hash_v2' => $meta['import_hash_v2'], - 'sepa_cc' => $meta['sepa_cc'], - 'sepa_ct_op' => $meta['sepa_ct_op'], - 'sepa_ct_id' => $meta['sepa_ct_id'], - 'sepa_db' => $meta['sepa_db'], - 'sepa_country' => $meta['sepa_country'], - 'sepa_ep' => $meta['sepa_ep'], - 'sepa_ci' => $meta['sepa_ci'], - 'sepa_batch_id' => $meta['sepa_batch_id'], - 'interest_date' => $this->date($meta['interest_date']), - 'book_date' => $this->date($meta['book_date']), - 'process_date' => $this->date($meta['process_date']), - 'due_date' => $this->date($meta['due_date']), - 'payment_date' => $this->date($meta['payment_date']), - 'invoice_date' => $this->date($meta['invoice_date']), - - // location data - 'longitude' => $longitude, - 'latitude' => $latitude, - 'zoom_level' => $zoomLevel, - // - // 'has_attachments' => $this->hasAttachments((int) $row['transaction_journal_id']), - ]; - } - private function transformTransactions(array $transactions): array { $return = []; @@ -287,19 +314,19 @@ class TransactionGroupTransformer extends AbstractTransformer { $transaction = new NullArrayObject($transaction); $type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionType::WITHDRAWAL); - $journalId = (int) $transaction['transaction_journal_id']; + $journalId = (int)$transaction['transaction_journal_id']; $meta = new NullArrayObject($this->meta[$journalId] ?? []); /** * Convert and use amount: */ - $amount = app('steam')->positive((string) ($transaction['amount'] ?? '0')); - $currencyId = (int) $transaction['currency_id']; + $amount = app('steam')->positive((string)($transaction['amount'] ?? '0')); + $currencyId = (int)$transaction['currency_id']; $nativeAmount = $this->converter->convert($this->default, $this->currencies[$currencyId], $transaction['date'], $amount); $foreignAmount = null; $nativeForeignAmount = null; if (null !== $transaction['foreign_amount']) { - $foreignCurrencyId = (int) $transaction['foreign_currency_id']; + $foreignCurrencyId = (int)$transaction['foreign_currency_id']; $foreignAmount = app('steam')->positive($transaction['foreign_amount']); $nativeForeignAmount = $this->converter->convert($this->default, $this->currencies[$foreignCurrencyId], $transaction['date'], $foreignAmount); } @@ -309,15 +336,15 @@ class TransactionGroupTransformer extends AbstractTransformer $latitude = null; $zoomLevel = null; if (array_key_exists('location', $this->journals[$journalId])) { - $latitude = (string) $this->journals[$journalId]['location']['latitude']; - $longitude = (string) $this->journals[$journalId]['location']['longitude']; + $latitude = (string)$this->journals[$journalId]['location']['latitude']; + $longitude = (string)$this->journals[$journalId]['location']['longitude']; $zoomLevel = $this->journals[$journalId]['location']['zoom_level']; } return [ - 'user' => (string) $transaction['user_id'], - 'user_group' => (string) $transaction['user_group_id'], - 'transaction_journal_id' => (string) $transaction['transaction_journal_id'], + 'user' => (string)$transaction['user_id'], + 'user_group' => (string)$transaction['user_group_id'], + 'transaction_journal_id' => (string)$transaction['transaction_journal_id'], 'type' => strtolower($type), 'date' => $transaction['date']->toAtomString(), 'order' => $transaction['order'], @@ -325,14 +352,14 @@ class TransactionGroupTransformer extends AbstractTransformer 'native_amount' => $nativeAmount, 'foreign_amount' => $foreignAmount, 'native_foreign_amount' => $nativeForeignAmount, - 'currency_id' => (string) $transaction['currency_id'], + 'currency_id' => (string)$transaction['currency_id'], 'currency_code' => $transaction['currency_code'], 'currency_name' => $transaction['currency_name'], 'currency_symbol' => $transaction['currency_symbol'], - 'currency_decimal_places' => (int) $transaction['currency_decimal_places'], + 'currency_decimal_places' => (int)$transaction['currency_decimal_places'], // converted to native currency - 'native_currency_id' => (string) $this->default->id, + 'native_currency_id' => (string)$this->default->id, 'native_currency_code' => $this->default->code, 'native_currency_name' => $this->default->name, 'native_currency_symbol' => $this->default->symbol, @@ -347,11 +374,11 @@ class TransactionGroupTransformer extends AbstractTransformer // foreign converted to native: 'description' => $transaction['description'], - 'source_id' => (string) $transaction['source_account_id'], + 'source_id' => (string)$transaction['source_account_id'], 'source_name' => $transaction['source_account_name'], 'source_iban' => $transaction['source_account_iban'], 'source_type' => $transaction['source_account_type'], - 'destination_id' => (string) $transaction['destination_account_id'], + 'destination_id' => (string)$transaction['destination_account_id'], 'destination_name' => $transaction['destination_account_name'], 'destination_iban' => $transaction['destination_account_iban'], 'destination_type' => $transaction['destination_account_type'], @@ -415,7 +442,7 @@ class TransactionGroupTransformer extends AbstractTransformer return $default; } if (null !== $array[$key]) { - return (string) $array[$key]; + return (string)$array[$key]; } if (null !== $default) { @@ -460,161 +487,134 @@ class TransactionGroupTransformer extends AbstractTransformer return $res; } - private function collectForArray(array $object): void + private function transformJournals(TransactionGroup $group): array { - foreach ($object['sums'] as $sum) { - $this->currencies[(int) $sum['currency_id']] ??= TransactionCurrency::find($sum['currency_id']); - } + $return = []; - /** @var array $transaction */ - foreach ($object['transactions'] as $transaction) { - $this->journals[(int) $transaction['transaction_journal_id']] = []; - } - } - - private function collectAllMetaData(): void - { - $meta = TransactionJournalMeta::whereIn('transaction_journal_id', array_keys($this->journals))->get(); - - /** @var TransactionJournalMeta $entry */ - foreach ($meta as $entry) { - $id = $entry->transaction_journal_id; - $this->journals[$id]['meta'] ??= []; - $this->journals[$id]['meta'][$entry->name] = $entry->data; - } - } - - private function collectAllNotes(): void - { - // grab all notes for all journals: - $notes = Note::whereNoteableType(TransactionJournal::class)->whereIn('noteable_id', array_keys($this->journals))->get(); - - /** @var Note $note */ - foreach ($notes as $note) { - $id = $note->noteable_id; - $this->journals[$id]['notes'] = $note->text; - } - } - - private function collectAllLocations(): void - { - // grab all locations for all journals: - $locations = Location::whereLocatableType(TransactionJournal::class)->whereIn('locatable_id', array_keys($this->journals))->get(); - - /** @var Location $location */ - foreach ($locations as $location) { - $id = $location->locatable_id; - $this->journals[$id]['location'] = [ - 'latitude' => $location->latitude, - 'longitude' => $location->longitude, - 'zoom_level' => $location->zoom_level, - ]; - } - } - - private function collectAllTags(): void - { - // grab all tags for all journals: - $tags = DB::table('tag_transaction_journal') - ->leftJoin('tags', 'tags.id', 'tag_transaction_journal.tag_id') - ->whereIn('tag_transaction_journal.transaction_journal_id', array_keys($this->journals)) - ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']) - ; - - /** @var \stdClass $tag */ - foreach ($tags as $tag) { - $id = (int) $tag->transaction_journal_id; - $this->journals[$id]['tags'][] = $tag->tag; - } - } - - private function collectForObject(TransactionGroup $object): void - { - foreach ($object->transactionJournals as $journal) { - $this->journals[$journal->id] = []; - $this->objects[] = $journal; - } - } - - private function collectAllCurrencies(): void - { /** @var TransactionJournal $journal */ - foreach ($this->objects as $journal) { - $id = $journal->id; - $this->journals[$id]['reconciled'] = false; - $this->journals[$id]['foreign_amount'] = null; - $this->journals[$id]['foreign_currency_id'] = null; - $this->journals[$id]['amount'] = null; - $this->journals[$id]['currency_id'] = null; - $this->journals[$id]['type'] = $journal->transactionType->type; - $this->journals[$id]['budget_id'] = null; - $this->journals[$id]['budget_name'] = null; - $this->journals[$id]['category_id'] = null; - $this->journals[$id]['category_name'] = null; - $this->journals[$id]['bill_id'] = null; - $this->journals[$id]['bill_name'] = null; - - // collect budget: - /** @var null|Budget $budget */ - $budget = $journal->budgets()->first(); - if (null !== $budget) { - $this->journals[$id]['budget_id'] = (string) $budget->id; - $this->journals[$id]['budget_name'] = $budget->name; - } - - // collect category: - /** @var null|Category $category */ - $category = $journal->categories()->first(); - if (null !== $category) { - $this->journals[$id]['category_id'] = (string) $category->id; - $this->journals[$id]['category_name'] = $category->name; - } - - // collect bill: - if (null !== $journal->bill_id) { - $bill = $journal->bill; - $this->journals[$id]['bill_id'] = (string) $bill->id; - $this->journals[$id]['bill_name'] = $bill->name; - } - - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - if (-1 === bccomp($transaction->amount, '0')) { - // only collect source account info - $account = $transaction->account; - $this->accountTypes[$account->account_type_id] ??= $account->accountType->type; - $this->journals[$id]['source_account_name'] = $account->name; - $this->journals[$id]['source_account_iban'] = $account->iban; - $this->journals[$id]['source_account_type'] = $this->accountTypes[$account->account_type_id]; - $this->journals[$id]['source_account_id'] = $transaction->account_id; - $this->journals[$id]['reconciled'] = $transaction->reconciled; - - continue; - } - - // add account - $account = $transaction->account; - $this->accountTypes[$account->account_type_id] ??= $account->accountType->type; - $this->journals[$id]['destination_account_name'] = $account->name; - $this->journals[$id]['destination_account_iban'] = $account->iban; - $this->journals[$id]['destination_account_type'] = $this->accountTypes[$account->account_type_id]; - $this->journals[$id]['destination_account_id'] = $transaction->account_id; - - // find and set currency - $currencyId = $transaction->transaction_currency_id; - $this->currencies[$currencyId] ??= $transaction->transactionCurrency; - $this->journals[$id]['currency_id'] = $currencyId; - $this->journals[$id]['amount'] = $transaction->amount; - // find and set foreign currency - if (null !== $transaction->foreign_currency_id) { - $foreignCurrencyId = $transaction->foreign_currency_id; - $this->currencies[$foreignCurrencyId] ??= $transaction->foreignCurrency; - $this->journals[$id]['foreign_currency_id'] = $foreignCurrencyId; - $this->journals[$id]['foreign_amount'] = $transaction->foreign_amount; - } - - // find and set destination account info. - } + foreach ($group->transactionJournals as $journal) { + $return[] = $this->transformJournal($journal); } + + return $return; + } + + /** + * @throws FireflyException + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function transformJournal(TransactionJournal $journal): array + { + $id = $journal->id; + + /** @var null|TransactionCurrency $foreignCurrency */ + $foreignCurrency = null; + + /** @var TransactionCurrency $currency */ + $currency = $this->currencies[$this->journals[$id]['currency_id']]; + $nativeForeignAmount = null; + $amount = $this->journals[$journal->id]['amount']; + $foreignAmount = $this->journals[$journal->id]['foreign_amount']; + $meta = new NullArrayObject($this->meta[$id] ?? []); + + // has foreign amount? + if (null !== $foreignAmount) { + $foreignCurrency = $this->currencies[$this->journals[$id]['foreign_currency_id']]; + $nativeForeignAmount = $this->converter->convert($this->default, $foreignCurrency, $journal->date, $foreignAmount); + } + + $nativeAmount = $this->converter->convert($this->default, $currency, $journal->date, $amount); + + $longitude = null; + $latitude = null; + $zoomLevel = null; + if (array_key_exists('location', $this->journals[$id])) { + $latitude = (string)$this->journals[$id]['location']['latitude']; + $longitude = (string)$this->journals[$id]['location']['longitude']; + $zoomLevel = $this->journals[$id]['location']['zoom_level']; + } + + return [ + 'user' => (string)$journal->user_id, + 'user_group' => (string)$journal->user_group_id, + 'transaction_journal_id' => (string)$journal->id, + 'type' => $this->journals[$journal->id]['type'], + 'date' => $journal->date->toAtomString(), + 'order' => $journal->order, + 'amount' => $amount, + 'native_amount' => $nativeAmount, + 'foreign_amount' => $foreignAmount, + 'native_foreign_amount' => $nativeForeignAmount, + 'currency_id' => (string)$currency->id, + 'currency_code' => $currency->code, + 'currency_name' => $currency->name, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + + // converted to native currency + 'native_currency_id' => (string)$this->default->id, + 'native_currency_code' => $this->default->code, + 'native_currency_name' => $this->default->name, + 'native_currency_symbol' => $this->default->symbol, + 'native_currency_decimal_places' => $this->default->decimal_places, + + // foreign currency amount: + 'foreign_currency_id' => $foreignCurrency?->id, + 'foreign_currency_code' => $foreignCurrency?->code, + 'foreign_currency_name' => $foreignCurrency?->name, + 'foreign_currency_symbol' => $foreignCurrency?->symbol, + 'foreign_currency_decimal_places' => $foreignCurrency?->decimal_places, + + 'description' => $journal->description, + 'source_id' => (string)$this->journals[$id]['source_account_id'], + 'source_name' => $this->journals[$id]['source_account_name'], + 'source_iban' => $this->journals[$id]['source_account_iban'], + 'source_type' => $this->journals[$id]['source_account_type'], + + 'destination_id' => (string)$this->journals[$id]['destination_account_id'], + 'destination_name' => $this->journals[$id]['destination_account_name'], + 'destination_iban' => $this->journals[$id]['destination_account_iban'], + 'destination_type' => $this->journals[$id]['destination_account_type'], + + 'budget_id' => $this->journals[$id]['budget_id'], + 'budget_name' => $this->journals[$id]['budget_name'], + 'category_id' => $this->journals[$id]['category_id'], + 'category_name' => $this->journals[$id]['category_name'], + 'bill_id' => $this->journals[$id]['bill_id'], + 'bill_name' => $this->journals[$id]['bill_name'], + 'reconciled' => $this->journals[$id]['reconciled'], + 'notes' => $this->journals[$id]['notes'] ?? null, + 'tags' => $this->journals[$id]['tags'] ?? [], + 'internal_reference' => $meta['internal_reference'], + 'external_id' => $meta['external_id'], + 'original_source' => $meta['original_source'], + 'recurrence_id' => $meta['recurrence_id'], + 'recurrence_total' => $meta['recurrence_total'], + 'recurrence_count' => $meta['recurrence_count'], + 'external_url' => $meta['external_url'], + 'import_hash_v2' => $meta['import_hash_v2'], + 'sepa_cc' => $meta['sepa_cc'], + 'sepa_ct_op' => $meta['sepa_ct_op'], + 'sepa_ct_id' => $meta['sepa_ct_id'], + 'sepa_db' => $meta['sepa_db'], + 'sepa_country' => $meta['sepa_country'], + 'sepa_ep' => $meta['sepa_ep'], + 'sepa_ci' => $meta['sepa_ci'], + 'sepa_batch_id' => $meta['sepa_batch_id'], + 'interest_date' => $this->date($meta['interest_date']), + 'book_date' => $this->date($meta['book_date']), + 'process_date' => $this->date($meta['process_date']), + 'due_date' => $this->date($meta['due_date']), + 'payment_date' => $this->date($meta['payment_date']), + 'invoice_date' => $this->date($meta['invoice_date']), + + // location data + 'longitude' => $longitude, + 'latitude' => $latitude, + 'zoom_level' => $zoomLevel, + // + // 'has_attachments' => $this->hasAttachments((int) $row['transaction_journal_id']), + ]; } } diff --git a/app/User.php b/app/User.php index 04a0c0df7d..1218e7deaf 100644 --- a/app/User.php +++ b/app/User.php @@ -364,11 +364,64 @@ class User extends Authenticatable } /** - * Does the user have role X in group Y? + * Does the user have role X, Y or Z in group A? */ - public function hasSpecificRoleInGroup(UserGroup $userGroup, UserRoleEnum $role): bool + private function hasAnyRoleInGroup(UserGroup $userGroup, array $roles): bool { - return $this->hasAnyRoleInGroup($userGroup, [$role]); + app('log')->debug(sprintf('in hasAnyRoleInGroup(%s)', implode(', ', $roles))); + + /** @var Collection $dbRoles */ + $dbRoles = UserRole::whereIn('title', $roles)->get(); + if (0 === $dbRoles->count()) { + app('log')->error(sprintf('Could not find role(s): %s. Probably migration mishap.', implode(', ', $roles))); + + return false; + } + $dbRolesIds = $dbRoles->pluck('id')->toArray(); + $dbRolesTitles = $dbRoles->pluck('title')->toArray(); + + /** @var Collection $groupMemberships */ + $groupMemberships = $this->groupMemberships() + ->whereIn('user_role_id', $dbRolesIds) + ->where('user_group_id', $userGroup->id)->get() + ; + if (0 === $groupMemberships->count()) { + app('log')->error(sprintf( + 'User #%d "%s" does not have roles %s in user group #%d "%s"', + $this->id, + $this->email, + implode(', ', $roles), + $userGroup->id, + $userGroup->title + )); + + return false; + } + foreach ($groupMemberships as $membership) { + app('log')->debug(sprintf( + 'User #%d "%s" has role "%s" in user group #%d "%s"', + $this->id, + $this->email, + $membership->userRole->title, + $userGroup->id, + $userGroup->title + )); + if (in_array($membership->userRole->title, $dbRolesTitles, true)) { + app('log')->debug(sprintf('Return true, found role "%s"', $membership->userRole->title)); + + return true; + } + } + app('log')->error(sprintf( + 'User #%d "%s" does not have roles %s in user group #%d "%s"', + $this->id, + $this->email, + implode(', ', $roles), + $userGroup->id, + $userGroup->title + )); + + return false; } public function groupMemberships(): HasMany @@ -376,6 +429,14 @@ class User extends Authenticatable return $this->hasMany(GroupMembership::class)->with(['userGroup', 'userRole']); } + /** + * Does the user have role X in group Y? + */ + public function hasSpecificRoleInGroup(UserGroup $userGroup, UserRoleEnum $role): bool + { + return $this->hasAnyRoleInGroup($userGroup, [$role]); + } + /** * Link to object groups. */ @@ -495,6 +556,8 @@ class User extends Authenticatable return $this->hasMany(RuleGroup::class); } + // start LDAP related code + /** * Link to rules. */ @@ -503,8 +566,6 @@ class User extends Authenticatable return $this->hasMany(Rule::class); } - // start LDAP related code - /** * Send the password reset notification. * @@ -585,65 +646,4 @@ class User extends Authenticatable { return $this->hasMany(Webhook::class); } - - /** - * Does the user have role X, Y or Z in group A? - */ - private function hasAnyRoleInGroup(UserGroup $userGroup, array $roles): bool - { - app('log')->debug(sprintf('in hasAnyRoleInGroup(%s)', implode(', ', $roles))); - - /** @var Collection $dbRoles */ - $dbRoles = UserRole::whereIn('title', $roles)->get(); - if (0 === $dbRoles->count()) { - app('log')->error(sprintf('Could not find role(s): %s. Probably migration mishap.', implode(', ', $roles))); - - return false; - } - $dbRolesIds = $dbRoles->pluck('id')->toArray(); - $dbRolesTitles = $dbRoles->pluck('title')->toArray(); - - /** @var Collection $groupMemberships */ - $groupMemberships = $this->groupMemberships() - ->whereIn('user_role_id', $dbRolesIds) - ->where('user_group_id', $userGroup->id)->get() - ; - if (0 === $groupMemberships->count()) { - app('log')->error(sprintf( - 'User #%d "%s" does not have roles %s in user group #%d "%s"', - $this->id, - $this->email, - implode(', ', $roles), - $userGroup->id, - $userGroup->title - )); - - return false; - } - foreach ($groupMemberships as $membership) { - app('log')->debug(sprintf( - 'User #%d "%s" has role "%s" in user group #%d "%s"', - $this->id, - $this->email, - $membership->userRole->title, - $userGroup->id, - $userGroup->title - )); - if (in_array($membership->userRole->title, $dbRolesTitles, true)) { - app('log')->debug(sprintf('Return true, found role "%s"', $membership->userRole->title)); - - return true; - } - } - app('log')->error(sprintf( - 'User #%d "%s" does not have roles %s in user group #%d "%s"', - $this->id, - $this->email, - implode(', ', $roles), - $userGroup->id, - $userGroup->title - )); - - return false; - } } diff --git a/app/Validation/Account/DepositValidation.php b/app/Validation/Account/DepositValidation.php index aaae93470b..ea75e241ae 100644 --- a/app/Validation/Account/DepositValidation.php +++ b/app/Validation/Account/DepositValidation.php @@ -45,7 +45,7 @@ trait DepositValidation if (null === $accountId && null === $accountName && null === $accountIban && false === $this->canCreateTypes($validTypes)) { // if both values are NULL we return false, // because the destination of a deposit can't be created. - $this->destError = (string) trans('validation.deposit_dest_need_data'); + $this->destError = (string)trans('validation.deposit_dest_need_data'); app('log')->error('Both values are NULL, cant create deposit destination.'); $result = false; } @@ -60,7 +60,7 @@ trait DepositValidation $search = $this->findExistingAccount($validTypes, $array); if (null === $search) { app('log')->debug('findExistingAccount() returned NULL, so the result is false.'); - $this->destError = (string) trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + $this->destError = (string)trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); $result = false; } if (null !== $search) { @@ -106,7 +106,7 @@ trait DepositValidation // if both values are NULL return false, // because the source of a deposit can't be created. // (this never happens). - $this->sourceError = (string) trans('validation.deposit_source_need_data'); + $this->sourceError = (string)trans('validation.deposit_source_need_data'); $result = false; } @@ -115,7 +115,7 @@ trait DepositValidation app('log')->debug('Check if there is not already another account with this IBAN'); $existing = $this->findExistingAccount($validTypes, ['iban' => $accountIban], true); if (null !== $existing) { - $this->sourceError = (string) trans('validation.deposit_src_iban_exists'); + $this->sourceError = (string)trans('validation.deposit_src_iban_exists'); return false; } diff --git a/app/Validation/AutoBudget/ValidatesAutoBudgetRequest.php b/app/Validation/AutoBudget/ValidatesAutoBudgetRequest.php index 0def803ee1..2c994fb960 100644 --- a/app/Validation/AutoBudget/ValidatesAutoBudgetRequest.php +++ b/app/Validation/AutoBudget/ValidatesAutoBudgetRequest.php @@ -41,10 +41,10 @@ trait ValidatesAutoBudgetRequest /** @var null|float|int|string $amount */ $amount = array_key_exists('auto_budget_amount', $data) ? $data['auto_budget_amount'] : null; $period = array_key_exists('auto_budget_period', $data) ? $data['auto_budget_period'] : null; - $currencyId = array_key_exists('auto_budget_currency_id', $data) ? (int) $data['auto_budget_currency_id'] : null; + $currencyId = array_key_exists('auto_budget_currency_id', $data) ? (int)$data['auto_budget_currency_id'] : null; $currencyCode = array_key_exists('auto_budget_currency_code', $data) ? $data['auto_budget_currency_code'] : null; if (is_numeric($type)) { - $type = (int) $type; + $type = (int)$type; } if ('' === $type || 0 === $type) { return; @@ -56,23 +56,23 @@ trait ValidatesAutoBudgetRequest } // basic float check: if (!is_numeric($amount)) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.amount_required_for_auto_budget')); + $validator->errors()->add('auto_budget_amount', (string)trans('validation.amount_required_for_auto_budget')); return; } - if (1 !== bccomp((string) $amount, '0')) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.auto_budget_amount_positive')); + if (1 !== bccomp((string)$amount, '0')) { + $validator->errors()->add('auto_budget_amount', (string)trans('validation.auto_budget_amount_positive')); } if ('' === $period) { - $validator->errors()->add('auto_budget_period', (string) trans('validation.auto_budget_period_mandatory')); + $validator->errors()->add('auto_budget_period', (string)trans('validation.auto_budget_period_mandatory')); } if (null !== $currencyId && null !== $currencyCode && '' === $currencyCode && 0 === $currencyId) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.require_currency_info')); + $validator->errors()->add('auto_budget_amount', (string)trans('validation.require_currency_info')); } // too big amount - if ((int) $amount > 268435456) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.amount_required_for_auto_budget')); + if ((int)$amount > 268435456) { + $validator->errors()->add('auto_budget_amount', (string)trans('validation.amount_required_for_auto_budget')); } } } diff --git a/app/Validation/CurrencyValidation.php b/app/Validation/CurrencyValidation.php index 52bad8ff61..c792b0a866 100644 --- a/app/Validation/CurrencyValidation.php +++ b/app/Validation/CurrencyValidation.php @@ -57,7 +57,7 @@ trait CurrencyValidation continue; } - $foreignAmount = (string) ($transaction['foreign_amount'] ?? ''); + $foreignAmount = (string)($transaction['foreign_amount'] ?? ''); $foreignId = $transaction['foreign_currency_id'] ?? null; $foreignCode = $transaction['foreign_currency_code'] ?? null; if ('' === $foreignAmount) { @@ -66,9 +66,9 @@ trait CurrencyValidation (array_key_exists('foreign_currency_id', $transaction) || array_key_exists('foreign_currency_code', $transaction)) && (null !== $foreignId || null !== $foreignCode) ) { - $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string) trans('validation.require_currency_amount')); - $validator->errors()->add('transactions.'.$index.'.foreign_currency_id', (string) trans('validation.require_currency_amount')); - $validator->errors()->add('transactions.'.$index.'.foreign_currency_code', (string) trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string)trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_currency_id', (string)trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_currency_code', (string)trans('validation.require_currency_amount')); } continue; @@ -79,14 +79,14 @@ trait CurrencyValidation Log::debug('validateForeignCurrencyInformation: array contains foreign amount info.'); if (!array_key_exists('foreign_currency_id', $transaction) && !array_key_exists('foreign_currency_code', $transaction)) { Log::debug('validateForeignCurrencyInformation: array contains NO foreign currency info.'); - $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string) trans('validation.require_currency_info')); + $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string)trans('validation.require_currency_info')); } } - if (0 === $compare && ('' !== (string) $foreignId || '' !== (string) $foreignCode)) { + if (0 === $compare && ('' !== (string)$foreignId || '' !== (string)$foreignCode)) { Log::debug('validateForeignCurrencyInformation: array contains foreign currency info, but zero amount.'); - $validator->errors()->add('transactions.'.$index.'.foreign_currency_id', (string) trans('validation.require_currency_amount')); - $validator->errors()->add('transactions.'.$index.'.foreign_currency_code', (string) trans('validation.require_currency_amount')); - $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string) trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_currency_id', (string)trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_currency_code', (string)trans('validation.require_currency_amount')); + $validator->errors()->add('transactions.'.$index.'.foreign_amount', (string)trans('validation.require_currency_amount')); } } } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 66e89d4f7b..65b27f3bb9 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -484,6 +484,106 @@ class FireflyValidator extends Validator return $this->validateByAccountName($value); } + private function validateAccountAnonymously(): bool + { + if (!array_key_exists('user_id', $this->data)) { + return false; + } + + /** @var User $user */ + $user = User::find($this->data['user_id']); + $type = AccountType::find($this->data['account_type_id'])->first(); + $value = $this->data['name']; + + /** @var null|Account $result */ + $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); + + return null === $result; + } + + private function validateByAccountTypeString(string $value, array $parameters, string $type): bool + { + /** @var null|array $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 null|Account $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 + */ + private function validateByAccountTypeId($value, $parameters): bool + { + $type = AccountType::find($this->data['account_type_id'])->first(); + $ignore = (int)($parameters[0] ?? 0.0); + + /** @var null|Account $result */ + $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) + ->where('name', $value) + ->first() + ; + + return null === $result; + } + + /** + * @param mixed $value + */ + 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 + */ + 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; + } + + private function validateByAccountName(string $value): bool + { + return 0 === auth()->user()->accounts()->where('name', $value)->count(); + } + /** * @param mixed $attribute * @param mixed $value @@ -724,110 +824,9 @@ class FireflyValidator extends Validator ->where('trigger', $trigger) ->where('response', $response) ->where('delivery', $delivery) - ->where('url', $url)->count() - ; + ->where('url', $url)->count(); } return false; } - - private function validateAccountAnonymously(): bool - { - if (!array_key_exists('user_id', $this->data)) { - return false; - } - - /** @var User $user */ - $user = User::find($this->data['user_id']); - $type = AccountType::find($this->data['account_type_id'])->first(); - $value = $this->data['name']; - - /** @var null|Account $result */ - $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); - - return null === $result; - } - - private function validateByAccountTypeString(string $value, array $parameters, string $type): bool - { - /** @var null|array $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 null|Account $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 - */ - private function validateByAccountTypeId($value, $parameters): bool - { - $type = AccountType::find($this->data['account_type_id'])->first(); - $ignore = (int)($parameters[0] ?? 0.0); - - /** @var null|Account $result */ - $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) - ->where('name', $value) - ->first() - ; - - return null === $result; - } - - /** - * @param mixed $value - */ - 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 - */ - 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; - } - - private function validateByAccountName(string $value): bool - { - return 0 === auth()->user()->accounts()->where('name', $value)->count(); - } } diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php index b4c3bdc286..f2ae1aa188 100644 --- a/app/Validation/RecurrenceValidation.php +++ b/app/Validation/RecurrenceValidation.php @@ -88,7 +88,7 @@ trait RecurrenceValidation continue; } // validate source account. - $sourceId = array_key_exists('source_id', $transaction) ? (int) $transaction['source_id'] : null; + $sourceId = array_key_exists('source_id', $transaction) ? (int)$transaction['source_id'] : null; $sourceName = $transaction['source_name'] ?? null; $validSource = $accountValidator->validateSource(['id' => $sourceId, 'name' => $sourceName]); @@ -100,7 +100,7 @@ trait RecurrenceValidation return; } // validate destination account - $destinationId = array_key_exists('destination_id', $transaction) ? (int) $transaction['destination_id'] : null; + $destinationId = array_key_exists('destination_id', $transaction) ? (int)$transaction['destination_id'] : null; $destinationName = $transaction['destination_name'] ?? null; $validDestination = $accountValidator->validateDestination(['id' => $destinationId, 'name' => $destinationName]); // do something with result: @@ -122,7 +122,7 @@ trait RecurrenceValidation $repetitions = $data['repetitions'] ?? []; // need at least one transaction if (!is_countable($repetitions) || 0 === count($repetitions)) { - $validator->errors()->add('repetitions', (string) trans('validation.at_least_one_repetition')); + $validator->errors()->add('repetitions', (string)trans('validation.at_least_one_repetition')); } } @@ -138,7 +138,7 @@ trait RecurrenceValidation } // need at least one transaction if (0 === count($repetitions)) { - $validator->errors()->add('repetitions', (string) trans('validation.at_least_one_repetition')); + $validator->errors()->add('repetitions', (string)trans('validation.at_least_one_repetition')); } } @@ -153,15 +153,15 @@ trait RecurrenceValidation $repeatUntil = $data['repeat_until'] ?? null; if (null !== $repetitions && null !== $repeatUntil) { // expect a date OR count: - $validator->errors()->add('repeat_until', (string) trans('validation.require_repeat_until')); - $validator->errors()->add('nr_of_repetitions', (string) trans('validation.require_repeat_until')); + $validator->errors()->add('repeat_until', (string)trans('validation.require_repeat_until')); + $validator->errors()->add('nr_of_repetitions', (string)trans('validation.require_repeat_until')); } } public function validateRecurringConfig(Validator $validator): void { $data = $validator->getData(); - $reps = array_key_exists('nr_of_repetitions', $data) ? (int) $data['nr_of_repetitions'] : null; + $reps = array_key_exists('nr_of_repetitions', $data) ? (int)$data['nr_of_repetitions'] : null; $repeatUntil = array_key_exists('repeat_until', $data) ? new Carbon($data['repeat_until']) : null; if (null === $reps && null === $repeatUntil) { @@ -181,7 +181,7 @@ trait RecurrenceValidation $data = $validator->getData(); $repetitions = $data['repetitions'] ?? []; if (!is_array($repetitions)) { - $validator->errors()->add(sprintf('repetitions.%d.type', 0), (string) trans('validation.valid_recurrence_rep_type')); + $validator->errors()->add(sprintf('repetitions.%d.type', 0), (string)trans('validation.valid_recurrence_rep_type')); return; } @@ -200,32 +200,32 @@ trait RecurrenceValidation switch ($repetition['type'] ?? 'empty') { default: - $validator->errors()->add(sprintf('repetitions.%d.type', $index), (string) trans('validation.valid_recurrence_rep_type')); + $validator->errors()->add(sprintf('repetitions.%d.type', $index), (string)trans('validation.valid_recurrence_rep_type')); return; case 'daily': - $this->validateDaily($validator, $index, (string) $repetition['moment']); + $this->validateDaily($validator, $index, (string)$repetition['moment']); break; case 'monthly': - $this->validateMonthly($validator, $index, (int) $repetition['moment']); + $this->validateMonthly($validator, $index, (int)$repetition['moment']); break; case 'ndom': - $this->validateNdom($validator, $index, (string) $repetition['moment']); + $this->validateNdom($validator, $index, (string)$repetition['moment']); break; case 'weekly': - $this->validateWeekly($validator, $index, (int) $repetition['moment']); + $this->validateWeekly($validator, $index, (int)$repetition['moment']); break; case 'yearly': - $this->validateYearly($validator, $index, (string) $repetition['moment']); + $this->validateYearly($validator, $index, (string)$repetition['moment']); break; } @@ -238,7 +238,7 @@ trait RecurrenceValidation protected function validateDaily(Validator $validator, int $index, string $moment): void { if ('' !== $moment) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } @@ -248,7 +248,7 @@ trait RecurrenceValidation protected function validateMonthly(Validator $validator, int $index, int $dayOfMonth): void { if ($dayOfMonth < 1 || $dayOfMonth > 31) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } @@ -260,19 +260,19 @@ trait RecurrenceValidation { $parameters = explode(',', $moment); if (2 !== count($parameters)) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); return; } - $nthDay = (int) ($parameters[0] ?? 0.0); - $dayOfWeek = (int) ($parameters[1] ?? 0.0); + $nthDay = (int)($parameters[0] ?? 0.0); + $dayOfWeek = (int)($parameters[1] ?? 0.0); if ($nthDay < 1 || $nthDay > 5) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); return; } if ($dayOfWeek < 1 || $dayOfWeek > 7) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } @@ -282,7 +282,7 @@ trait RecurrenceValidation protected function validateWeekly(Validator $validator, int $index, int $dayOfWeek): void { if ($dayOfWeek < 1 || $dayOfWeek > 7) { - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } @@ -295,7 +295,7 @@ trait RecurrenceValidation Carbon::createFromFormat('Y-m-d', $moment); } catch (\InvalidArgumentException $e) { // @phpstan-ignore-line app('log')->debug(sprintf('Invalid argument for Carbon: %s', $e->getMessage())); - $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string) trans('validation.valid_recurrence_rep_moment')); + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } @@ -310,7 +310,7 @@ trait RecurrenceValidation if (0 === $submittedTrCount) { app('log')->warning('[b] User submitted no transactions.'); - $validator->errors()->add('transactions', (string) trans('validation.at_least_one_transaction')); + $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); return; } @@ -323,16 +323,16 @@ trait RecurrenceValidation return; // home safe! } $id = $first['id']; - if ('' === (string) $id) { + if ('' === (string)$id) { app('log')->debug('Single count and empty ID, done.'); return; // home safe! } - $integer = (int) $id; + $integer = (int)$id; $secondCount = $recurrence->recurrenceTransactions()->where('recurrences_transactions.id', $integer)->count(); app('log')->debug(sprintf('Result of ID count: %d', $secondCount)); if (0 === $secondCount) { - $validator->errors()->add('transactions.0.id', (string) trans('validation.id_does_not_match', ['id' => $integer])); + $validator->errors()->add('transactions.0.id', (string)trans('validation.id_does_not_match', ['id' => $integer])); } app('log')->debug('Single ID validation done.'); @@ -363,19 +363,19 @@ trait RecurrenceValidation app('log')->debug(sprintf('Now at %d/%d', $index + 1, $submittedTrCount)); if (!is_array($transaction)) { app('log')->warning('Not an array. Give error.'); - $validator->errors()->add(sprintf('transactions.%d.id', $index), (string) trans('validation.at_least_one_transaction')); + $validator->errors()->add(sprintf('transactions.%d.id', $index), (string)trans('validation.at_least_one_transaction')); return; } if (!array_key_exists('id', $transaction) && $idsMandatory) { app('log')->warning('ID is mandatory but array has no ID.'); - $validator->errors()->add(sprintf('transactions.%d.id', $index), (string) trans('validation.need_id_to_match')); + $validator->errors()->add(sprintf('transactions.%d.id', $index), (string)trans('validation.need_id_to_match')); return; } if (array_key_exists('id', $transaction)) { // don't matter if $idsMandatory app('log')->debug('Array has ID.'); - $idCount = $recurrence->recurrenceTransactions()->where('recurrences_transactions.id', (int) $transaction['id'])->count(); + $idCount = $recurrence->recurrenceTransactions()->where('recurrences_transactions.id', (int)$transaction['id'])->count(); if (0 === $idCount) { app('log')->debug('ID does not exist or no match. Count another unmatched ID.'); ++$unmatchedIds; @@ -391,7 +391,7 @@ trait RecurrenceValidation app('log')->debug(sprintf('Submitted: %d. Original: %d. User can submit %d unmatched transactions.', $submittedTrCount, $originalTrCount, $maxUnmatched)); if ($unmatchedIds > $maxUnmatched) { app('log')->warning(sprintf('Too many unmatched transactions (%d).', $unmatchedIds)); - $validator->errors()->add('transactions.0.id', (string) trans('validation.too_many_unmatched')); + $validator->errors()->add('transactions.0.id', (string)trans('validation.too_many_unmatched')); return; } diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index e24ac3a995..63a2adace3 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -71,136 +71,6 @@ trait TransactionValidation } } - /** - * Validates the given account information. Switches on given transaction type. - * - * @throws FireflyException - */ - public function validateAccountInformationUpdate(Validator $validator, TransactionGroup $transactionGroup): void - { - app('log')->debug('Now in validateAccountInformationUpdate()'); - if ($validator->errors()->count() > 0) { - app('log')->debug('Validator already has errors, so return.'); - - return; - } - - $transactions = $this->getTransactionsArray($validator); - - /** - * @var null|int $index - * @var array $transaction - */ - foreach ($transactions as $index => $transaction) { - if (!is_int($index)) { - throw new FireflyException('Invalid data submitted: transaction is not array.'); - } - $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); - } - } - - /** - * Adds an error to the validator when there are no transactions in the array of data. - */ - public function validateOneRecurrenceTransaction(Validator $validator): void - { - app('log')->debug('Now in validateOneRecurrenceTransaction()'); - $transactions = $this->getTransactionsArray($validator); - - // need at least one transaction - if (0 === count($transactions)) { - $validator->errors()->add('transactions', (string) trans('validation.at_least_one_transaction')); - } - } - - /** - * Adds an error to the validator when there are no transactions in the array of data. - */ - public function validateOneTransaction(Validator $validator): void - { - app('log')->debug('Now in validateOneTransaction'); - if ($validator->errors()->count() > 0) { - app('log')->debug('Validator already has errors, so return.'); - - return; - } - $transactions = $this->getTransactionsArray($validator); - // need at least one transaction - if (0 === count($transactions)) { - $validator->errors()->add('transactions.0.description', (string) trans('validation.at_least_one_transaction')); - app('log')->debug('Added error: at_least_one_transaction.'); - - return; - } - app('log')->debug('Added NO errors.'); - } - - public function validateTransactionArray(Validator $validator): void - { - if ($validator->errors()->count() > 0) { - return; - } - $transactions = $this->getTransactionsArray($validator); - foreach (array_keys($transactions) as $key) { - if (!is_int($key)) { - $validator->errors()->add('transactions.0.description', (string) trans('validation.at_least_one_transaction')); - app('log')->debug('Added error: at_least_one_transaction.'); - - return; - } - } - } - - /** - * All types of splits must be equal. - */ - public function validateTransactionTypes(Validator $validator): void - { - if ($validator->errors()->count() > 0) { - return; - } - app('log')->debug('Now in validateTransactionTypes()'); - $transactions = $this->getTransactionsArray($validator); - - $types = []; - foreach ($transactions as $transaction) { - $types[] = $transaction['type'] ?? 'invalid'; - } - $unique = array_unique($types); - if (count($unique) > 1) { - $validator->errors()->add('transactions.0.type', (string) trans('validation.transaction_types_equal')); - - return; - } - $first = $unique[0] ?? 'invalid'; - if ('invalid' === $first) { - $validator->errors()->add('transactions.0.type', (string) trans('validation.invalid_transaction_type')); - } - } - - /** - * All types of splits must be equal. - */ - public function validateTransactionTypesForUpdate(Validator $validator): void - { - app('log')->debug('Now in validateTransactionTypesForUpdate()'); - $transactions = $this->getTransactionsArray($validator); - $types = []; - foreach ($transactions as $transaction) { - $originalType = $this->getOriginalType((int) ($transaction['transaction_journal_id'] ?? 0)); - // if type is not set, fall back to the type of the journal, if one is given. - $types[] = $transaction['type'] ?? $originalType; - } - $unique = array_unique($types); - if (count($unique) > 1) { - app('log')->warning('Add error for mismatch transaction types.'); - $validator->errors()->add('transactions.0.type', (string) trans('validation.transaction_types_equal')); - - return; - } - app('log')->debug('No errors in validateTransactionTypesForUpdate()'); - } - protected function getTransactionsArray(Validator $validator): array { app('log')->debug('Now in getTransactionsArray'); @@ -238,10 +108,10 @@ trait TransactionValidation $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; + $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, @@ -258,10 +128,10 @@ trait TransactionValidation 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; + $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, @@ -308,92 +178,6 @@ trait TransactionValidation } } - protected function validateSingleUpdate(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void - { - app('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)) { - app('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 ID/name/iban/number are valid - if ( - array_key_exists('source_id', $transaction) - || array_key_exists('source_name', $transaction) - || array_key_exists('source_iban', $transaction) - || array_key_exists('source_number', $transaction) - ) { - app('log')->debug('Will try to validate source account information.'); - $sourceId = (int) ($transaction['source_id'] ?? 0); - $sourceName = $transaction['source_name'] ?? null; - $sourceIban = $transaction['source_iban'] ?? null; - $sourceNumber = $transaction['source_number'] ?? null; - $validSource = $accountValidator->validateSource( - ['id' => $sourceId, 'name' => $sourceName, 'iban' => $sourceIban, 'number' => $sourceNumber] - ); - - // 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); - $validator->errors()->add(sprintf('transactions.%d.source_iban', $index), $accountValidator->sourceError); - $validator->errors()->add(sprintf('transactions.%d.source_number', $index), $accountValidator->sourceError); - - return; - } - app('log')->debug('Source account info is valid.'); - } - - if ( - array_key_exists('destination_id', $transaction) - || array_key_exists('destination_name', $transaction) - || array_key_exists('destination_iban', $transaction) - || array_key_exists('destination_number', $transaction) - ) { - app('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) { - app('log')->debug('Account validator has no source account, must find it.'); - $source = $this->getOriginalSource($transaction, $transactionGroup); - if (null !== $source) { - app('log')->debug('Found a source!'); - $accountValidator->source = $source; - } - } - $destinationId = (int) ($transaction['destination_id'] ?? 0); - $destinationName = $transaction['destination_name'] ?? null; - $destinationIban = $transaction['destination_iban'] ?? null; - $destinationNumber = $transaction['destination_number'] ?? null; - $array = ['id' => $destinationId, 'name' => $destinationName, 'iban' => $destinationIban, 'number' => $destinationNumber]; - $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); - } - app('log')->debug('Destination account info is valid.'); - } - app('log')->debug('Done with validateSingleUpdate().'); - } - /** * TODO describe this method. * @@ -459,17 +243,17 @@ trait TransactionValidation // 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')); + $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); + $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== $sourceCurrency->id) { - $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string) trans('validation.require_foreign_src')); + $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string)trans('validation.require_foreign_src')); return; } @@ -486,19 +270,19 @@ trait TransactionValidation // 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')); + $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); + $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0); app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== $destinationCurrency->id) { app('log')->debug(sprintf('No match on code, "%s" vs "%s"', $foreignCurrencyCode, $destinationCurrency->code)); app('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')); + $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_dest')); } } } @@ -536,16 +320,130 @@ trait TransactionValidation if ('' === $transaction['foreign_amount']) { return false; } - if (0 === bccomp('0', (string) $transaction['foreign_amount'])) { + if (0 === bccomp('0', (string)$transaction['foreign_amount'])) { return false; } return true; } + /** + * Validates the given account information. Switches on given transaction type. + * + * @throws FireflyException + */ + public function validateAccountInformationUpdate(Validator $validator, TransactionGroup $transactionGroup): void + { + app('log')->debug('Now in validateAccountInformationUpdate()'); + if ($validator->errors()->count() > 0) { + app('log')->debug('Validator already has errors, so return.'); + + return; + } + + $transactions = $this->getTransactionsArray($validator); + + /** + * @var null|int $index + * @var array $transaction + */ + foreach ($transactions as $index => $transaction) { + if (!is_int($index)) { + throw new FireflyException('Invalid data submitted: transaction is not array.'); + } + $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); + } + } + + protected function validateSingleUpdate(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void + { + app('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)) { + app('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 ID/name/iban/number are valid + if ( + array_key_exists('source_id', $transaction) + || array_key_exists('source_name', $transaction) + || array_key_exists('source_iban', $transaction) + || array_key_exists('source_number', $transaction) + ) { + app('log')->debug('Will try to validate source account information.'); + $sourceId = (int)($transaction['source_id'] ?? 0); + $sourceName = $transaction['source_name'] ?? null; + $sourceIban = $transaction['source_iban'] ?? null; + $sourceNumber = $transaction['source_number'] ?? null; + $validSource = $accountValidator->validateSource( + ['id' => $sourceId, 'name' => $sourceName, 'iban' => $sourceIban, 'number' => $sourceNumber] + ); + + // 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); + $validator->errors()->add(sprintf('transactions.%d.source_iban', $index), $accountValidator->sourceError); + $validator->errors()->add(sprintf('transactions.%d.source_number', $index), $accountValidator->sourceError); + + return; + } + app('log')->debug('Source account info is valid.'); + } + + if ( + array_key_exists('destination_id', $transaction) + || array_key_exists('destination_name', $transaction) + || array_key_exists('destination_iban', $transaction) + || array_key_exists('destination_number', $transaction) + ) { + app('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) { + app('log')->debug('Account validator has no source account, must find it.'); + $source = $this->getOriginalSource($transaction, $transactionGroup); + if (null !== $source) { + app('log')->debug('Found a source!'); + $accountValidator->source = $source; + } + } + $destinationId = (int)($transaction['destination_id'] ?? 0); + $destinationName = $transaction['destination_name'] ?? null; + $destinationIban = $transaction['destination_iban'] ?? null; + $destinationNumber = $transaction['destination_number'] ?? null; + $array = ['id' => $destinationId, 'name' => $destinationName, 'iban' => $destinationIban, 'number' => $destinationNumber]; + $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); + } + app('log')->debug('Destination account info is valid.'); + } + app('log')->debug('Done with validateSingleUpdate().'); + } + private function getTransactionType(TransactionGroup $group, array $transactions): string { - return $transactions[0]['type'] ?? strtolower((string) $group->transactionJournals()->first()?->transactionType->type); + return $transactions[0]['type'] ?? strtolower((string)$group->transactionJournals()->first()?->transactionType->type); } private function getOriginalSource(array $transaction, TransactionGroup $transactionGroup): ?Account @@ -558,7 +456,7 @@ trait TransactionValidation /** @var TransactionJournal $journal */ foreach ($transactionGroup->transactionJournals as $journal) { - $journalId = (int) ($transaction['transaction_journal_id'] ?? 0); + $journalId = (int)($transaction['transaction_journal_id'] ?? 0); if ($journal->id === $journalId) { return $journal->transactions()->where('amount', '<', 0)->first()?->account; } @@ -567,6 +465,108 @@ trait TransactionValidation return null; } + /** + * Adds an error to the validator when there are no transactions in the array of data. + */ + public function validateOneRecurrenceTransaction(Validator $validator): void + { + app('log')->debug('Now in validateOneRecurrenceTransaction()'); + $transactions = $this->getTransactionsArray($validator); + + // need at least one transaction + if (0 === count($transactions)) { + $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); + } + } + + /** + * Adds an error to the validator when there are no transactions in the array of data. + */ + public function validateOneTransaction(Validator $validator): void + { + app('log')->debug('Now in validateOneTransaction'); + if ($validator->errors()->count() > 0) { + app('log')->debug('Validator already has errors, so return.'); + + return; + } + $transactions = $this->getTransactionsArray($validator); + // need at least one transaction + if (0 === count($transactions)) { + $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); + app('log')->debug('Added error: at_least_one_transaction.'); + + return; + } + app('log')->debug('Added NO errors.'); + } + + public function validateTransactionArray(Validator $validator): void + { + if ($validator->errors()->count() > 0) { + return; + } + $transactions = $this->getTransactionsArray($validator); + foreach (array_keys($transactions) as $key) { + if (!is_int($key)) { + $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); + app('log')->debug('Added error: at_least_one_transaction.'); + + return; + } + } + } + + /** + * All types of splits must be equal. + */ + public function validateTransactionTypes(Validator $validator): void + { + if ($validator->errors()->count() > 0) { + return; + } + app('log')->debug('Now in validateTransactionTypes()'); + $transactions = $this->getTransactionsArray($validator); + + $types = []; + foreach ($transactions as $transaction) { + $types[] = $transaction['type'] ?? 'invalid'; + } + $unique = array_unique($types); + if (count($unique) > 1) { + $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); + + return; + } + $first = $unique[0] ?? 'invalid'; + if ('invalid' === $first) { + $validator->errors()->add('transactions.0.type', (string)trans('validation.invalid_transaction_type')); + } + } + + /** + * All types of splits must be equal. + */ + public function validateTransactionTypesForUpdate(Validator $validator): void + { + app('log')->debug('Now in validateTransactionTypesForUpdate()'); + $transactions = $this->getTransactionsArray($validator); + $types = []; + foreach ($transactions as $transaction) { + $originalType = $this->getOriginalType((int)($transaction['transaction_journal_id'] ?? 0)); + // if type is not set, fall back to the type of the journal, if one is given. + $types[] = $transaction['type'] ?? $originalType; + } + $unique = array_unique($types); + if (count($unique) > 1) { + app('log')->warning('Add error for mismatch transaction types.'); + $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); + + return; + } + app('log')->debug('No errors in validateTransactionTypesForUpdate()'); + } + private function getOriginalType(int $journalId): string { if (0 === $journalId) { @@ -608,22 +608,22 @@ trait TransactionValidation default: case 'withdrawal': if (count($sources) > 1) { - $validator->errors()->add('transactions.0.source_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); } break; case 'deposit': if (count($dests) > 1) { - $validator->errors()->add('transactions.0.destination_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } break; case 'transfer': if (count($sources) > 1 || count($dests) > 1) { - $validator->errors()->add('transactions.0.source_id', (string) trans('validation.all_accounts_equal')); - $validator->errors()->add('transactions.0.destination_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } break; @@ -657,14 +657,14 @@ trait TransactionValidation $result = $this->compareAccountData($type, $comparison); if (false === $result) { if ('withdrawal' === $type) { - $validator->errors()->add('transactions.0.source_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); } if ('deposit' === $type) { - $validator->errors()->add('transactions.0.destination_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } if ('transfer' === $type) { - $validator->errors()->add('transactions.0.source_id', (string) trans('validation.all_accounts_equal')); - $validator->errors()->add('transactions.0.destination_id', (string) trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } app('log')->warning('Add error about equal accounts.'); @@ -683,7 +683,7 @@ trait TransactionValidation /** @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)); + $originalData = $this->getOriginalData((int)($transaction['transaction_journal_id'] ?? 0)); // get field. $comparison[$field][] = $transaction[$field] ?? $originalData[$field];