Allow account endpoint to be filtered on various fields.

This commit is contained in:
James Cole
2024-08-03 06:00:22 +02:00
parent c8646e20cb
commit b213148ae8
11 changed files with 259 additions and 106 deletions

View File

@@ -30,63 +30,62 @@ use FireflyIII\Models\AccountType;
*/
trait AccountFilter
{
protected array $types = [
'all' => [
AccountType::DEFAULT,
AccountType::CASH,
AccountType::ASSET,
AccountType::EXPENSE,
AccountType::REVENUE,
AccountType::INITIAL_BALANCE,
AccountType::BENEFICIARY,
AccountType::IMPORT,
AccountType::RECONCILIATION,
AccountType::LOAN,
AccountType::DEBT,
AccountType::MORTGAGE,
],
'asset' => [AccountType::DEFAULT, AccountType::ASSET],
'cash' => [AccountType::CASH],
'expense' => [AccountType::EXPENSE, AccountType::BENEFICIARY],
'revenue' => [AccountType::REVENUE],
'special' => [AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
'hidden' => [AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
'liability' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
'liabilities' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
AccountType::DEFAULT => [AccountType::DEFAULT],
AccountType::CASH => [AccountType::CASH],
AccountType::ASSET => [AccountType::ASSET],
AccountType::EXPENSE => [AccountType::EXPENSE],
AccountType::REVENUE => [AccountType::REVENUE],
AccountType::INITIAL_BALANCE => [AccountType::INITIAL_BALANCE],
AccountType::BENEFICIARY => [AccountType::BENEFICIARY],
AccountType::IMPORT => [AccountType::IMPORT],
AccountType::RECONCILIATION => [AccountType::RECONCILIATION],
AccountType::LOAN => [AccountType::LOAN],
AccountType::MORTGAGE => [AccountType::MORTGAGE],
AccountType::DEBT => [AccountType::DEBT],
AccountType::CREDITCARD => [AccountType::CREDITCARD],
'default account' => [AccountType::DEFAULT],
'cash account' => [AccountType::CASH],
'asset account' => [AccountType::ASSET],
'expense account' => [AccountType::EXPENSE],
'revenue account' => [AccountType::REVENUE],
'initial balance account' => [AccountType::INITIAL_BALANCE],
'reconciliation' => [AccountType::RECONCILIATION],
'loan' => [AccountType::LOAN],
'mortgage' => [AccountType::MORTGAGE],
'debt' => [AccountType::DEBT],
'credit card' => [AccountType::CREDITCARD],
'credit-card' => [AccountType::CREDITCARD],
'creditcard' => [AccountType::CREDITCARD],
'cc' => [AccountType::CREDITCARD],
];
/**
* All the available types.
*/
protected function mapAccountTypes(string $type): array
{
$types = [
'all' => [
AccountType::DEFAULT,
AccountType::CASH,
AccountType::ASSET,
AccountType::EXPENSE,
AccountType::REVENUE,
AccountType::INITIAL_BALANCE,
AccountType::BENEFICIARY,
AccountType::IMPORT,
AccountType::RECONCILIATION,
AccountType::LOAN,
AccountType::DEBT,
AccountType::MORTGAGE,
],
'asset' => [AccountType::DEFAULT, AccountType::ASSET],
'cash' => [AccountType::CASH],
'expense' => [AccountType::EXPENSE, AccountType::BENEFICIARY],
'revenue' => [AccountType::REVENUE],
'special' => [AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
'hidden' => [AccountType::INITIAL_BALANCE, AccountType::IMPORT, AccountType::RECONCILIATION],
'liability' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
'liabilities' => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
AccountType::DEFAULT => [AccountType::DEFAULT],
AccountType::CASH => [AccountType::CASH],
AccountType::ASSET => [AccountType::ASSET],
AccountType::EXPENSE => [AccountType::EXPENSE],
AccountType::REVENUE => [AccountType::REVENUE],
AccountType::INITIAL_BALANCE => [AccountType::INITIAL_BALANCE],
AccountType::BENEFICIARY => [AccountType::BENEFICIARY],
AccountType::IMPORT => [AccountType::IMPORT],
AccountType::RECONCILIATION => [AccountType::RECONCILIATION],
AccountType::LOAN => [AccountType::LOAN],
AccountType::MORTGAGE => [AccountType::MORTGAGE],
AccountType::DEBT => [AccountType::DEBT],
AccountType::CREDITCARD => [AccountType::CREDITCARD],
'default account' => [AccountType::DEFAULT],
'cash account' => [AccountType::CASH],
'asset account' => [AccountType::ASSET],
'expense account' => [AccountType::EXPENSE],
'revenue account' => [AccountType::REVENUE],
'initial balance account' => [AccountType::INITIAL_BALANCE],
'reconciliation' => [AccountType::RECONCILIATION],
'loan' => [AccountType::LOAN],
'mortgage' => [AccountType::MORTGAGE],
'debt' => [AccountType::DEBT],
'credit card' => [AccountType::CREDITCARD],
'credit-card' => [AccountType::CREDITCARD],
'creditcard' => [AccountType::CREDITCARD],
'cc' => [AccountType::CREDITCARD],
];
return $types[$type] ?? $types['all'];
return $this->types[$type] ?? $this->types['all'];
}
}

View File

@@ -23,8 +23,10 @@ declare(strict_types=1);
namespace FireflyIII\Support\JsonApi;
use FireflyIII\Models\Account;
use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
use LaravelJsonApi\Core\Query\FilterParameters;
use LaravelJsonApi\Core\Query\SortFields;
@@ -51,35 +53,80 @@ trait ExpandsQuery
return $query;
}
private function parseAccountTypeFilter(array $value): array
{
$return = [];
foreach ($value as $entry) {
$return = array_merge($return, $this->mapAccountTypes($entry));
}
return array_unique($return);
}
private function parseAllFilters(string $class, FilterParameters $filters): array
{
$config = config('api.valid_api_filters')[$class];
$parsed = [];
foreach ($filters->all() as $filter) {
$key = $filter->key();
if (!in_array($key, $config, true)) {
continue;
}
// make array if not array:
$value = $filter->value();
if (null === $value) {
continue;
}
if (!is_array($value)) {
$value = [$value];
}
switch ($filter->key()) {
case 'name':
$parsed['name'] = $value;
break;
case 'type':
$parsed['type'] = $this->parseAccountTypeFilter($value);
break;
}
}
return $parsed;
}
final protected function addFilterParams(string $class, Builder $query, ?FilterParameters $filters): Builder
{
Log::debug(__METHOD__);
if (null === $filters) {
return $query;
}
$config = config(sprintf('firefly.valid_query_filters.%s', $class)) ?? [];
if (0 === count($filters->all())) {
return $query;
}
$query->where(function (Builder $q) use ($config, $filters): void {
foreach ($filters->all() as $filter) {
if (in_array($filter->key(), $config, true)) {
foreach ($filter->value() as $value) {
$q->where($filter->key(), 'LIKE', sprintf('%%%s%%', $value));
// parse filters valid for this class.
$parsed = $this->parseAllFilters($class, $filters);
// expand query for each query filter
$config = config('api.valid_query_filters')[$class];
$query->where(function (Builder $q) use ($config, $parsed): void {
foreach ($parsed as $key => $filter) {
if (in_array($key, $config, true)) {
Log::debug(sprintf('Add query filter "%s"', $key));
// add type to query:
foreach ($filter as $value) {
$q->where($key, 'LIKE', sprintf('%%%s%%', $value));
}
}
}
});
// some filters are special, i.e. the account type filter.
$typeFilters = $filters->value('type', false);
if (false !== $typeFilters && count($typeFilters) > 0) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
foreach ($typeFilters as $typeFilter) {
$types = $this->mapAccountTypes($typeFilter);
$query->whereIn('account_types.type', $types);
// TODO this is special treatment, but alas, unavoidable right now.
if ($class === Account::class && array_key_exists('type', $parsed)) {
if (count($parsed['type']) > 0) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$query->whereIn('account_types.type', $parsed['type']);
}
}
return $query;
}
}

View File

@@ -33,7 +33,7 @@ trait ValidateSortParameters
return false;
}
$config = config(sprintf('firefly.full_data_set.%s', $class)) ?? [];
$config = config(sprintf('api.full_data_set.%s', $class)) ?? [];
foreach ($params->all() as $field) {
if (in_array($field->name(), $config, true)) {