diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index e18ff15b5b..5bd47b75fd 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -67,7 +67,6 @@ abstract class Controller extends BaseController protected array $accepts = ['application/json', 'application/vnd.api+json']; /** @var array */ - protected array $allowedSort; protected bool $convertToPrimary = false; protected TransactionCurrency $primaryCurrency; protected ParameterBag $parameters; @@ -78,7 +77,6 @@ abstract class Controller extends BaseController public function __construct() { // get global parameters - $this->allowedSort = config('firefly.allowed_sort_parameters'); $this->middleware( function ($request, $next) { $this->parameters = $this->getParameters(); @@ -150,13 +148,7 @@ abstract class Controller extends BaseController } if (null !== $value) { $value = (int)$value; - if ($value < 1) { - $value = 1; - } - if ($value > 2 ** 16) { - $value = 2 ** 16; - } - + $value = min(max(1, $value), 2 ** 16); $bag->set($integer, $value); } if (null === $value @@ -173,39 +165,8 @@ abstract class Controller extends BaseController } // sort fields: - return $this->getSortParameters($bag); - } - - private function getSortParameters(ParameterBag $bag): ParameterBag - { - $sortParameters = []; - - try { - $param = (string)request()->query->get('sort'); - } catch (BadRequestException $e) { - Log::error('Request field "sort" contains a non-scalar value. Value set to NULL.'); - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - $param = ''; - } - if ('' === $param) { - return $bag; - } - $parts = explode(',', $param); - foreach ($parts as $part) { - $part = trim($part); - $direction = 'asc'; - if ('-' === $part[0]) { - $part = substr($part, 1); - $direction = 'desc'; - } - if (in_array($part, $this->allowedSort, true)) { - $sortParameters[] = [$part, $direction]; - } - } - $bag->set('sort', $sortParameters); - return $bag; + //return $this->getSortParameters($bag); } /** diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php index b2b6c5c580..d2d02ac82a 100644 --- a/app/Api/V1/Controllers/Models/Account/ShowController.php +++ b/app/Api/V1/Controllers/Models/Account/ShowController.php @@ -82,17 +82,19 @@ class ShowController extends Controller // get list of accounts. Count it and split it. $this->repository->resetAccountOrder(); - // TODO fix sort. - $collection = $this->repository->getAccountsByType($types); + $collection = $this->repository->getAccountsByType($types, $params['sort']); $count = $collection->count(); // continue sort: + // TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after. + // TODO still need to figure out how to do this easily. $accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']); // enrich /** @var User $admin */ $admin = auth()->user(); $enrichment = new AccountEnrichment(); + $enrichment->setSort($params['sort']); $enrichment->setDate($this->parameters->get('date')); $enrichment->setStart($this->parameters->get('start')); $enrichment->setEnd($this->parameters->get('end')); diff --git a/app/Api/V1/Requests/Models/Account/ShowRequest.php b/app/Api/V1/Requests/Models/Account/ShowRequest.php index 155193953e..42ab14354a 100644 --- a/app/Api/V1/Requests/Models/Account/ShowRequest.php +++ b/app/Api/V1/Requests/Models/Account/ShowRequest.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Account; use Carbon\Carbon; +use FireflyIII\Models\Account; use FireflyIII\Models\Preference; +use FireflyIII\Rules\IsValidSortInstruction; use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Request\ConvertsDataTypes; @@ -55,7 +57,7 @@ class ShowRequest extends FormRequest return [ 'type' => $this->convertString('type', 'all'), 'limit' => $limit, - 'sort' => $this->convertString('sort', 'order'), + 'sort' => $this->convertSortParameters('sort',Account::class), 'page' => $page, ]; } @@ -68,7 +70,7 @@ class ShowRequest extends FormRequest 'date' => 'date', 'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02', 'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02', - 'sort' => 'nullable|in:active,iban,name,order,-active,-iban,-name,-order', // TODO improve me. + 'sort' => ['nullable', new IsValidSortInstruction(Account::class)], 'type' => sprintf('in:%s', $keys), 'limit' => 'numeric|min:1|max:131337', 'page' => 'numeric|min:1|max:131337', diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index afd301bf22..1d5ee94224 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -484,14 +484,19 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac $query->accountTypeIn($types); } - // add sort parameters. At this point they're filtered to allowed fields to sort by: + // add sort parameters + $allowed = config('firefly.allowed_db_sort_parameters.Account', []); + $sorted = 0; if (0 !== count($sort)) { foreach ($sort as $param) { - $query->orderBy($param[0], $param[1]); + if(in_array($param[0], $allowed, true)) { + $query->orderBy($param[0], $param[1]); + ++$sorted; + } } } - if (0 === count($sort)) { + if (0 === $sorted) { if (0 !== count($res)) { $query->orderBy('accounts.order', 'ASC'); } diff --git a/app/Rules/IsValidSortInstruction.php b/app/Rules/IsValidSortInstruction.php new file mode 100644 index 0000000000..52c0970f58 --- /dev/null +++ b/app/Rules/IsValidSortInstruction.php @@ -0,0 +1,64 @@ +. + */ + +namespace FireflyIII\Rules; + +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; + +class IsValidSortInstruction implements ValidationRule +{ + private string $class; + + public function __construct(string $class) + { + $this->class = $class; + } + + public function validate(string $attribute, mixed $value, Closure $fail): void + { + $shortClass = str_replace('FireflyIII\\Models\\', '', $this->class); + if (!is_string($value)) { + $fail('validation.invalid_sort_instruction')->translate(['object' => $shortClass]); + return; + } + $validParameters = config(sprintf('firefly.allowed_sort_parameters.%s', $shortClass)); + if (!is_array($validParameters)) { + $fail('validation.no_sort_instructions')->translate(['object' => $shortClass]); + return; + } + $parts = explode(',', $value); + foreach ($parts as $i => $part) { + $part = trim($part); + if (strlen($part) < 2) { + $fail('validation.invalid_sort_instruction_index')->translate(['index' => $i, 'object' => $shortClass]); + return; + } + if ('-' === $part[0]) { + $part = substr($part, 1); + } + if (!in_array($part, $validParameters, true)) { + $fail('validation.invalid_sort_instruction_index')->translate(['index' => $i, 'object' => $shortClass]); + return; + } + } + } +} diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index 5f188b8002..4baecf7163 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -75,6 +75,7 @@ class AccountEnrichment implements EnrichmentInterface private array $endBalances = []; private array $objectGroups = []; private array $mappedObjects = []; + private array $sort = []; /** * TODO The account enricher must do conversion from and to the primary currency. @@ -115,6 +116,7 @@ class AccountEnrichment implements EnrichmentInterface $this->collectObjectGroups(); $this->collectBalances(); $this->appendCollectedData(); + $this->sortData(); return $this->collection; } @@ -142,10 +144,9 @@ class AccountEnrichment implements EnrichmentInterface private function collectMetaData(): void { - $set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt']) - ->whereIn('account_id', $this->ids) - ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray() - ; + $set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt']) + ->whereIn('account_id', $this->ids) + ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray(); /** @var array $entry */ foreach ($set as $entry) { @@ -171,10 +172,9 @@ class AccountEnrichment implements EnrichmentInterface private function collectNotes(): void { $notes = Note::query()->whereIn('noteable_id', $this->ids) - ->whereNotNull('notes.text') - ->where('notes.text', '!=', '') - ->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray() - ; + ->whereNotNull('notes.text') + ->where('notes.text', '!=', '') + ->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray(); foreach ($notes as $note) { $this->notes[(int)$note['noteable_id']] = (string)$note['text']; } @@ -184,15 +184,14 @@ class AccountEnrichment implements EnrichmentInterface private function collectLocations(): void { $locations = Location::query()->whereIn('locatable_id', $this->ids) - ->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray() - ; + ->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray(); foreach ($locations as $location) { $this->locations[(int)$location['locatable_id']] = [ - 'latitude' => (float)$location['latitude'], - 'longitude' => (float)$location['longitude'], - 'zoom_level' => (int)$location['zoom_level'], - ]; + 'latitude' => (float)$location['latitude'], + 'longitude' => (float)$location['longitude'], + 'zoom_level' => (int)$location['zoom_level'], + ]; } Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations))); } @@ -207,20 +206,19 @@ class AccountEnrichment implements EnrichmentInterface ->setUserGroup($this->userGroup) ->setAccounts($this->collection) ->withAccountInformation() - ->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]) - ; - $journals = $collector->getExtractedJournals(); + ->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]); + $journals = $collector->getExtractedJournals(); foreach ($journals as $journal) { $this->openingBalances[(int)$journal['source_account_id']] = [ - 'amount' => Steam::negative($journal['amount']), - 'date' => $journal['date'], - ]; + 'amount' => Steam::negative($journal['amount']), + 'date' => $journal['date'], + ]; $this->openingBalances[(int)$journal['destination_account_id']] = [ - 'amount' => Steam::positive($journal['amount']), - 'date' => $journal['date'], - ]; + 'amount' => Steam::positive($journal['amount']), + 'date' => $journal['date'], + ]; } } @@ -238,9 +236,9 @@ class AccountEnrichment implements EnrichmentInterface private function appendCollectedData(): void { $this->collection = $this->collection->map(function (Account $item) { - $id = (int)$item->id; - $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null; - $meta = [ + $id = (int)$item->id; + $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null; + $meta = [ 'currency' => null, 'location' => [ 'latitude' => null, @@ -287,30 +285,30 @@ class AccountEnrichment implements EnrichmentInterface // add balances // get currencies: - $currency = $this->primaryCurrency; // assume primary currency + $currency = $this->primaryCurrency; // assume primary currency if (null !== $meta['currency']) { $currency = $meta['currency']; } // get the current balance: - $date = $this->getDate(); + $date = $this->getDate(); // $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary); - $finalBalance = $this->balances[$id]; - $balanceDifference = $this->getBalanceDifference($id, $currency); + $finalBalance = $this->balances[$id]; + $balanceDifference = $this->getBalanceDifference($id, $currency); Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance); // collect current balances: - $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places); - $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places); - $virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places); - $debtAmount = $meta['current_debt'] ?? null; + $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places); + $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places); + $virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places); + $debtAmount = $meta['current_debt'] ?? null; // set some pc_ default values to NULL: - $pcCurrentBalance = null; - $pcOpeningBalance = null; - $pcVirtualBalance = null; - $pcDebtAmount = null; - $pcBalanceDifference = null; + $pcCurrentBalance = null; + $pcOpeningBalance = null; + $pcVirtualBalance = null; + $pcDebtAmount = null; + $pcBalanceDifference = null; // convert to primary currency if needed: if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) { @@ -349,7 +347,7 @@ class AccountEnrichment implements EnrichmentInterface 'pc_balance_difference' => $pcBalanceDifference, ]; // end add balances - $item->meta = $meta; + $item->meta = $meta; return $item; }); @@ -371,13 +369,12 @@ class AccountEnrichment implements EnrichmentInterface private function collectObjectGroups(): void { - $set = DB::table('object_groupables') - ->whereIn('object_groupable_id', $this->ids) - ->where('object_groupable_type', Account::class) - ->get(['object_groupable_id', 'object_group_id']) - ; + $set = DB::table('object_groupables') + ->whereIn('object_groupable_id', $this->ids) + ->where('object_groupable_type', Account::class) + ->get(['object_groupable_id', 'object_group_id']); - $ids = array_unique($set->pluck('object_group_id')->toArray()); + $ids = array_unique($set->pluck('object_group_id')->toArray()); foreach ($set as $entry) { $this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id; @@ -429,9 +426,36 @@ class AccountEnrichment implements EnrichmentInterface if (0 === count($startBalance) || 0 === count($endBalance)) { return null; } - $start = $startBalance[$currency->code] ?? '0'; - $end = $endBalance[$currency->code] ?? '0'; + $start = $startBalance[$currency->code] ?? '0'; + $end = $endBalance[$currency->code] ?? '0'; return bcsub($end, $start); } + + public function setSort(array $sort): void + { + $this->sort = $sort; + } + + private function sortData(): void + { + $dbParams = config('firefly.allowed_db_sort_parameters.Account', []); + /** @var array $parameter */ + foreach ($this->sort as $parameter) { + if (in_array($parameter[0], $dbParams, true)) { + continue; + } + switch ($parameter[0]) { + default: + throw new FireflyException(sprintf('Account enrichment cannot sort on field "%s"', $parameter[0])); + case 'current_balance': + case 'pc_current_balance': + $this->collection = $this->collection->sortBy(static function (Account $account) use ($parameter) { + return $account->meta['balances'][$parameter[0]] ?? '0'; + }, SORT_NUMERIC, 'desc' === $parameter[1]); + break; + } + } + } + } diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index 3e40f57113..70a40a6d57 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -31,7 +31,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\Facades\Steam; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; - use function Safe\preg_replace; /** @@ -99,6 +98,24 @@ trait ConvertsDataTypes return Steam::filterSpaces($string); } + public function convertSortParameters(string $field, string $class): array + { + // assume this all works, because the validator would have caught any errors. + $parameter = (string)request()->query->get($field); + $parts = explode(',', $parameter); + $sortParameters = []; + foreach ($parts as $part) { + $part = trim($part); + $direction = 'asc'; + if ('-' === $part[0]) { + $part = substr($part, 1); + $direction = 'desc'; + } + $sortParameters[] = [$part, $direction]; + } + return $sortParameters; + } + public function clearString(?string $string): ?string { $string = $this->clearStringKeepNewlines($string); @@ -129,7 +146,7 @@ trait ConvertsDataTypes // clear zalgo text (TODO also in API v2) $string = preg_replace('/(\pM{2})\pM+/u', '\1', $string); - return trim((string) $string); + return trim((string)$string); } public function convertIban(string $field): string @@ -147,7 +164,7 @@ trait ConvertsDataTypes return $default; } - return (string) $this->clearString((string) $entry); + return (string)$this->clearString((string)$entry); } /** @@ -161,7 +178,7 @@ trait ConvertsDataTypes */ public function convertInteger(string $field): int { - return (int) $this->get($field); + return (int)$this->get($field); } /** @@ -186,7 +203,7 @@ trait ConvertsDataTypes $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); } @@ -201,7 +218,7 @@ trait ConvertsDataTypes */ public function stringWithNewlines(string $field): string { - return (string) $this->clearStringKeepNewlines((string) ($this->get($field) ?? '')); + return (string)$this->clearStringKeepNewlines((string)($this->get($field) ?? '')); } /** @@ -245,14 +262,14 @@ trait ConvertsDataTypes protected function convertDateTime(?string $string): ?Carbon { - $value = $this->get((string) $string); + $value = $this->get((string)$string); if (null === $value) { return null; } if ('' === $value) { return null; } - if (10 === strlen((string) $value)) { + if (10 === strlen((string)$value)) { // probably a date format. try { $carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone')); @@ -300,7 +317,7 @@ trait ConvertsDataTypes return null; } - return (float) $res; + return (float)$res; } protected function dateFromValue(?string $string): ?Carbon @@ -338,7 +355,7 @@ trait ConvertsDataTypes return null; } - return (float) $string; + return (float)$string; } /** @@ -375,10 +392,10 @@ trait ConvertsDataTypes { $result = null; - Log::debug(sprintf('Date string is "%s"', (string) $this->get($field))); + Log::debug(sprintf('Date string is "%s"', (string)$this->get($field))); try { - $result = '' !== (string) $this->get($field) ? new Carbon((string) $this->get($field), config('app.timezone')) : null; + $result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null; } catch (InvalidFormatException) { // @ignoreException Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); @@ -399,12 +416,12 @@ trait ConvertsDataTypes return null; } - $value = (string) $this->get($field); + $value = (string)$this->get($field); if ('' === $value) { return null; } - return (int) $value; + return (int)$value; } protected function parseAccounts(mixed $array): array @@ -417,9 +434,9 @@ trait ConvertsDataTypes if (!is_array($entry)) { continue; } - $amount = null; + $amount = null; if (array_key_exists('current_amount', $entry)) { - $amount = $this->clearString((string) ($entry['current_amount'] ?? '0')); + $amount = $this->clearString((string)($entry['current_amount'] ?? '0')); if (null === $entry['current_amount']) { $amount = null; } @@ -428,7 +445,7 @@ trait ConvertsDataTypes $amount = null; } $return[] = [ - 'account_id' => $this->integerFromValue((string) ($entry['account_id'] ?? '0')), + 'account_id' => $this->integerFromValue((string)($entry['account_id'] ?? '0')), 'current_amount' => $amount, ]; } @@ -448,6 +465,6 @@ trait ConvertsDataTypes return null; } - return (int) $string; + return (int)$string; } } diff --git a/config/firefly.php b/config/firefly.php index ee6907381e..fa3e1e01ca 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -182,12 +182,12 @@ return [ 'darkMode' => 'browser', 'list_length' => 10, // to be removed if v1 is cancelled. 'default_preferences' => [ - 'frontpageAccounts' => [], - 'listPageSize' => 50, - 'currencyPreference' => 'EUR', - 'language' => 'en_US', - 'locale' => 'equal', - 'convertToPrimary' => false, + 'frontpageAccounts' => [], + 'listPageSize' => 50, + 'currencyPreference' => 'EUR', + 'language' => 'en_US', + 'locale' => 'equal', + 'convertToPrimary' => false, ], 'default_currency' => 'EUR', 'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'), @@ -407,7 +407,7 @@ return [ ], - 'rule-actions' => [ + 'rule-actions' => [ 'set_category' => SetCategory::class, 'clear_category' => ClearCategory::class, 'set_budget' => SetBudget::class, @@ -441,7 +441,7 @@ return [ // 'set_foreign_amount' => SetForeignAmount::class, // 'set_foreign_currency' => SetForeignCurrency::class, ], - 'context-rule-actions' => [ + 'context-rule-actions' => [ 'set_category', 'set_budget', 'add_tag', @@ -460,13 +460,13 @@ return [ 'convert_transfer', ], - 'test-triggers' => [ + 'test-triggers' => [ 'limit' => 10, 'range' => 200, ], // expected source types for each transaction type, in order of preference. - 'expected_source_types' => [ + 'expected_source_types' => [ 'source' => [ TransactionTypeEnum::WITHDRAWAL->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::CASH->value], @@ -511,7 +511,7 @@ return [ TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], ], ], - 'allowed_opposing_types' => [ + 'allowed_opposing_types' => [ 'source' => [ AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value, @@ -601,7 +601,7 @@ return [ ], ], // depending on the account type, return the allowed transaction types: - 'allowed_transaction_types' => [ + 'allowed_transaction_types' => [ 'source' => [ AccountTypeEnum::ASSET->value => [ TransactionTypeEnum::WITHDRAWAL->value, @@ -670,7 +670,7 @@ return [ ], // having the source + dest will tell you the transaction type. - 'account_to_transaction' => [ + 'account_to_transaction' => [ AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value, AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value, @@ -735,7 +735,7 @@ return [ ], // allowed source -> destination accounts. - 'source_dests' => [ + 'source_dests' => [ TransactionTypeEnum::WITHDRAWAL->value => [ AccountTypeEnum::ASSET->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CASH->value], AccountTypeEnum::LOAN->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value], @@ -774,7 +774,7 @@ return [ ], ], // if you add fields to this array, don't forget to update the export routine (ExportDataGenerator). - 'journal_meta_fields' => [ + 'journal_meta_fields' => [ // sepa 'sepa_cc', 'sepa_ct_op', @@ -808,31 +808,47 @@ return [ 'recurrence_count', 'recurrence_date', ], - 'webhooks' => [ + 'webhooks' => [ 'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3), ], - 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value], - 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], - 'dynamic_creation_allowed' => [ + 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value], + 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], + 'dynamic_creation_allowed' => [ AccountTypeEnum::EXPENSE->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::LIABILITY_CREDIT->value, ], - 'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], - 'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], - 'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'], + 'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], + 'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], + 'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'], // dynamic date ranges are as follows: - 'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'], + 'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'], + + 'allowed_sort_parameters' => [ + 'Account' => ['id', 'order', 'name', 'iban', 'active', 'account_type_id', + 'current_balance', + 'pc_current_balance', + 'opening_balance', + 'pc_opening_balance', + 'virtual_balance', + 'pc_virtual_balance', + 'debt_amount', + 'pc_debt_amount', + 'balance_difference', + 'pc_balance_difference', + ], + ], + 'allowed_db_sort_parameters' => [ + 'Account' => ['id', 'order', 'name', 'iban', 'active', 'account_type_id'], + ], - // only used in v1 - 'allowed_sort_parameters' => ['order', 'name', 'iban'], // preselected account lists possibilities: - 'preselected_accounts' => ['all', 'assets', 'liabilities'], + 'preselected_accounts' => ['all', 'assets', 'liabilities'], // allowed to store a piggy bank in: - 'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], + 'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], ]; diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 73d78f9700..9c9052b050 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -24,6 +24,9 @@ declare(strict_types=1); return [ + 'invalid_sort_instruction' => 'The sort instruction is invalid for an object of type ":object".', + 'invalid_sort_instruction_index' => 'The sort instruction at index #:index is invalid for an object of type ":object".', + 'no_sort_instructions' => 'There are no sort instructions defined for an object of type ":object".', 'webhook_budget_info' => 'Cannot deliver budget information for transaction related webhooks.', 'webhook_account_info' => 'Cannot deliver account information for budget related webhooks.', 'webhook_transaction_info' => 'Cannot deliver transaction information for budget related webhooks.',