From d2fabe7ce49c310ba5b90457e921e7814e2b6b64 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 13 Nov 2021 20:38:29 +0100 Subject: [PATCH] Feature/add value column to accounts table (#468) * Add value column * Update changelog --- CHANGELOG.md | 1 + .../api/src/app/account/account.controller.ts | 15 +++-- apps/api/src/app/account/account.module.ts | 6 +- .../api/src/app/portfolio/portfolio.module.ts | 1 + .../src/app/portfolio/portfolio.service.ts | 62 +++++++++++++------ .../accounts-table.component.html | 36 ++++++++++- .../accounts-table.component.ts | 9 ++- apps/client/src/app/services/data.service.ts | 4 +- .../src/lib/types/account-with-value.type.ts | 6 ++ libs/common/src/lib/types/index.ts | 2 + 10 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 libs/common/src/lib/types/account-with-value.type.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c23a299..e5f13c3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a logo to the log on the server start - Added the data gathering progress to the log and the admin control panel +- Added the value column to the accounts table ## 1.74.0 - 11.11.2021 diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index caae33645..d6a4ff29e 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -1,3 +1,4 @@ +import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; @@ -6,7 +7,10 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import type { RequestWithUser } from '@ghostfolio/common/types'; +import type { + AccountWithValue, + RequestWithUser +} from '@ghostfolio/common/types'; import { Body, Controller, @@ -34,6 +38,7 @@ export class AccountController { public constructor( private readonly accountService: AccountService, private readonly impersonationService: ImpersonationService, + private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly userService: UserService ) {} @@ -85,14 +90,14 @@ export class AccountController { @UseGuards(AuthGuard('jwt')) public async getAllAccounts( @Headers('impersonation-id') impersonationId - ): Promise { + ): Promise { const impersonationUserId = await this.impersonationService.validateImpersonationId( impersonationId, this.request.user.id ); - let accounts = await this.accountService.getAccounts( + let accounts = await this.portfolioService.getAccounts( impersonationUserId || this.request.user.id ); @@ -102,9 +107,11 @@ export class AccountController { ) { accounts = nullifyValuesInObjects(accounts, [ 'balance', + 'convertedBalance', 'fee', 'quantity', - 'unitPrice' + 'unitPrice', + 'value' ]); } diff --git a/apps/api/src/app/account/account.module.ts b/apps/api/src/app/account/account.module.ts index 0b7c28728..2c11de472 100644 --- a/apps/api/src/app/account/account.module.ts +++ b/apps/api/src/app/account/account.module.ts @@ -1,3 +1,4 @@ +import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; @@ -11,16 +12,17 @@ import { AccountController } from './account.controller'; import { AccountService } from './account.service'; @Module({ + controllers: [AccountController], imports: [ ConfigurationModule, DataProviderModule, ExchangeRateDataModule, ImpersonationModule, - RedisCacheModule, + PortfolioModule, PrismaModule, + RedisCacheModule, UserModule ], - controllers: [AccountController], providers: [AccountService] }) export class AccountModule {} diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 2af37562c..34aaeb99b 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -18,6 +18,7 @@ import { PortfolioService } from './portfolio.service'; import { RulesService } from './rules.service'; @Module({ + exports: [PortfolioService], imports: [ AccessModule, ConfigurationModule, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index eebab24cc..6fbcf9a7b 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -37,6 +37,7 @@ import { } from '@ghostfolio/common/interfaces'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import type { + AccountWithValue, DateRange, OrderWithAccount, RequestWithUser @@ -79,6 +80,36 @@ export class PortfolioService { private readonly symbolProfileService: SymbolProfileService ) {} + public async getAccounts(aUserId: string): Promise { + const [accounts, details] = await Promise.all([ + this.accountService.accounts({ + include: { Order: true, Platform: true }, + orderBy: { name: 'asc' }, + where: { userId: aUserId } + }), + this.getDetails(aUserId, aUserId) + ]); + + const userCurrency = this.request.user.Settings.currency; + + return accounts.map((account) => { + const result = { + ...account, + convertedBalance: this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + userCurrency + ), + transactionCount: account.Order.length, + value: details.accounts[account.name].current + }; + + delete result.Order; + + return result; + }); + } + public async getInvestments( aImpersonationId: string ): Promise { @@ -256,7 +287,7 @@ export class PortfolioService { value: totalValue }); - const accounts = await this.getAccounts( + const accounts = await this.getValueOfAccounts( orders, portfolioItemsNow, userCurrency, @@ -617,7 +648,7 @@ export class PortfolioService { currentGrossPerformancePercent, currentNetPerformance, currentNetPerformancePercent, - currentValue: currentValue + currentValue } }; } @@ -667,7 +698,7 @@ export class PortfolioService { for (const position of currentPositions.positions) { portfolioItemsNow[position.symbol] = position; } - const accounts = await this.getAccounts( + const accounts = await this.getValueOfAccounts( orders, portfolioItemsNow, currency, @@ -867,7 +898,7 @@ export class PortfolioService { }; } - private async getAccounts( + private async getValueOfAccounts( orders: OrderWithAccount[], portfolioItemsNow: { [p: string]: TimelinePosition }, userCurrency: string, @@ -882,20 +913,15 @@ export class PortfolioService { return accountId === account.id; }); - if (ordersByAccount.length <= 0) { - // Add account without orders - const balance = this.exchangeRateDataService.toCurrency( - account.balance, - account.currency, - userCurrency - ); - accounts[account.name] = { - current: balance, - original: balance - }; - - continue; - } + const convertedBalance = this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + userCurrency + ); + accounts[account.name] = { + current: convertedBalance, + original: convertedBalance + }; for (const order of ordersByAccount) { let currentValueOfSymbol = this.exchangeRateDataService.toCurrency( diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.html b/apps/client/src/app/components/accounts-table/accounts-table.component.html index c38bb4073..d3c010601 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.html +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.html @@ -17,6 +17,20 @@ + + + Currency + + + {{ element.currency }} + + + Transactions - {{ element.transactionCount }} + {{ + element.transactionCount + }} @@ -56,9 +72,23 @@ + + + + + + Value + + + diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.ts b/apps/client/src/app/components/accounts-table/accounts-table.component.ts index 8a0982304..8a0480537 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.ts +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.ts @@ -41,7 +41,14 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit { public ngOnInit() {} public ngOnChanges() { - this.displayedColumns = ['account', 'platform', 'transactions', 'balance']; + this.displayedColumns = [ + 'account', + 'currency', + 'platform', + 'transactions', + 'balance', + 'value' + ]; if (this.showActions) { this.displayedColumns.push('actions'); diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 43ea50d9e..890c75c38 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -29,7 +29,7 @@ import { } from '@ghostfolio/common/interfaces'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { permissions } from '@ghostfolio/common/permissions'; -import { DateRange } from '@ghostfolio/common/types'; +import { AccountWithValue, DateRange } from '@ghostfolio/common/types'; import { Account as AccountModel, DataSource, @@ -62,7 +62,7 @@ export class DataService { } public fetchAccounts() { - return this.http.get('/api/account'); + return this.http.get('/api/account'); } public fetchAdminData() { diff --git a/libs/common/src/lib/types/account-with-value.type.ts b/libs/common/src/lib/types/account-with-value.type.ts new file mode 100644 index 000000000..3c6ac173a --- /dev/null +++ b/libs/common/src/lib/types/account-with-value.type.ts @@ -0,0 +1,6 @@ +import { Account as AccountModel } from '@prisma/client'; + +export type AccountWithValue = AccountModel & { + convertedBalance: number; + value: number; +}; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index f2728ebcc..cd9408b5d 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -1,4 +1,5 @@ import type { AccessWithGranteeUser } from './access-with-grantee-user.type'; +import { AccountWithValue } from './account-with-value.type'; import type { DateRange } from './date-range.type'; import type { Granularity } from './granularity.type'; import type { OrderWithAccount } from './order-with-account.type'; @@ -6,6 +7,7 @@ import type { RequestWithUser } from './request-with-user.type'; export type { AccessWithGranteeUser, + AccountWithValue, DateRange, Granularity, OrderWithAccount,