From 7b6893b5edb56e8eaedcb22e834587540daec47f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 12 Mar 2022 13:44:47 +0100 Subject: [PATCH] Feature/add support for emergency fund (#749) * Add support for emergency fund * Update changelog --- CHANGELOG.md | 1 + .../src/app/portfolio/portfolio.controller.ts | 1 + .../app/portfolio/portfolio.service-new.ts | 51 +++++++++++++++++-- .../src/app/portfolio/portfolio.service.ts | 51 +++++++++++++++++-- .../interfaces/user-settings.interface.ts | 2 + .../src/app/user/update-user-setting.dto.ts | 6 ++- apps/api/src/app/user/user.controller.ts | 1 - apps/api/src/app/user/user.service.ts | 2 +- .../home-summary/home-summary.component.ts | 26 ++++++++++ .../components/home-summary/home-summary.html | 2 + .../portfolio-summary.component.html | 20 ++++++++ .../portfolio-summary.component.ts | 19 ++++++- .../portfolio-summary.module.ts | 4 +- .../positions-table.component.ts | 6 ++- libs/common/src/lib/config.ts | 2 + .../portfolio-position.interface.ts | 2 +- .../interfaces/portfolio-summary.interface.ts | 1 + .../portfolio-proportion-chart.component.ts | 2 +- 18 files changed, 182 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b9a9251..13f3cf93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added support for an emergency fund - Added the contexts to the logger commands ### Changed diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index fd11334d9..2d7993859 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -334,6 +334,7 @@ export class PortfolioController { 'currentNetPerformance', 'currentValue', 'dividend', + 'emergencyFund', 'fees', 'items', 'netWorth', diff --git a/apps/api/src/app/portfolio/portfolio.service-new.ts b/apps/api/src/app/portfolio/portfolio.service-new.ts index b34a9206f..f11909b85 100644 --- a/apps/api/src/app/portfolio/portfolio.service-new.ts +++ b/apps/api/src/app/portfolio/portfolio.service-new.ts @@ -5,6 +5,8 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; +import { UserSettings } from '@ghostfolio/api/app/user/interfaces/user-settings.interface'; +import { UserService } from '@ghostfolio/api/app/user/user.service'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; @@ -19,7 +21,11 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; -import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config'; +import { + ASSET_SUB_CLASS_EMERGENCY_FUND, + UNKNOWN_KEY, + baseCurrency +} from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { Accounts, @@ -76,7 +82,8 @@ export class PortfolioServiceNew { private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly rulesService: RulesService, - private readonly symbolProfileService: SymbolProfileService + private readonly symbolProfileService: SymbolProfileService, + private readonly userService: UserService ) {} public async getAccounts(aUserId: string): Promise { @@ -295,7 +302,11 @@ export class PortfolioServiceNew { aDateRange: DateRange = 'max' ): Promise { const userId = await this.getUserId(aImpersonationId, aUserId); + const user = await this.userService.user({ id: userId }); + const emergencyFund = new Big( + (user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 + ); const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency; const { orders, portfolioOrders, transactionPoints } = @@ -393,6 +404,7 @@ export class PortfolioServiceNew { const cashPositions = await this.getCashPositions({ cashDetails, + emergencyFund, userCurrency, investment: totalInvestment, value: totalValue @@ -883,6 +895,7 @@ export class PortfolioServiceNew { public async getSummary(aImpersonationId: string): Promise { const userCurrency = this.request.user.Settings.currency; const userId = await this.getUserId(aImpersonationId, this.request.user.id); + const user = await this.userService.user({ id: userId }); const performanceInformation = await this.getPerformance(aImpersonationId); @@ -895,6 +908,9 @@ export class PortfolioServiceNew { userId }); const dividend = this.getDividend(orders).toNumber(); + const emergencyFund = new Big( + (user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 + ); const fees = this.getFees(orders).toNumber(); const firstOrderDate = orders[0]?.date; const items = this.getItems(orders).toNumber(); @@ -902,6 +918,7 @@ export class PortfolioServiceNew { const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY'); const totalSell = this.getTotalByType(orders, userCurrency, 'SELL'); + const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber(); const committedFunds = new Big(totalBuy).minus(totalSell); const netWorth = new Big(balanceInBaseCurrency) @@ -927,6 +944,7 @@ export class PortfolioServiceNew { return { ...performanceInformation.performance, annualizedPerformancePercent, + cash, dividend, fees, firstOrderDate, @@ -934,8 +952,8 @@ export class PortfolioServiceNew { netWorth, totalBuy, totalSell, - cash: balanceInBaseCurrency, committedFunds: committedFunds.toNumber(), + emergencyFund: emergencyFund.toNumber(), ordersCount: orders.filter((order) => { return order.type === 'BUY' || order.type === 'SELL'; }).length @@ -944,16 +962,18 @@ export class PortfolioServiceNew { private async getCashPositions({ cashDetails, + emergencyFund, investment, userCurrency, value }: { cashDetails: CashDetails; + emergencyFund: Big; investment: Big; value: Big; userCurrency: string; }) { - const cashPositions = {}; + const cashPositions: PortfolioDetails['holdings'] = {}; for (const account of cashDetails.accounts) { const convertedBalance = this.exchangeRateDataService.toCurrency( @@ -977,6 +997,7 @@ export class PortfolioServiceNew { assetSubClass: AssetClass.CASH, countries: [], currency: account.currency, + dataSource: undefined, grossPerformance: 0, grossPerformancePercent: 0, investment: convertedBalance, @@ -994,6 +1015,28 @@ export class PortfolioServiceNew { } } + if (emergencyFund.gt(0)) { + cashPositions[ASSET_SUB_CLASS_EMERGENCY_FUND] = { + ...cashPositions[userCurrency], + assetSubClass: ASSET_SUB_CLASS_EMERGENCY_FUND, + investment: emergencyFund.toNumber(), + name: ASSET_SUB_CLASS_EMERGENCY_FUND, + symbol: ASSET_SUB_CLASS_EMERGENCY_FUND, + value: emergencyFund.toNumber() + }; + + cashPositions[userCurrency].investment = new Big( + cashPositions[userCurrency].investment + ) + .minus(emergencyFund) + .toNumber(); + cashPositions[userCurrency].value = new Big( + cashPositions[userCurrency].value + ) + .minus(emergencyFund) + .toNumber(); + } + for (const symbol of Object.keys(cashPositions)) { // Calculate allocations for each currency cashPositions[symbol].allocationCurrent = new Big( diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index f3df18c30..f7902a2d7 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -6,6 +6,8 @@ import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfol import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/portfolio-calculator'; +import { UserSettings } from '@ghostfolio/api/app/user/interfaces/user-settings.interface'; +import { UserService } from '@ghostfolio/api/app/user/user.service'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; @@ -20,7 +22,11 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; -import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config'; +import { + ASSET_SUB_CLASS_EMERGENCY_FUND, + UNKNOWN_KEY, + baseCurrency +} from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { Accounts, @@ -75,7 +81,8 @@ export class PortfolioService { private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly rulesService: RulesService, - private readonly symbolProfileService: SymbolProfileService + private readonly symbolProfileService: SymbolProfileService, + private readonly userService: UserService ) {} public async getAccounts(aUserId: string): Promise { @@ -286,7 +293,11 @@ export class PortfolioService { aDateRange: DateRange = 'max' ): Promise { const userId = await this.getUserId(aImpersonationId, aUserId); + const user = await this.userService.user({ id: userId }); + const emergencyFund = new Big( + (user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 + ); const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency; const portfolioCalculator = new PortfolioCalculator( this.currentRateService, @@ -381,6 +392,7 @@ export class PortfolioService { const cashPositions = await this.getCashPositions({ cashDetails, + emergencyFund, userCurrency, investment: totalInvestment, value: totalValue @@ -861,6 +873,7 @@ export class PortfolioService { public async getSummary(aImpersonationId: string): Promise { const userCurrency = this.request.user.Settings.currency; const userId = await this.getUserId(aImpersonationId, this.request.user.id); + const user = await this.userService.user({ id: userId }); const performanceInformation = await this.getPerformance(aImpersonationId); @@ -873,6 +886,9 @@ export class PortfolioService { userId }); const dividend = this.getDividend(orders).toNumber(); + const emergencyFund = new Big( + (user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 + ); const fees = this.getFees(orders).toNumber(); const firstOrderDate = orders[0]?.date; const items = this.getItems(orders).toNumber(); @@ -880,6 +896,7 @@ export class PortfolioService { const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY'); const totalSell = this.getTotalByType(orders, userCurrency, 'SELL'); + const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber(); const committedFunds = new Big(totalBuy).minus(totalSell); const netWorth = new Big(balanceInBaseCurrency) @@ -889,6 +906,7 @@ export class PortfolioService { return { ...performanceInformation.performance, + cash, dividend, fees, firstOrderDate, @@ -898,8 +916,8 @@ export class PortfolioService { totalSell, annualizedPerformancePercent: performanceInformation.performance.annualizedPerformancePercent, - cash: balanceInBaseCurrency, committedFunds: committedFunds.toNumber(), + emergencyFund: emergencyFund.toNumber(), ordersCount: orders.filter((order) => { return order.type === 'BUY' || order.type === 'SELL'; }).length @@ -908,16 +926,18 @@ export class PortfolioService { private async getCashPositions({ cashDetails, + emergencyFund, investment, userCurrency, value }: { cashDetails: CashDetails; + emergencyFund: Big; investment: Big; userCurrency: string; value: Big; }) { - const cashPositions = {}; + const cashPositions: PortfolioDetails['holdings'] = {}; for (const account of cashDetails.accounts) { const convertedBalance = this.exchangeRateDataService.toCurrency( @@ -941,6 +961,7 @@ export class PortfolioService { assetSubClass: AssetClass.CASH, countries: [], currency: account.currency, + dataSource: undefined, grossPerformance: 0, grossPerformancePercent: 0, investment: convertedBalance, @@ -958,6 +979,28 @@ export class PortfolioService { } } + if (emergencyFund.gt(0)) { + cashPositions[ASSET_SUB_CLASS_EMERGENCY_FUND] = { + ...cashPositions[userCurrency], + assetSubClass: ASSET_SUB_CLASS_EMERGENCY_FUND, + investment: emergencyFund.toNumber(), + name: ASSET_SUB_CLASS_EMERGENCY_FUND, + symbol: ASSET_SUB_CLASS_EMERGENCY_FUND, + value: emergencyFund.toNumber() + }; + + cashPositions[userCurrency].investment = new Big( + cashPositions[userCurrency].investment + ) + .minus(emergencyFund) + .toNumber(); + cashPositions[userCurrency].value = new Big( + cashPositions[userCurrency].value + ) + .minus(emergencyFund) + .toNumber(); + } + for (const symbol of Object.keys(cashPositions)) { // Calculate allocations for each currency cashPositions[symbol].allocationCurrent = new Big( diff --git a/apps/api/src/app/user/interfaces/user-settings.interface.ts b/apps/api/src/app/user/interfaces/user-settings.interface.ts index 7870abee9..fb4b2af35 100644 --- a/apps/api/src/app/user/interfaces/user-settings.interface.ts +++ b/apps/api/src/app/user/interfaces/user-settings.interface.ts @@ -1,3 +1,5 @@ export interface UserSettings { + emergencyFund?: number; + isNewCalculationEngine?: boolean; isRestrictedView?: boolean; } diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 38dc9cafb..5af2f5f8d 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -1,6 +1,10 @@ -import { IsBoolean, IsOptional } from 'class-validator'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; export class UpdateUserSettingDto { + @IsNumber() + @IsOptional() + emergencyFund?: number; + @IsBoolean() @IsOptional() isNewCalculationEngine?: boolean; diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index a615d2f2b..97cf25b6e 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -23,7 +23,6 @@ import { import { REQUEST } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt'; import { AuthGuard } from '@nestjs/passport'; -import { Provider, Role } from '@prisma/client'; import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 5a21e9303..43b3b52c7 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -15,7 +15,7 @@ import { } from '@ghostfolio/common/permissions'; import { SubscriptionType } from '@ghostfolio/common/types/subscription.type'; import { Injectable } from '@nestjs/common'; -import { Prisma, Provider, Role, User, ViewMode } from '@prisma/client'; +import { Prisma, Role, User, ViewMode } from '@prisma/client'; import { UserSettingsParams } from './interfaces/user-settings-params.interface'; import { UserSettings } from './interfaces/user-settings.interface'; diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index b2d8d91f4..09bcb0454 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -1,7 +1,9 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { PortfolioSummary, User } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -11,6 +13,8 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './home-summary.html' }) export class HomeSummaryComponent implements OnDestroy, OnInit { + public hasImpersonationId: boolean; + public hasPermissionToUpdateUserSettings: boolean; public isLoading = true; public summary: PortfolioSummary; public user: User; @@ -23,6 +27,7 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private impersonationStorageService: ImpersonationStorageService, private userService: UserService ) { this.userService.stateChanged @@ -31,6 +36,11 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + this.hasPermissionToUpdateUserSettings = hasPermission( + this.user.permissions, + permissions.updateUserSettings + ); + this.changeDetectorRef.markForCheck(); } }); @@ -40,9 +50,25 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { + this.impersonationStorageService + .onChangeHasImpersonation() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((aId) => { + this.hasImpersonationId = !!aId; + }); + this.update(); } + public onChangeEmergencyFund(emergencyFund: number) { + this.dataService + .putUserSetting({ emergencyFund }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.update(); + }); + } + public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); diff --git a/apps/client/src/app/components/home-summary/home-summary.html b/apps/client/src/app/components/home-summary/home-summary.html index fe0b99a2b..7905130bd 100644 --- a/apps/client/src/app/components/home-summary/home-summary.html +++ b/apps/client/src/app/components/home-summary/home-summary.html @@ -8,9 +8,11 @@ diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html index 1fc5e1f85..5335b77a7 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html @@ -130,6 +130,26 @@ > +
+
Emergency Fund
+
+ + +
+
Cash (Buying Power)
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts index 77debbb0f..d8cda52f4 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, Component, + EventEmitter, Input, OnChanges, - OnInit + OnInit, + Output } from '@angular/core'; import { PortfolioSummary } from '@ghostfolio/common/interfaces'; import { formatDistanceToNow } from 'date-fns'; @@ -16,10 +18,13 @@ import { formatDistanceToNow } from 'date-fns'; }) export class PortfolioSummaryComponent implements OnChanges, OnInit { @Input() baseCurrency: string; + @Input() hasPermissionToUpdateUserSettings: boolean; @Input() isLoading: boolean; @Input() locale: string; @Input() summary: PortfolioSummary; + @Output() emergencyFundChanged = new EventEmitter(); + public timeInMarket: string; public constructor() {} @@ -37,4 +42,16 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit { this.timeInMarket = undefined; } } + + public onEditEmergencyFund() { + const emergencyFundInput = prompt( + 'Please enter the amount of your emergency fund:', + this.summary.emergencyFund.toString() + ); + const emergencyFund = parseFloat(emergencyFundInput?.trim()); + + if (emergencyFund >= 0) { + this.emergencyFundChanged.emit(emergencyFund); + } + } } diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.module.ts b/apps/client/src/app/components/portfolio-summary/portfolio-summary.module.ts index 306de7f06..1724aa8c5 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.module.ts +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.module.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { GfValueModule } from '@ghostfolio/ui/value'; import { PortfolioSummaryComponent } from './portfolio-summary.component'; @@ -8,6 +8,6 @@ import { PortfolioSummaryComponent } from './portfolio-summary.component'; declarations: [PortfolioSummaryComponent], exports: [PortfolioSummaryComponent], imports: [CommonModule, GfValueModule], - providers: [] + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GfPortfolioSummaryModule {} diff --git a/apps/client/src/app/components/positions-table/positions-table.component.ts b/apps/client/src/app/components/positions-table/positions-table.component.ts index 9dadb27df..ec40d9bf3 100644 --- a/apps/client/src/app/components/positions-table/positions-table.component.ts +++ b/apps/client/src/app/components/positions-table/positions-table.component.ts @@ -13,6 +13,7 @@ import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; +import { ASSET_SUB_CLASS_EMERGENCY_FUND } from '@ghostfolio/common/config'; import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; import { AssetClass, Order as OrderModel } from '@prisma/client'; import { Subject, Subscription } from 'rxjs'; @@ -39,7 +40,10 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit { public dataSource: MatTableDataSource = new MatTableDataSource(); public displayedColumns = []; - public ignoreAssetSubClasses = [AssetClass.CASH.toString()]; + public ignoreAssetSubClasses = [ + AssetClass.CASH.toString(), + ASSET_SUB_CLASS_EMERGENCY_FUND + ]; public isLoading = true; public pageSize = 7; public routeQueryParams: Subscription; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index a04736620..535fa2ef3 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -42,6 +42,8 @@ export const warnColorRgb = { b: 69 }; +export const ASSET_SUB_CLASS_EMERGENCY_FUND = 'EMERGENCY_FUND'; + export const DEFAULT_DATE_FORMAT = 'dd.MM.yyyy'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index 1145d98ff..87ee917d4 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -8,7 +8,7 @@ export interface PortfolioPosition { allocationCurrent: number; allocationInvestment: number; assetClass?: AssetClass; - assetSubClass?: AssetSubClass | 'CASH'; + assetSubClass?: AssetSubClass | 'CASH' | 'EMERGENCY_FUND'; countries: Country[]; currency: string; dataSource: DataSource; diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 7ffec5d4d..b692e9ba9 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -5,6 +5,7 @@ export interface PortfolioSummary extends PortfolioPerformance { cash: number; dividend: number; committedFunds: number; + emergencyFund: number; fees: number; firstOrderDate: Date; items: number; diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 4fe51497d..9fee13d0e 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -122,7 +122,7 @@ export class PortfolioProportionChartComponent chartData[this.positions[symbol][this.keys[0]]] = { name: this.positions[symbol].name, subCategory: {}, - value: new Big(this.positions[symbol].value) + value: new Big(this.positions[symbol].value ?? 0) }; if (this.positions[symbol][this.keys[1]]) {