diff --git a/app/Repositories/UserGroups/Account/AccountRepository.php b/app/Repositories/UserGroups/Account/AccountRepository.php index 8e69ddef29..875e722c8d 100644 --- a/app/Repositories/UserGroups/Account/AccountRepository.php +++ b/app/Repositories/UserGroups/Account/AccountRepository.php @@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\UserGroups\Account; use FireflyIII\Models\Account; use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; +use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Services\Internal\Update\AccountUpdateService; @@ -65,8 +66,7 @@ class AccountRepository implements AccountRepositoryInterface $q1->where('account_meta.name', '=', 'account_number'); $q1->where('account_meta.data', '=', $json); } - ) - ; + ); if (0 !== count($types)) { $dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); @@ -92,7 +92,7 @@ class AccountRepository implements AccountRepositoryInterface public function findByName(string $name, array $types): ?Account { - $query = $this->userGroup->accounts(); + $query = $this->userGroup->accounts(); if (0 !== count($types)) { $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); @@ -116,8 +116,8 @@ class AccountRepository implements AccountRepositoryInterface public function getAccountCurrency(Account $account): ?TransactionCurrency { - $type = $account->accountType->type; - $list = config('firefly.valid_currency_account_types'); + $type = $account->accountType->type; + $list = config('firefly.valid_currency_account_types'); // return null if not in this list. if (!in_array($type, $list, true)) { @@ -242,9 +242,9 @@ class AccountRepository implements AccountRepositoryInterface public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection { - $sortable = ['name', 'active']; // TODO yes this is a duplicate array. - $res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types); - $query = $this->userGroup->accounts(); + $sortable = ['name', 'active']; // TODO yes this is a duplicate array. + $res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types); + $query = $this->userGroup->accounts(); if (0 !== count($types)) { $query->accountTypeIn($types); } @@ -252,15 +252,15 @@ class AccountRepository implements AccountRepositoryInterface // process filters // TODO this should be repeatable, it feels like a hack when you do it here. // TODO some fields cannot be filtered using the query, and a second filter must be applied on the collection. - foreach($filters as $column => $value) { + foreach ($filters as $column => $value) { // filter on NULL values - if(null === $value) { + if (null === $value) { continue; } if ('active' === $column) { $query->where('accounts.active', $value); } - if('name' === $column) { + if ('name' === $column) { $query->where('accounts.name', 'LIKE', sprintf('%%%s%%', $value)); } } @@ -294,12 +294,11 @@ class AccountRepository implements AccountRepositoryInterface { // search by group, not by user $dbQuery = $this->userGroup->accounts() - ->where('active', true) - ->orderBy('accounts.order', 'ASC') - ->orderBy('accounts.account_type_id', 'ASC') - ->orderBy('accounts.name', 'ASC') - ->with(['accountType']) - ; + ->where('active', true) + ->orderBy('accounts.order', 'ASC') + ->orderBy('accounts.account_type_id', 'ASC') + ->orderBy('accounts.name', 'ASC') + ->with(['accountType']); if ('' !== $query) { // split query on spaces just in case: $parts = explode(' ', $query); @@ -340,18 +339,42 @@ class AccountRepository implements AccountRepositoryInterface public function getAccountTypes(Collection $accounts): Collection { return AccountType::leftJoin('accounts', 'accounts.account_type_id', '=', 'account_types.id') - ->whereIn('accounts.id', $accounts->pluck('id')->toArray()) - ->get(['accounts.id', 'account_types.type']) - ; + ->whereIn('accounts.id', $accounts->pluck('id')->toArray()) + ->get(['accounts.id', 'account_types.type']); } #[\Override] public function getLastActivity(Collection $accounts): array { return Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) - ->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id') - ->groupBy('transactions.account_id') - ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id') + ->groupBy('transactions.account_id') + ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line + ; + } + + #[\Override] public function getObjectGroups(Collection $accounts): array + { + $groupIds = []; + $return = []; + $set = DB::table('object_groupables')->where('object_groupable_type', Account::class) + ->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get(); + /** @var \stdClass $row */ + foreach ($set as $row) { + $groupIds[] = $row->object_group_id; + } + $groupIds = array_unique($groupIds); + $groups = ObjectGroup::whereIn('id', $groupIds)->get(); + /** @var \stdClass $row */ + foreach ($set as $row) { + if (!array_key_exists($row->object_groupable_id, $return)) { + /** @var ObjectGroup|null $group */ + $group = $groups->firstWhere('id', '=', $row->object_group_id); + if (null !== $group) { + $return[$row->object_groupable_id] = ['title' => $group->title, 'order' => $group->order, 'id' => $group->id]; + } + } + } + return $return; } } diff --git a/app/Repositories/UserGroups/Account/AccountRepositoryInterface.php b/app/Repositories/UserGroups/Account/AccountRepositoryInterface.php index 674843687f..5782b36742 100644 --- a/app/Repositories/UserGroups/Account/AccountRepositoryInterface.php +++ b/app/Repositories/UserGroups/Account/AccountRepositoryInterface.php @@ -69,6 +69,8 @@ interface AccountRepositoryInterface */ public function getMetaValue(Account $account, string $field): ?string; + public function getObjectGroups(Collection $accounts) : array; + public function getUserGroup(): UserGroup; /** diff --git a/app/Transformers/V2/AccountTransformer.php b/app/Transformers/V2/AccountTransformer.php index fbe210ebd0..70176693b0 100644 --- a/app/Transformers/V2/AccountTransformer.php +++ b/app/Transformers/V2/AccountTransformer.php @@ -47,6 +47,7 @@ class AccountTransformer extends AbstractTransformer private array $currencies; private TransactionCurrency $default; private array $lastActivity; + private array $objectGroups; /** * This method collects meta-data for one or all accounts in the transformer's collection. @@ -58,6 +59,7 @@ class AccountTransformer extends AbstractTransformer $this->accountTypes = []; $this->fullTypes = []; $this->lastActivity = []; + $this->objectGroups = []; $this->convertedBalances = []; $this->balanceDifferences = []; @@ -81,6 +83,9 @@ class AccountTransformer extends AbstractTransformer $this->getBalanceDifference($objects, $this->parameters->get('start'), $this->parameters->get('end')); } + // get object groups" + $this->getObjectGroups($objects); + return $this->sortAccounts($objects); } @@ -127,6 +132,11 @@ class AccountTransformer extends AbstractTransformer $order = null; } + // object group + $objectGroupId = $this->objectGroups[$id]['id'] ?? null; + $objectGroupOrder = $this->objectGroups[$id]['order'] ?? null; + $objectGroupTitle = $this->objectGroups[$id]['title'] ?? null; + // balance difference $diffStart = null; $diffEnd = null; @@ -181,6 +191,11 @@ class AccountTransformer extends AbstractTransformer 'interest_period' => $interestPeriod, 'current_debt' => $currentDebt, + // object group + 'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null, + 'object_group_order' => $objectGroupOrder, + 'object_group_title' => $objectGroupTitle, + // 'notes' => $this->repository->getNoteText($account), // 'monthly_payment_date' => $monthlyPaymentDate, // 'credit_card_type' => $creditCardType, @@ -394,4 +409,11 @@ class AccountTransformer extends AbstractTransformer return $rightCurrent <=> $leftCurrent; }); } + + private function getObjectGroups(Collection $accounts): void + { + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $this->objectGroups = $accountRepository->getObjectGroups($accounts); + } } diff --git a/resources/assets/v2/src/pages/accounts/index.js b/resources/assets/v2/src/pages/accounts/index.js index 3c925c1576..672a8c65b3 100644 --- a/resources/assets/v2/src/pages/accounts/index.js +++ b/resources/assets/v2/src/pages/accounts/index.js @@ -157,7 +157,11 @@ let index = function () { // hide modal window.bootstrap.Modal.getInstance(document.getElementById('filterModal')).hide(); this.loadAccounts(); - + }, + saveGroupedAccounts() { + setVariable(this.getPreferenceKey('grouped'), this.pageOptions.groupedAccounts); + this.page = 1; + this.loadAccounts(); }, removeFilter(field) { this.filters[field] = null; @@ -236,7 +240,6 @@ let index = function () { }) - // some opts this.pageOptions.isLoading = true; this.notifications.wait.show = true; @@ -249,6 +252,7 @@ let index = function () { {name: this.getPreferenceKey('sc'), default: ''}, {name: this.getPreferenceKey('sd'), default: ''}, {name: this.getPreferenceKey('filters'), default: this.filters}, + {name: this.getPreferenceKey('grouped'), default: true}, ]).then((res) => { // process columns: for (let k in res[0]) { @@ -270,6 +274,9 @@ let index = function () { } } + // group accounts + this.pageOptions.groupedAccounts = res[4]; + this.loadAccounts(); }); }, @@ -360,10 +367,8 @@ let index = function () { delete params.start; delete params.end; } - // check if cache is present: - this.accounts = []; - + let groupedAccounts = {}; // one page only.o (new Get()).index(params).then(response => { this.totalPages = response.meta.pagination.total_pages; @@ -392,12 +397,38 @@ let index = function () { interest_period: current.attributes.interest_period, current_debt: current.attributes.current_debt, }; - this.accounts.push(account); + + // get group info: + let groupId = current.attributes.object_group_id; + if(!this.pageOptions.groupedAccounts) { + groupId = '0'; + } + if (!groupedAccounts.hasOwnProperty(groupId)) { + groupedAccounts[groupId] = { + group: { + id: '0' === groupId || null === groupId ? null : parseInt(groupId), + title: current.attributes.object_group_title, // are ignored if group id is null. + order: current.attributes.object_group_order, + }, + accounts: [], + } + } + groupedAccounts[groupId].accounts.push(account); + + //this.accounts.push(account); } } + // order grouped accounts by order. + let sortable = []; + for (let set in groupedAccounts) { + sortable.push(groupedAccounts[set]); + } + sortable.sort(function(a, b) { + return a.group.order - b.group.order; + }); + this.accounts = sortable; this.notifications.wait.show = false; this.pageOptions.isLoading = false; - // add click trigger thing. }); }, } diff --git a/resources/views/v2/accounts/index.blade.php b/resources/views/v2/accounts/index.blade.php index 9ef64c775e..b12cf455d1 100644 --- a/resources/views/v2/accounts/index.blade.php +++ b/resources/views/v2/accounts/index.blade.php @@ -37,16 +37,20 @@
-
+
+