Can now update order of accounts efficiently.

This commit is contained in:
James Cole
2021-03-07 16:19:14 +01:00
parent 91394553c3
commit e3161a8b9c
5 changed files with 179 additions and 30 deletions

View File

@@ -82,6 +82,7 @@ class ShowController extends Controller
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
// get list of accounts. Count it and split it. // get list of accounts. Count it and split it.
$this->repository->sortAccounts();
$collection = $this->repository->getAccountsByType($types); $collection = $this->repository->getAccountsByType($types);
$count = $collection->count(); $count = $collection->count();
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); $accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);

View File

@@ -62,7 +62,6 @@ class UpdateRequest extends FormRequest
'account_type' => $this->nullableString('type'), 'account_type' => $this->nullableString('type'),
'account_type_id' => null, 'account_type_id' => null,
'currency_id' => $this->nullableInteger('currency_id'), 'currency_id' => $this->nullableInteger('currency_id'),
'order' => $this->integer('order'),
'currency_code' => $this->nullableString('currency_code'), 'currency_code' => $this->nullableString('currency_code'),
'virtual_balance' => $this->nullableString('virtual_balance'), 'virtual_balance' => $this->nullableString('virtual_balance'),
'iban' => $this->nullableString('iban'), 'iban' => $this->nullableString('iban'),
@@ -77,6 +76,9 @@ class UpdateRequest extends FormRequest
'interest' => $this->nullableString('interest'), 'interest' => $this->nullableString('interest'),
'interest_period' => $this->nullableString('interest_period'), 'interest_period' => $this->nullableString('interest_period'),
]; ];
if(null !== $this->get('order')) {
$data['order'] = $this->integer('order');
}
$data = $this->appendLocationData($data, null); $data = $this->appendLocationData($data, null);

View File

@@ -37,8 +37,8 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Services\Internal\Destroy\AccountDestroyService; use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
use FireflyIII\Services\Internal\Update\AccountUpdateService; use FireflyIII\Services\Internal\Update\AccountUpdateService;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
use Storage; use Storage;
@@ -193,7 +193,7 @@ class AccountRepository implements AccountRepositoryInterface
*/ */
public function getAccountCurrency(Account $account): ?TransactionCurrency public function getAccountCurrency(Account $account): ?TransactionCurrency
{ {
$currencyId = (int) $this->getMetaValue($account, 'currency_id'); $currencyId = (int)$this->getMetaValue($account, 'currency_id');
if ($currencyId > 0) { if ($currencyId > 0) {
return TransactionCurrency::find($currencyId); return TransactionCurrency::find($currencyId);
} }
@@ -303,15 +303,18 @@ class AccountRepository implements AccountRepositoryInterface
*/ */
public function getMetaValue(Account $account, string $field): ?string public function getMetaValue(Account $account, string $field): ?string
{ {
$result = $account->accountMeta->filter(function (AccountMeta $meta) use ($field) { $result = $account->accountMeta->filter(
return strtolower($meta->name) === strtolower($field); function (AccountMeta $meta) use ($field) {
}); return strtolower($meta->name) === strtolower($field);
}
);
if (0 === $result->count()) { if (0 === $result->count()) {
return null; return null;
} }
if (1 === $result->count()) { if (1 === $result->count()) {
return (string) $result->first()->data; return (string)$result->first()->data;
} }
return null; return null;
} }
@@ -369,7 +372,7 @@ class AccountRepository implements AccountRepositoryInterface
return null; return null;
} }
return (string) $transaction->amount; return (string)$transaction->amount;
} }
/** /**
@@ -487,7 +490,7 @@ class AccountRepository implements AccountRepositoryInterface
->orderBy('transaction_journals.id', 'ASC') ->orderBy('transaction_journals.id', 'ASC')
->first(['transaction_journals.id']); ->first(['transaction_journals.id']);
if (null !== $first) { if (null !== $first) {
return TransactionJournal::find((int) $first->id); return TransactionJournal::find((int)$first->id);
} }
return null; return null;
@@ -577,6 +580,7 @@ class AccountRepository implements AccountRepositoryInterface
{ {
/** @var AccountUpdateService $service */ /** @var AccountUpdateService $service */
$service = app(AccountUpdateService::class); $service = app(AccountUpdateService::class);
return $service->update($account, $data); return $service->update($account, $data);
} }
@@ -641,8 +645,8 @@ class AccountRepository implements AccountRepositoryInterface
$info = $account->transactions()->get(['transaction_currency_id', 'foreign_currency_id'])->toArray(); $info = $account->transactions()->get(['transaction_currency_id', 'foreign_currency_id'])->toArray();
$currencyIds = []; $currencyIds = [];
foreach ($info as $entry) { foreach ($info as $entry) {
$currencyIds[] = (int) $entry['transaction_currency_id']; $currencyIds[] = (int)$entry['transaction_currency_id'];
$currencyIds[] = (int) $entry['foreign_currency_id']; $currencyIds[] = (int)$entry['foreign_currency_id'];
} }
$currencyIds = array_unique($currencyIds); $currencyIds = array_unique($currencyIds);
@@ -682,13 +686,17 @@ class AccountRepository implements AccountRepositoryInterface
$parts = explode(' ', $query); $parts = explode(' ', $query);
foreach ($parts as $part) { foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part); $search = sprintf('%%%s%%', $part);
$dbQuery->where(function (EloquentBuilder $q1) use ($search) { $dbQuery->where(
$q1->where('accounts.iban', 'LIKE', $search); function (EloquentBuilder $q1) use ($search) {
$q1->orWhere(function (EloquentBuilder $q2) use ($search) { $q1->where('accounts.iban', 'LIKE', $search);
$q2->where('account_meta.name', '=', 'account_number'); $q1->orWhere(
$q2->where('account_meta.data', 'LIKE', $search); function (EloquentBuilder $q2) use ($search) {
}); $q2->where('account_meta.name', '=', 'account_number');
}); $q2->where('account_meta.data', 'LIKE', $search);
}
);
}
);
} }
} }
if (!empty($types)) { if (!empty($types)) {
@@ -698,4 +706,51 @@ class AccountRepository implements AccountRepositoryInterface
return $dbQuery->take($limit)->get(['accounts.*']); return $dbQuery->take($limit)->get(['accounts.*']);
} }
/**
* @inheritDoc
*/
public function sortAccounts(): void
{
// sort assets
$list = $this->user->accounts()
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->where('account_types.type', AccountType::ASSET)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.name', 'ASC')
->orderBy('accounts.created_at', 'ASC')->get(['accounts.id', 'accounts.order']);
$index = 1;
/** @var Account $account */
foreach ($list as $account) {
if ($account->order !== $index) {
$account->order = $index;
$account->save();
}
$index++;
}
// sort liabilities
$list = $this->user->accounts()
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereIn('account_types.type', [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE])
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.name', 'ASC')
->orderBy('accounts.created_at', 'ASC')->get(['accounts.id', 'accounts.order']);
$index = 1;
/** @var Account $account */
foreach ($list as $account) {
if ($account->order !== $index) {
$account->order = $index;
$account->save();
}
$index++;
}
// set the rest to zero:
$this->user->accounts()
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereNotIn('account_types.type', [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE])
->update(['order' => '0']);
}
} }

View File

@@ -61,6 +61,11 @@ interface AccountRepositoryInterface
*/ */
public function getUsedCurrencies(Account $account): Collection; public function getUsedCurrencies(Account $account): Collection;
/**
* Sort accounts (and fix the sort if necessary).
*/
public function sortAccounts(): void;
/** /**
* @param Account $account * @param Account $account
* *
@@ -80,7 +85,7 @@ interface AccountRepositoryInterface
/** /**
* Moved here from account CRUD. * Moved here from account CRUD.
* *
* @param Account $account * @param Account $account
* @param Account|null $moveTo * @param Account|null $moveTo
* *
* @return bool * @return bool
@@ -98,7 +103,7 @@ interface AccountRepositoryInterface
/** /**
* @param string $iban * @param string $iban
* @param array $types * @param array $types
* *
* @return Account|null * @return Account|null
*/ */
@@ -106,7 +111,7 @@ interface AccountRepositoryInterface
/** /**
* @param string $name * @param string $name
* @param array $types * @param array $types
* *
* @return Account|null * @return Account|null
*/ */
@@ -172,7 +177,7 @@ interface AccountRepositoryInterface
* Return meta value for account. Null if not found. * Return meta value for account. Null if not found.
* *
* @param Account $account * @param Account $account
* @param string $field * @param string $field
* *
* @return null|string * @return null|string
*/ */
@@ -265,8 +270,8 @@ interface AccountRepositoryInterface
/** /**
* @param string $query * @param string $query
* @param array $types * @param array $types
* @param int $limit * @param int $limit
* *
* @return Collection * @return Collection
*/ */
@@ -274,8 +279,8 @@ interface AccountRepositoryInterface
/** /**
* @param string $query * @param string $query
* @param array $types * @param array $types
* @param int $limit * @param int $limit
* *
* @return Collection * @return Collection
*/ */
@@ -295,7 +300,7 @@ interface AccountRepositoryInterface
/** /**
* @param Account $account * @param Account $account
* @param array $data * @param array $data
* *
* @return Account * @return Account
*/ */

View File

@@ -74,6 +74,7 @@ class AccountUpdateService
$this->accountRepository->setUser($account->user); $this->accountRepository->setUser($account->user);
$this->user = $account->user; $this->user = $account->user;
$account = $this->updateAccount($account, $data); $account = $this->updateAccount($account, $data);
$account = $this->updateAccountOrder($account, $data);
// find currency, or use default currency instead. // find currency, or use default currency instead.
if (isset($data['currency_id']) && (null !== $data['currency_id'] || null !== $data['currency_code'])) { if (isset($data['currency_id']) && (null !== $data['currency_id'] || null !== $data['currency_code'])) {
@@ -106,7 +107,8 @@ class AccountUpdateService
* @param Account $account * @param Account $account
* @param array $data * @param array $data
*/ */
private function updateLocation(Account $account, array $data): void { private function updateLocation(Account $account, array $data): void
{
$updateLocation = $data['update_location'] ?? false; $updateLocation = $data['update_location'] ?? false;
// location must be updated? // location must be updated?
if (true === $updateLocation) { if (true === $updateLocation) {
@@ -169,7 +171,6 @@ class AccountUpdateService
$account->name = $data['name'] ?? $account->name; $account->name = $data['name'] ?? $account->name;
$account->active = $data['active'] ?? $account->active; $account->active = $data['active'] ?? $account->active;
$account->iban = $data['iban'] ?? $account->iban; $account->iban = $data['iban'] ?? $account->iban;
$account->order = $data['order'] ?? $account->order;
// if account type is a liability, the liability type (account type) // if account type is a liability, the liability type (account type)
// can be updated to another one. // can be updated to another one.
@@ -202,7 +203,7 @@ class AccountUpdateService
$array = $preference->data; $array = $preference->data;
Log::debug('Current list of accounts: ', $array); Log::debug('Current list of accounts: ', $array);
Log::debug(sprintf('Going to remove account #%d', $removeAccountId)); Log::debug(sprintf('Going to remove account #%d', $removeAccountId));
$filtered = array_filter( $filtered = array_filter(
$array, function ($accountId) use ($removeAccountId) { $array, function ($accountId) use ($removeAccountId) {
return (int)$accountId !== $removeAccountId; return (int)$accountId !== $removeAccountId;
} }
@@ -210,9 +211,11 @@ class AccountUpdateService
Log::debug('Left with accounts', array_values($filtered)); Log::debug('Left with accounts', array_values($filtered));
app('preferences')->setForUser($account->user, 'frontpageAccounts', array_values($filtered)); app('preferences')->setForUser($account->user, 'frontpageAccounts', array_values($filtered));
app('preferences')->forget($account->user, 'frontpageAccounts'); app('preferences')->forget($account->user, 'frontpageAccounts');
return; return;
} }
Log::debug("Found no frontpageAccounts preference, do nothing."); Log::debug("Found no frontpageAccounts preference, do nothing.");
return; return;
} }
Log::debug('Account was not marked as inactive, do nothing.'); Log::debug('Account was not marked as inactive, do nothing.');
@@ -241,4 +244,87 @@ class AccountUpdateService
} }
} }
} }
/**
* @param Account $account
* @param array $data
*
* @return Account
*/
private function updateAccountOrder(Account $account, array $data): Account
{
// skip if no order info
if (!array_key_exists('order', $data) || $data['order'] === $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)) {
return $account;
}
// get account type ID's because a join and an update is hard:
$list = $this->getTypeIds([AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT]);
$oldOrder = (int)$account->order;
$newOrder = $data['order'];
if (in_array($type, [AccountType::ASSET], true)) {
$list = $this->getTypeIds([AccountType::ASSET]);
}
if ($oldOrder > $newOrder) {
// say you move from 9 (old) to 3 (new)
// everything that's 3 or higher moves up one spot.
// that leaves a gap for nr 3 later on.
// 1 2 (!) 4 5 6 7 8 9 10 11 12 13 14
$this->user->accounts()
->whereIn('accounts.account_type_id', $list)
->where('accounts.order', '>=', $newOrder)
->update(['accounts.order' => \DB::raw('accounts.order + 1')]);
// update the account and save it:
// nummer 9 (now 10!) will move to nr 3.
// a gap appears on spot 10.
// 1 2 3 4 5 6 7 8 9 11 12 13 14
$account->order = $newOrder;
$account->save();
// everything over 9 (old) drops one spot
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14
$this->user->accounts()
->whereIn('accounts.account_type_id', $list)
->where('accounts.order', '>', $oldOrder)
->update(['accounts.order' => \DB::raw('accounts.order - 1')]);
return $account;
}
if ($oldOrder < $newOrder) {
// if it goes from 3 (old) to 9 (new),
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// everything that is between 3 and 9 (incl) - 1 spot
// 1 2 2 3 4 5 6 7 8 10 11 12 13 14
$this->user->accounts()
->whereIn('accounts.account_type_id', $list)
->where('accounts.order', '>=', $oldOrder)
->where('accounts.order', '<=', $newOrder)
->update(['accounts.order' => \DB::raw('accounts.order - 1')]);
// then set order to 9
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14
$account->order = $newOrder;
$account->save();
}
return $account;
}
private function getTypeIds(array $array): array
{
$return = [];
/** @var string $type */
foreach ($array as $type) {
/** @var AccountType $type */
$type = AccountType::whereType($type)->first();
$return[] = (int)$type->id;
}
return $return;
}
} }