mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Fix most of the index with json laravel api endpoints.
This commit is contained in:
parent
5e6034fc86
commit
762d898fee
@ -63,7 +63,7 @@ class AccountSchema extends Schema
|
||||
Attribute::make('current_debt')->sortable(),
|
||||
|
||||
// dynamic data
|
||||
Attribute::make('last_activity'),
|
||||
Attribute::make('last_activity')->sortable(),
|
||||
Attribute::make('balance_difference')->sortable(), // only used for sort.
|
||||
|
||||
// group
|
||||
|
@ -31,13 +31,13 @@ use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\ExpandsQuery;
|
||||
use FireflyIII\Support\JsonApi\FiltersPagination;
|
||||
use FireflyIII\Support\JsonApi\SortsCollection;
|
||||
use FireflyIII\Support\JsonApi\SortsQueryResults;
|
||||
use FireflyIII\Support\JsonApi\ValidateSortParameters;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Contracts\Pagination\Page;
|
||||
use LaravelJsonApi\Contracts\Store\HasPagination;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
|
||||
|
||||
class AccountQuery extends QueryAll implements HasPagination
|
||||
{
|
||||
@ -48,6 +48,8 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
use ValidateSortParameters;
|
||||
use CollectsCustomParameters;
|
||||
use AccountFilter;
|
||||
use SortsQueryResults;
|
||||
|
||||
//use PaginatesEnumerables;
|
||||
|
||||
#[\Override]
|
||||
@ -59,17 +61,15 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
public function get(): iterable
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// collect filters
|
||||
$filters = $this->queryParameters->filter();
|
||||
|
||||
// collect sort options
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
|
||||
// collect pagination based on the page
|
||||
$pagination = $this->filtersPagination($this->queryParameters->page());
|
||||
|
||||
// check if we need all accounts, regardless of pagination
|
||||
// This is necessary when the user wants to sort on specific params.
|
||||
$needsAll = $this->needsFullDataset('account', $sort);
|
||||
$needsAll = $this->needsFullDataset(Account::class, $sort);
|
||||
|
||||
// params that were not recognised, may be my own custom stuff.
|
||||
$otherParams = $this->getOtherParams($this->queryParameters->unrecognisedParameters());
|
||||
@ -77,34 +77,56 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
// start the query
|
||||
$query = $this->userGroup->accounts();
|
||||
|
||||
// if (!$needsAll) {
|
||||
// Log::debug('Does not need full dataset, will paginate.');
|
||||
// $query = $this->addPagination($query, $pagination);
|
||||
// }
|
||||
|
||||
// add sort and filter parameters to the query.
|
||||
$query = $this->addSortParams(Account::class, $query, $sort);
|
||||
$query = $this->addFilterParams(Account::class, $query, $filters);
|
||||
$query = $this->addFilterParams(Account::class, $query, $this->queryParameters->filter());
|
||||
|
||||
|
||||
// collect the result.
|
||||
$collection = $query->get(['accounts.*']);
|
||||
// sort the data after the query, and return it right away.
|
||||
$sorted = $this->sortCollection(Account::class, $collection, $sort);
|
||||
$collection = $this->sortCollection(Account::class, $collection, $sort);
|
||||
|
||||
// take from the collection the filtered page + page number:
|
||||
$currentPage = $sorted->skip($pagination['number'] - 1 * $pagination['size'])->take($pagination['size']);
|
||||
// if the entire collection needs to be enriched and sorted, do so now:
|
||||
$totalCount = $collection->count();
|
||||
Log::debug(sprintf('Total is %d', $totalCount));
|
||||
if ($needsAll) {
|
||||
Log::debug('Needs the entire collection');
|
||||
// enrich the entire collection
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
// TODO sort the set based on post-query sort options:
|
||||
$collection = $this->postQuerySort(Account::class, $collection, $sort);
|
||||
|
||||
// take the current page from the enriched set.
|
||||
$currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
|
||||
|
||||
}
|
||||
if (!$needsAll) {
|
||||
Log::debug('Needs only partial collection');
|
||||
// take from the collection the filtered page + page number:
|
||||
$currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
|
||||
// enrich only the current page.
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
$currentPage = $enrichment->enrich($currentPage);
|
||||
}
|
||||
// get current page?
|
||||
Log::debug(sprintf('Skip %d, take %d', ($pagination['number'] - 1) * $pagination['size'], $pagination['size']));
|
||||
//$currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
Log::debug(sprintf('New collection size: %d', $currentPage->count()));
|
||||
|
||||
// enrich the current page.
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
$currentPage = $enrichment->enrich($currentPage);
|
||||
|
||||
// TODO add filters after the query, if there are filters that cannot be applied to the database
|
||||
// TODO same for sort things.
|
||||
|
||||
|
||||
return new LengthAwarePaginator($currentPage,$sorted->count(),$pagination['size'],$pagination['number']);
|
||||
return new LengthAwarePaginator($currentPage, $totalCount, $pagination['size'], $pagination['number']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,12 +37,14 @@ class IsValidAccountType implements ValidationRule
|
||||
#[\Override] public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
// only check the type.
|
||||
if (array_key_exists('type', $value)) {
|
||||
$value = $value['type'];
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
|
||||
$filtered = [];
|
||||
$keys = array_keys($this->types);
|
||||
/** @var mixed $entry */
|
||||
|
@ -37,6 +37,9 @@ trait CollectsCustomParameters
|
||||
if (array_key_exists('endPeriod', $params)) {
|
||||
$return['end'] = Carbon::parse($params['endPeriod']);
|
||||
}
|
||||
if(array_key_exists('currentMoment', $params)) {
|
||||
$return['today'] = Carbon::parse($params['currentMoment']);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
133
app/Support/JsonApi/SortsQueryResults.php
Normal file
133
app/Support/JsonApi/SortsQueryResults.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/*
|
||||
* SortsQueryResults.php
|
||||
* Copyright (c) 2024 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/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Query\SortField;
|
||||
use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
trait SortsQueryResults
|
||||
{
|
||||
|
||||
final protected function postQuerySort(string $class, Collection $collection, SortFields $parameters): Collection
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
foreach ($parameters->all() as $field) {
|
||||
$collection = $this->sortQueryCollection($class, $collection, $field);
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO improve this.
|
||||
*
|
||||
* @param string $class
|
||||
* @param Collection $collection
|
||||
* @param SortField $field
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function sortQueryCollection(string $class, Collection $collection, SortField $field): Collection
|
||||
{
|
||||
// here be custom sort things.
|
||||
// sort by balance
|
||||
if (Account::class === $class && 'balance' === $field->name()) {
|
||||
$ascending = $field->isAscending();
|
||||
$collection = $collection->sort(function (Account $left, Account $right) use ($ascending): int {
|
||||
$leftSum = $this->sumBalance($left->balance);
|
||||
$rightSum = $this->sumBalance($right->balance);
|
||||
return $ascending ? bccomp($leftSum, $rightSum) : bccomp($rightSum, $leftSum);
|
||||
});
|
||||
}
|
||||
if (Account::class === $class && 'balance_difference' === $field->name()) {
|
||||
$ascending = $field->isAscending();
|
||||
$collection = $collection->sort(function (Account $left, Account $right) use ($ascending): int {
|
||||
$leftSum = $this->sumBalanceDifference($left->balance);
|
||||
$rightSum = $this->sumBalanceDifference($right->balance);
|
||||
return $ascending ? bccomp($leftSum, $rightSum) : bccomp($rightSum, $leftSum);
|
||||
});
|
||||
}
|
||||
// sort by account number
|
||||
if (Account::class === $class && 'account_number' === $field->name()) {
|
||||
$ascending = $field->isAscending();
|
||||
$collection = $collection->sort(function (Account $left, Account $right) use ($ascending): int {
|
||||
$leftNr = sprintf('%s%s', $left->iban, $left->account_number);
|
||||
$rightNr = sprintf('%s%s', $right->iban, $right->account_number);
|
||||
return $ascending ? strcmp($leftNr, $rightNr) : strcmp($rightNr, $leftNr);
|
||||
});
|
||||
}
|
||||
|
||||
// sort by last activity
|
||||
if (Account::class === $class && 'last_activity' === $field->name()) {
|
||||
$ascending = $field->isAscending();
|
||||
$collection = $collection->sort(function (Account $left, Account $right) use ($ascending): int {
|
||||
$leftNr = (int)$left->last_activity?->format('U');
|
||||
$rightNr = (int)$right->last_activity?->format('U');
|
||||
if($ascending){
|
||||
return $leftNr <=> $rightNr;
|
||||
}
|
||||
return $rightNr <=> $leftNr;
|
||||
//return (int) ($ascending ? $rightNr < $leftNr : $leftNr < $rightNr );
|
||||
});
|
||||
}
|
||||
|
||||
// sort by balance difference.
|
||||
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
private function sumBalance(?array $balance): string
|
||||
{
|
||||
if (null === $balance) {
|
||||
return '-10000000000'; // minus one billion
|
||||
}
|
||||
if (0 === count($balance)) {
|
||||
return '-10000000000'; // minus one billion
|
||||
}
|
||||
$sum = '0';
|
||||
foreach ($balance as $entry) {
|
||||
$sum = bcadd($sum, $entry['balance']);
|
||||
}
|
||||
return $sum;
|
||||
}
|
||||
|
||||
private function sumBalanceDifference(?array $balance): string
|
||||
{
|
||||
if (null === $balance) {
|
||||
return '-10000000000'; // minus one billion
|
||||
}
|
||||
if (0 === count($balance)) {
|
||||
return '-10000000000'; // minus one billion
|
||||
}
|
||||
$sum = '0';
|
||||
foreach ($balance as $entry) {
|
||||
$sum = bcadd($sum, $entry['balance_difference']);
|
||||
}
|
||||
return $sum;
|
||||
}
|
||||
|
||||
}
|
@ -34,9 +34,7 @@ trait ValidateSortParameters
|
||||
if (null === $params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$config = config(sprintf('api.full_data_set.%s', $class)) ?? [];
|
||||
|
||||
$config = config('api.full_data_set')[$class] ?? [];
|
||||
foreach ($params->all() as $field) {
|
||||
if (in_array($field->name(), $config, true)) {
|
||||
Log::debug('TRUE');
|
||||
|
@ -46,15 +46,16 @@ return [
|
||||
'accounts' => ['name', 'active', 'iban', 'order', 'account_number', 'balance', 'last_activity', 'balance_difference', 'current_debt'],
|
||||
],
|
||||
],
|
||||
// valid query columns for sorting:
|
||||
// valid query columns for sorting the query
|
||||
'valid_query_sort' => [
|
||||
Account::class => ['id','name', 'active', 'iban', 'order'],
|
||||
Account::class => ['id', 'name', 'active', 'iban', 'order'],
|
||||
],
|
||||
'valid_api_sort' => [
|
||||
Account::class => [],
|
||||
// valid query columns for sorting the query results
|
||||
'valid_api_sort' => [
|
||||
Account::class => ['account_number'],
|
||||
],
|
||||
'full_data_set' => [
|
||||
'account' => ['last_activity', 'balance_difference', 'current_balance', 'current_debt'],
|
||||
Account::class => ['last_activity', 'balance', 'balance_difference', 'current_debt', 'account_number'],
|
||||
],
|
||||
'valid_query_filters' => [
|
||||
Account::class => ['id', 'name', 'iban', 'active'],
|
||||
|
@ -48,7 +48,12 @@ const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: (searchParams, prop) => searchParams.get(prop),
|
||||
});
|
||||
sortingColumn = params.column ?? '';
|
||||
sortDirection = params.direction ?? '';
|
||||
sortDirection = 'asc';
|
||||
if(sortingColumn[0] === '-') {
|
||||
sortingColumn = sortingColumn.substring(1);
|
||||
sortDirection = 'desc';
|
||||
}
|
||||
|
||||
page = parseInt(params.page ?? 1);
|
||||
|
||||
|
||||
@ -77,6 +82,7 @@ let index = function () {
|
||||
filters: {
|
||||
active: null,
|
||||
name: null,
|
||||
type: type,
|
||||
},
|
||||
pageOptions: {
|
||||
isLoading: true,
|
||||
@ -345,10 +351,11 @@ let index = function () {
|
||||
//const sorting = [{column: this.pageOptions.sortingColumn, direction: this.pageOptions.sortDirection}];
|
||||
|
||||
// filter instructions
|
||||
let filters = [];
|
||||
let filters = {};
|
||||
for (let k in this.filters) {
|
||||
if (this.filters.hasOwnProperty(k) && null !== this.filters[k]) {
|
||||
filters.push({column: k, filter: this.filters[k]});
|
||||
filters[k] = this.filters[k];
|
||||
//filters.push({column: k, filter: this.filters[k]});
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,9 +365,9 @@ let index = function () {
|
||||
const today = new Date();
|
||||
|
||||
let params = {
|
||||
sorting: sorting,
|
||||
filters: filters,
|
||||
// today: today,
|
||||
sort: sorting,
|
||||
filter: filters,
|
||||
currentMoment: today,
|
||||
// type: type,
|
||||
page: {number: this.page},
|
||||
startPeriod: start,
|
||||
@ -368,8 +375,8 @@ let index = function () {
|
||||
};
|
||||
|
||||
if (!this.tableColumns.balance_difference.enabled) {
|
||||
delete params.start;
|
||||
delete params.end;
|
||||
delete params.startPeriod;
|
||||
delete params.enPeriod;
|
||||
}
|
||||
this.accounts = [];
|
||||
let groupedAccounts = {};
|
||||
@ -404,7 +411,6 @@ let index = function () {
|
||||
balance: current.attributes.balance,
|
||||
native_balance: current.attributes.native_balance,
|
||||
};
|
||||
|
||||
// get group info:
|
||||
let groupId = current.attributes.object_group_id;
|
||||
if(!this.pageOptions.groupedAccounts) {
|
||||
|
@ -125,12 +125,12 @@
|
||||
{{ __('list.interest') }}
|
||||
</th>
|
||||
<th x-show="tableColumns.number.visible && tableColumns.number.enabled">
|
||||
<a href="#" x-on:click.prevent="sort('iban')">
|
||||
<a href="#" x-on:click.prevent="sort('account_number')">
|
||||
{{ __('list.account_number') }}
|
||||
</a>
|
||||
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'asc'"
|
||||
<em x-show="pageOptions.sortingColumn === 'account_number' && pageOptions.sortDirection === 'asc'"
|
||||
class="fa-solid fa-arrow-down-a-z"></em>
|
||||
<em x-show="pageOptions.sortingColumn === 'iban' && pageOptions.sortDirection === 'desc'"
|
||||
<em x-show="pageOptions.sortingColumn === 'account_number' && pageOptions.sortDirection === 'desc'"
|
||||
class="fa-solid fa-arrow-down-z-a"></em>
|
||||
</th>
|
||||
<th x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||
@ -262,13 +262,17 @@
|
||||
</template>
|
||||
</td>
|
||||
<td x-show="tableColumns.current_balance.visible && tableColumns.current_balance.enabled">
|
||||
<template x-if="null !== account.balance">
|
||||
<template x-for="balance in account.balance">
|
||||
<span x-show="balance.balance < 0" class="text-danger"
|
||||
<span>
|
||||
<span x-show="parseFloat(balance.balance) < 0.0" class="text-danger"
|
||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
||||
<span x-show="balance.balance == 0" class="text-muted"
|
||||
<span x-show="parseFloat(balance.balance) === 0.0" class="text-muted"
|
||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
||||
<span x-show="balance.balance > 0" class="text-success"
|
||||
<span x-show="parseFloat(balance.balance) > 0.0" class="text-success"
|
||||
x-text="formatMoney(balance.balance, balance.currency_code)"></span>
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</td>
|
||||
<td x-show="tableColumns.amount_due.visible && tableColumns.amount_due.enabled">
|
||||
@ -284,14 +288,17 @@
|
||||
<span x-text="account.last_activity"></span>
|
||||
</td>
|
||||
<td x-show="tableColumns.balance_difference.visible && tableColumns.balance_difference.enabled">
|
||||
|
||||
<template x-if="null !== account.balance">
|
||||
<template x-for="balance in account.balance">
|
||||
<span>
|
||||
<span x-show="null != balance.balance_difference && balance.balance_difference < 0" class="text-danger"
|
||||
x-text="formatMoney(balance.balance_difference, balance.currency_code)"></span>
|
||||
<span x-show="null != balance.balance_difference && balance.balance_difference == 0" class="text-muted"
|
||||
x-text="formatMoney(balance.balance_difference, balance.currency_code)"></span>
|
||||
<span x-show="null != balance.balance_difference && balance.balance_difference > 0" class="text-success"
|
||||
x-text="formatMoney(balance.balance_difference, balance.currency_code)"></span>
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</td>
|
||||
<td x-show="tableColumns.menu.visible && tableColumns.menu.enabled">
|
||||
|
Loading…
Reference in New Issue
Block a user