From c1d460cead4b33f0c9e13cbde6c9cd2b1694295a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 10 May 2022 21:24:36 +0200 Subject: [PATCH] Improve filtering (#901) --- apps/api/src/app/account/account.service.ts | 34 ++++++-- .../src/app/portfolio/portfolio.controller.ts | 2 +- .../src/app/portfolio/portfolio.service.ts | 81 ++++++++++++------- .../allocations/allocations-page.component.ts | 29 +++---- .../activities-filter.component.ts | 4 +- 5 files changed, 92 insertions(+), 58 deletions(-) diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index ec1678131..34c2f4a15 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -1,5 +1,6 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { Filter } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { Account, Order, Platform, Prisma } from '@prisma/client'; import Big from 'big.js'; @@ -102,22 +103,39 @@ export class AccountService { }); } - public async getCashDetails( - aUserId: string, - aCurrency: string - ): Promise { + public async getCashDetails({ + currency, + filters = [], + userId + }: { + currency: string; + filters?: Filter[]; + userId: string; + }): Promise { let totalCashBalanceInBaseCurrency = new Big(0); - const accounts = await this.accounts({ - where: { userId: aUserId } - }); + const where: Prisma.AccountWhereInput = { userId }; + + if (filters?.length > 0) { + where.id = { + in: filters + .filter(({ type }) => { + return type === 'account'; + }) + .map(({ id }) => { + return id; + }) + }; + } + + const accounts = await this.accounts({ where }); for (const account of accounts) { totalCashBalanceInBaseCurrency = totalCashBalanceInBaseCurrency.plus( this.exchangeRateDataService.toCurrency( account.balance, account.currency, - aCurrency + currency ) ); } diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 25021492e..68cd03c0a 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -182,8 +182,8 @@ export class PortfolioController { this.request.user.subscription.type === 'Basic'; return { + accounts, hasError, - accounts: filters.length === 0 ? accounts : {}, holdings: isBasicUser ? {} : holdings }; } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 314cedf60..3ecb60d41 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -68,7 +68,7 @@ import { subDays, subYears } from 'date-fns'; -import { isEmpty, sortBy, uniqBy } from 'lodash'; +import { isEmpty, sortBy, uniq, uniqBy } from 'lodash'; import { HistoricalDataContainer, @@ -344,10 +344,11 @@ export class PortfolioService { startDate ); - const cashDetails = await this.accountService.getCashDetails( + const cashDetails = await this.accountService.getCashDetails({ userId, - userCurrency - ); + currency: userCurrency, + filters: aFilters + }); const holdings: PortfolioDetails['holdings'] = {}; const totalInvestment = currentPositions.totalInvestment.plus( @@ -440,26 +441,26 @@ export class PortfolioService { }; } - const cashPositions = await this.getCashPositions({ - cashDetails, - emergencyFund, - userCurrency, - investment: totalInvestment, - value: totalValue - }); - if (aFilters?.length === 0) { + const cashPositions = await this.getCashPositions({ + cashDetails, + emergencyFund, + userCurrency, + investment: totalInvestment, + value: totalValue + }); + for (const symbol of Object.keys(cashPositions)) { holdings[symbol] = cashPositions[symbol]; } } - const accounts = await this.getValueOfAccounts( + const accounts = await this.getValueOfAccounts({ orders, + userId, portfolioItemsNow, - userCurrency, - userId - ); + filters: aFilters + }); return { accounts, holdings, hasErrors: currentPositions.hasErrors }; } @@ -890,12 +891,11 @@ export class PortfolioService { for (const position of currentPositions.positions) { portfolioItemsNow[position.symbol] = position; } - const accounts = await this.getValueOfAccounts( + const accounts = await this.getValueOfAccounts({ orders, portfolioItemsNow, - currency, userId - ); + }); return { rules: { accountClusterRisk: await this.rulesService.evaluate( @@ -957,10 +957,10 @@ export class PortfolioService { const performanceInformation = await this.getPerformance(aImpersonationId); - const { balanceInBaseCurrency } = await this.accountService.getCashDetails( + const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ userId, - userCurrency - ); + currency: userCurrency + }); const orders = await this.orderService.getOrders({ userCurrency, userId @@ -1253,21 +1253,40 @@ export class PortfolioService { portfolioCalculator.computeTransactionPoints(); return { - transactionPoints: portfolioCalculator.getTransactionPoints(), orders, - portfolioOrders + portfolioOrders, + transactionPoints: portfolioCalculator.getTransactionPoints() }; } - private async getValueOfAccounts( - orders: OrderWithAccount[], - portfolioItemsNow: { [p: string]: TimelinePosition }, - userCurrency: string, - userId: string - ) { + private async getValueOfAccounts({ + filters = [], + orders, + portfolioItemsNow, + userId + }: { + filters?: Filter[]; + orders: OrderWithAccount[]; + portfolioItemsNow: { [p: string]: TimelinePosition }; + userId: string; + }) { const accounts: PortfolioDetails['accounts'] = {}; - const currentAccounts = await this.accountService.getAccounts(userId); + let currentAccounts = []; + + if (filters.length === 0) { + currentAccounts = await this.accountService.getAccounts(userId); + } else { + const accountIds = uniq( + orders.map(({ accountId }) => { + return accountId; + }) + ); + + currentAccounts = await this.accountService.accounts({ + where: { id: { in: accountIds } } + }); + } for (const account of currentAccounts) { const ordersByAccount = orders.filter(({ accountId }) => { diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 01e9a7cef..b2c63664f 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -155,15 +155,17 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; - const accountFilters: Filter[] = this.user.accounts.map( - ({ id, name }) => { + const accountFilters: Filter[] = this.user.accounts + .filter(({ accountType }) => { + return accountType === 'SECURITIES'; + }) + .map(({ id, name }) => { return { - id: id, + id, label: name, type: 'account' }; - } - ); + }); const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => { return { @@ -347,17 +349,12 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { } } - if ( - this.activeFilters?.length === 0 || - position.assetSubClass !== AssetClass.CASH - ) { - this.symbols[prettifySymbol(symbol)] = { - dataSource: position.dataSource, - name: position.name, - symbol: prettifySymbol(symbol), - value: aPeriod === 'original' ? position.investment : position.value - }; - } + this.symbols[prettifySymbol(symbol)] = { + dataSource: position.dataSource, + name: position.name, + symbol: prettifySymbol(symbol), + value: aPeriod === 'original' ? position.investment : position.value + }; } const marketsTotal = diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts index b114317e5..bf8c5878b 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -48,7 +48,7 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy { public constructor() { this.searchControl.valueChanges .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((currentFilter: string) => { + .subscribe((currentFilter: Filter) => { if (currentFilter) { this.filters$.next( this.allFilters @@ -61,7 +61,7 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy { .filter((filter) => { return filter.label .toLowerCase() - .startsWith(currentFilter?.toLowerCase()); + .startsWith(currentFilter.label.toLowerCase()); }) .sort((a, b) => a.label.localeCompare(b.label)) );