Fix sort params

This commit is contained in:
James Cole
2025-09-03 20:34:28 +02:00
parent af78158d0b
commit 536eacbc0c
9 changed files with 235 additions and 141 deletions

View File

@@ -67,7 +67,6 @@ abstract class Controller extends BaseController
protected array $accepts = ['application/json', 'application/vnd.api+json']; protected array $accepts = ['application/json', 'application/vnd.api+json'];
/** @var array<int, string> */ /** @var array<int, string> */
protected array $allowedSort;
protected bool $convertToPrimary = false; protected bool $convertToPrimary = false;
protected TransactionCurrency $primaryCurrency; protected TransactionCurrency $primaryCurrency;
protected ParameterBag $parameters; protected ParameterBag $parameters;
@@ -78,7 +77,6 @@ abstract class Controller extends BaseController
public function __construct() public function __construct()
{ {
// get global parameters // get global parameters
$this->allowedSort = config('firefly.allowed_sort_parameters');
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
$this->parameters = $this->getParameters(); $this->parameters = $this->getParameters();
@@ -150,13 +148,7 @@ abstract class Controller extends BaseController
} }
if (null !== $value) { if (null !== $value) {
$value = (int)$value; $value = (int)$value;
if ($value < 1) { $value = min(max(1, $value), 2 ** 16);
$value = 1;
}
if ($value > 2 ** 16) {
$value = 2 ** 16;
}
$bag->set($integer, $value); $bag->set($integer, $value);
} }
if (null === $value if (null === $value
@@ -173,39 +165,8 @@ abstract class Controller extends BaseController
} }
// sort fields: // 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 $bag;
//return $this->getSortParameters($bag);
} }
/** /**

View File

@@ -82,17 +82,19 @@ class ShowController extends Controller
// get list of accounts. Count it and split it. // get list of accounts. Count it and split it.
$this->repository->resetAccountOrder(); $this->repository->resetAccountOrder();
// TODO fix sort. $collection = $this->repository->getAccountsByType($types, $params['sort']);
$collection = $this->repository->getAccountsByType($types);
$count = $collection->count(); $count = $collection->count();
// continue sort: // 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']); $accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']);
// enrich // enrich
/** @var User $admin */ /** @var User $admin */
$admin = auth()->user(); $admin = auth()->user();
$enrichment = new AccountEnrichment(); $enrichment = new AccountEnrichment();
$enrichment->setSort($params['sort']);
$enrichment->setDate($this->parameters->get('date')); $enrichment->setDate($this->parameters->get('date'));
$enrichment->setStart($this->parameters->get('start')); $enrichment->setStart($this->parameters->get('start'));
$enrichment->setEnd($this->parameters->get('end')); $enrichment->setEnd($this->parameters->get('end'));

View File

@@ -24,7 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\Account; namespace FireflyIII\Api\V1\Requests\Models\Account;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\Rules\IsValidSortInstruction;
use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
@@ -55,7 +57,7 @@ class ShowRequest extends FormRequest
return [ return [
'type' => $this->convertString('type', 'all'), 'type' => $this->convertString('type', 'all'),
'limit' => $limit, 'limit' => $limit,
'sort' => $this->convertString('sort', 'order'), 'sort' => $this->convertSortParameters('sort',Account::class),
'page' => $page, 'page' => $page,
]; ];
} }
@@ -68,7 +70,7 @@ class ShowRequest extends FormRequest
'date' => 'date', 'date' => 'date',
'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02', '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', '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), 'type' => sprintf('in:%s', $keys),
'limit' => 'numeric|min:1|max:131337', 'limit' => 'numeric|min:1|max:131337',
'page' => 'numeric|min:1|max:131337', 'page' => 'numeric|min:1|max:131337',

View File

@@ -484,14 +484,19 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
$query->accountTypeIn($types); $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)) { if (0 !== count($sort)) {
foreach ($sort as $param) { 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)) { if (0 !== count($res)) {
$query->orderBy('accounts.order', 'ASC'); $query->orderBy('accounts.order', 'ASC');
} }

View File

@@ -0,0 +1,64 @@
<?php
/*
* IsValidSortInstruction.php
* Copyright (c) 2025 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
}
}

View File

@@ -75,6 +75,7 @@ class AccountEnrichment implements EnrichmentInterface
private array $endBalances = []; private array $endBalances = [];
private array $objectGroups = []; private array $objectGroups = [];
private array $mappedObjects = []; private array $mappedObjects = [];
private array $sort = [];
/** /**
* TODO The account enricher must do conversion from and to the primary currency. * TODO The account enricher must do conversion from and to the primary currency.
@@ -115,6 +116,7 @@ class AccountEnrichment implements EnrichmentInterface
$this->collectObjectGroups(); $this->collectObjectGroups();
$this->collectBalances(); $this->collectBalances();
$this->appendCollectedData(); $this->appendCollectedData();
$this->sortData();
return $this->collection; return $this->collection;
} }
@@ -142,10 +144,9 @@ class AccountEnrichment implements EnrichmentInterface
private function collectMetaData(): void 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']) $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) ->whereIn('account_id', $this->ids)
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray() ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray();
;
/** @var array $entry */ /** @var array $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
@@ -171,10 +172,9 @@ class AccountEnrichment implements EnrichmentInterface
private function collectNotes(): void private function collectNotes(): void
{ {
$notes = Note::query()->whereIn('noteable_id', $this->ids) $notes = Note::query()->whereIn('noteable_id', $this->ids)
->whereNotNull('notes.text') ->whereNotNull('notes.text')
->where('notes.text', '!=', '') ->where('notes.text', '!=', '')
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray() ->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
;
foreach ($notes as $note) { foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text']; $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
} }
@@ -184,15 +184,14 @@ class AccountEnrichment implements EnrichmentInterface
private function collectLocations(): void private function collectLocations(): void
{ {
$locations = Location::query()->whereIn('locatable_id', $this->ids) $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) { foreach ($locations as $location) {
$this->locations[(int)$location['locatable_id']] $this->locations[(int)$location['locatable_id']]
= [ = [
'latitude' => (float)$location['latitude'], 'latitude' => (float)$location['latitude'],
'longitude' => (float)$location['longitude'], 'longitude' => (float)$location['longitude'],
'zoom_level' => (int)$location['zoom_level'], 'zoom_level' => (int)$location['zoom_level'],
]; ];
} }
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations))); Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
} }
@@ -207,20 +206,19 @@ class AccountEnrichment implements EnrichmentInterface
->setUserGroup($this->userGroup) ->setUserGroup($this->userGroup)
->setAccounts($this->collection) ->setAccounts($this->collection)
->withAccountInformation() ->withAccountInformation()
->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]) ->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]);
; $journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
foreach ($journals as $journal) { foreach ($journals as $journal) {
$this->openingBalances[(int)$journal['source_account_id']] $this->openingBalances[(int)$journal['source_account_id']]
= [ = [
'amount' => Steam::negative($journal['amount']), 'amount' => Steam::negative($journal['amount']),
'date' => $journal['date'], 'date' => $journal['date'],
]; ];
$this->openingBalances[(int)$journal['destination_account_id']] $this->openingBalances[(int)$journal['destination_account_id']]
= [ = [
'amount' => Steam::positive($journal['amount']), 'amount' => Steam::positive($journal['amount']),
'date' => $journal['date'], 'date' => $journal['date'],
]; ];
} }
} }
@@ -238,9 +236,9 @@ class AccountEnrichment implements EnrichmentInterface
private function appendCollectedData(): void private function appendCollectedData(): void
{ {
$this->collection = $this->collection->map(function (Account $item) { $this->collection = $this->collection->map(function (Account $item) {
$id = (int)$item->id; $id = (int)$item->id;
$item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null; $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
$meta = [ $meta = [
'currency' => null, 'currency' => null,
'location' => [ 'location' => [
'latitude' => null, 'latitude' => null,
@@ -287,30 +285,30 @@ class AccountEnrichment implements EnrichmentInterface
// add balances // add balances
// get currencies: // get currencies:
$currency = $this->primaryCurrency; // assume primary currency $currency = $this->primaryCurrency; // assume primary currency
if (null !== $meta['currency']) { if (null !== $meta['currency']) {
$currency = $meta['currency']; $currency = $meta['currency'];
} }
// get the current balance: // get the current balance:
$date = $this->getDate(); $date = $this->getDate();
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary); // $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
$finalBalance = $this->balances[$id]; $finalBalance = $this->balances[$id];
$balanceDifference = $this->getBalanceDifference($id, $currency); $balanceDifference = $this->getBalanceDifference($id, $currency);
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance); Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
// collect current balances: // collect current balances:
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places); $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
$openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '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); $virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
$debtAmount = $meta['current_debt'] ?? null; $debtAmount = $meta['current_debt'] ?? null;
// set some pc_ default values to NULL: // set some pc_ default values to NULL:
$pcCurrentBalance = null; $pcCurrentBalance = null;
$pcOpeningBalance = null; $pcOpeningBalance = null;
$pcVirtualBalance = null; $pcVirtualBalance = null;
$pcDebtAmount = null; $pcDebtAmount = null;
$pcBalanceDifference = null; $pcBalanceDifference = null;
// convert to primary currency if needed: // convert to primary currency if needed:
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) { if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
@@ -349,7 +347,7 @@ class AccountEnrichment implements EnrichmentInterface
'pc_balance_difference' => $pcBalanceDifference, 'pc_balance_difference' => $pcBalanceDifference,
]; ];
// end add balances // end add balances
$item->meta = $meta; $item->meta = $meta;
return $item; return $item;
}); });
@@ -371,13 +369,12 @@ class AccountEnrichment implements EnrichmentInterface
private function collectObjectGroups(): void private function collectObjectGroups(): void
{ {
$set = DB::table('object_groupables') $set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids) ->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', Account::class) ->where('object_groupable_type', Account::class)
->get(['object_groupable_id', 'object_group_id']) ->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) { foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id; $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)) { if (0 === count($startBalance) || 0 === count($endBalance)) {
return null; return null;
} }
$start = $startBalance[$currency->code] ?? '0'; $start = $startBalance[$currency->code] ?? '0';
$end = $endBalance[$currency->code] ?? '0'; $end = $endBalance[$currency->code] ?? '0';
return bcsub($end, $start); 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<string,string> $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;
}
}
}
} }

View File

@@ -31,7 +31,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use function Safe\preg_replace; use function Safe\preg_replace;
/** /**
@@ -99,6 +98,24 @@ trait ConvertsDataTypes
return Steam::filterSpaces($string); 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 public function clearString(?string $string): ?string
{ {
$string = $this->clearStringKeepNewlines($string); $string = $this->clearStringKeepNewlines($string);
@@ -129,7 +146,7 @@ trait ConvertsDataTypes
// clear zalgo text (TODO also in API v2) // clear zalgo text (TODO also in API v2)
$string = preg_replace('/(\pM{2})\pM+/u', '\1', $string); $string = preg_replace('/(\pM{2})\pM+/u', '\1', $string);
return trim((string) $string); return trim((string)$string);
} }
public function convertIban(string $field): string public function convertIban(string $field): string
@@ -147,7 +164,7 @@ trait ConvertsDataTypes
return $default; 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 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(); $collection = new Collection();
if (is_array($set)) { if (is_array($set)) {
foreach ($set as $accountId) { foreach ($set as $accountId) {
$account = $repository->find((int) $accountId); $account = $repository->find((int)$accountId);
if (null !== $account) { if (null !== $account) {
$collection->push($account); $collection->push($account);
} }
@@ -201,7 +218,7 @@ trait ConvertsDataTypes
*/ */
public function stringWithNewlines(string $field): string 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 protected function convertDateTime(?string $string): ?Carbon
{ {
$value = $this->get((string) $string); $value = $this->get((string)$string);
if (null === $value) { if (null === $value) {
return null; return null;
} }
if ('' === $value) { if ('' === $value) {
return null; return null;
} }
if (10 === strlen((string) $value)) { if (10 === strlen((string)$value)) {
// probably a date format. // probably a date format.
try { try {
$carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone')); $carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone'));
@@ -300,7 +317,7 @@ trait ConvertsDataTypes
return null; return null;
} }
return (float) $res; return (float)$res;
} }
protected function dateFromValue(?string $string): ?Carbon protected function dateFromValue(?string $string): ?Carbon
@@ -338,7 +355,7 @@ trait ConvertsDataTypes
return null; return null;
} }
return (float) $string; return (float)$string;
} }
/** /**
@@ -375,10 +392,10 @@ trait ConvertsDataTypes
{ {
$result = null; $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 { 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) { } catch (InvalidFormatException) {
// @ignoreException // @ignoreException
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
@@ -399,12 +416,12 @@ trait ConvertsDataTypes
return null; return null;
} }
$value = (string) $this->get($field); $value = (string)$this->get($field);
if ('' === $value) { if ('' === $value) {
return null; return null;
} }
return (int) $value; return (int)$value;
} }
protected function parseAccounts(mixed $array): array protected function parseAccounts(mixed $array): array
@@ -417,9 +434,9 @@ trait ConvertsDataTypes
if (!is_array($entry)) { if (!is_array($entry)) {
continue; continue;
} }
$amount = null; $amount = null;
if (array_key_exists('current_amount', $entry)) { 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']) { if (null === $entry['current_amount']) {
$amount = null; $amount = null;
} }
@@ -428,7 +445,7 @@ trait ConvertsDataTypes
$amount = null; $amount = null;
} }
$return[] = [ $return[] = [
'account_id' => $this->integerFromValue((string) ($entry['account_id'] ?? '0')), 'account_id' => $this->integerFromValue((string)($entry['account_id'] ?? '0')),
'current_amount' => $amount, 'current_amount' => $amount,
]; ];
} }
@@ -448,6 +465,6 @@ trait ConvertsDataTypes
return null; return null;
} }
return (int) $string; return (int)$string;
} }
} }

View File

@@ -182,12 +182,12 @@ return [
'darkMode' => 'browser', 'darkMode' => 'browser',
'list_length' => 10, // to be removed if v1 is cancelled. 'list_length' => 10, // to be removed if v1 is cancelled.
'default_preferences' => [ 'default_preferences' => [
'frontpageAccounts' => [], 'frontpageAccounts' => [],
'listPageSize' => 50, 'listPageSize' => 50,
'currencyPreference' => 'EUR', 'currencyPreference' => 'EUR',
'language' => 'en_US', 'language' => 'en_US',
'locale' => 'equal', 'locale' => 'equal',
'convertToPrimary' => false, 'convertToPrimary' => false,
], ],
'default_currency' => 'EUR', 'default_currency' => 'EUR',
'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'), 'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'),
@@ -407,7 +407,7 @@ return [
], ],
'rule-actions' => [ 'rule-actions' => [
'set_category' => SetCategory::class, 'set_category' => SetCategory::class,
'clear_category' => ClearCategory::class, 'clear_category' => ClearCategory::class,
'set_budget' => SetBudget::class, 'set_budget' => SetBudget::class,
@@ -441,7 +441,7 @@ return [
// 'set_foreign_amount' => SetForeignAmount::class, // 'set_foreign_amount' => SetForeignAmount::class,
// 'set_foreign_currency' => SetForeignCurrency::class, // 'set_foreign_currency' => SetForeignCurrency::class,
], ],
'context-rule-actions' => [ 'context-rule-actions' => [
'set_category', 'set_category',
'set_budget', 'set_budget',
'add_tag', 'add_tag',
@@ -460,13 +460,13 @@ return [
'convert_transfer', 'convert_transfer',
], ],
'test-triggers' => [ 'test-triggers' => [
'limit' => 10, 'limit' => 10,
'range' => 200, 'range' => 200,
], ],
// expected source types for each transaction type, in order of preference. // expected source types for each transaction type, in order of preference.
'expected_source_types' => [ 'expected_source_types' => [
'source' => [ 'source' => [
TransactionTypeEnum::WITHDRAWAL->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], 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], 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], TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
], ],
], ],
'allowed_opposing_types' => [ 'allowed_opposing_types' => [
'source' => [ 'source' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value, AccountTypeEnum::ASSET->value,
@@ -601,7 +601,7 @@ return [
], ],
], ],
// depending on the account type, return the allowed transaction types: // depending on the account type, return the allowed transaction types:
'allowed_transaction_types' => [ 'allowed_transaction_types' => [
'source' => [ 'source' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::WITHDRAWAL->value,
@@ -670,7 +670,7 @@ return [
], ],
// having the source + dest will tell you the transaction type. // having the source + dest will tell you the transaction type.
'account_to_transaction' => [ 'account_to_transaction' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value, AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value,
AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value, AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value,
@@ -735,7 +735,7 @@ return [
], ],
// allowed source -> destination accounts. // allowed source -> destination accounts.
'source_dests' => [ 'source_dests' => [
TransactionTypeEnum::WITHDRAWAL->value => [ TransactionTypeEnum::WITHDRAWAL->value => [
AccountTypeEnum::ASSET->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CASH->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], 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). // if you add fields to this array, don't forget to update the export routine (ExportDataGenerator).
'journal_meta_fields' => [ 'journal_meta_fields' => [
// sepa // sepa
'sepa_cc', 'sepa_cc',
'sepa_ct_op', 'sepa_ct_op',
@@ -808,31 +808,47 @@ return [
'recurrence_count', 'recurrence_count',
'recurrence_date', 'recurrence_date',
], ],
'webhooks' => [ 'webhooks' => [
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3), 'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
], ],
'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value], 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value],
'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
'dynamic_creation_allowed' => [ 'dynamic_creation_allowed' => [
AccountTypeEnum::EXPENSE->value, AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::REVENUE->value, AccountTypeEnum::REVENUE->value,
AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::RECONCILIATION->value,
AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LIABILITY_CREDIT->value,
], ],
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], '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_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_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
// dynamic date ranges are as follows: // 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 account lists possibilities:
'preselected_accounts' => ['all', 'assets', 'liabilities'], 'preselected_accounts' => ['all', 'assets', 'liabilities'],
// allowed to store a piggy bank in: // 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],
]; ];

View File

@@ -24,6 +24,9 @@
declare(strict_types=1); declare(strict_types=1);
return [ 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_budget_info' => 'Cannot deliver budget information for transaction related webhooks.',
'webhook_account_info' => 'Cannot deliver account information for budget related webhooks.', 'webhook_account_info' => 'Cannot deliver account information for budget related webhooks.',
'webhook_transaction_info' => 'Cannot deliver transaction information for budget related webhooks.', 'webhook_transaction_info' => 'Cannot deliver transaction information for budget related webhooks.',