diff --git a/CHANGELOG.md b/CHANGELOG.md index edde86609..ee8047827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Restructured the response of the portfolio report endpoint (_X-ray_) - Refactored the create or update access dialog component to standalone - Upgraded `envalid` from version `8.0.0` to `8.1.0` - Upgraded `prisma` from version `6.14.0` to `6.15.0` diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 748cdceb8..d703cf604 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -655,8 +655,8 @@ export class PortfolioController { this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.request.user.subscription.type === 'Basic' ) { - for (const rule in report.xRay.rules) { - report.xRay.rules[rule] = null; + for (const category of report.xRay.categories) { + category.rules = null; } report.xRay.statistics = { diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1d010bcf7..f5b4ab1c6 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -50,6 +50,7 @@ import { PortfolioPerformanceResponse, PortfolioPosition, PortfolioReportResponse, + PortfolioReportRule, PortfolioSummary, UserSettings } from '@ghostfolio/common/interfaces'; @@ -1231,176 +1232,236 @@ export class PortfolioService { }) ).toNumber(); - const rules: PortfolioReportResponse['xRay']['rules'] = { - accountClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new AccountClusterRiskCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - accounts - ), - new AccountClusterRiskSingleAccount( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - accounts - ) - ], - userSettings + const categories: PortfolioReportResponse['xRay']['categories'] = [ + { + key: 'liquidity', + name: this.i18nService.getTranslation({ + id: 'rule.liquidity.category', + languageCode: userSettings.language + }), + rules: await this.rulesService.evaluate( + [ + new BuyingPower( + this.exchangeRateDataService, + this.i18nService, + summary.cash, + userSettings.language ) - : undefined, - assetClassClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new AssetClassClusterRiskEquity( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - Object.values(holdings) - ), - new AssetClassClusterRiskFixedIncome( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - Object.values(holdings) - ) - ], - userSettings - ) - : undefined, - currencyClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new CurrencyClusterRiskBaseCurrencyCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - Object.values(holdings), - userSettings.language - ), - new CurrencyClusterRiskCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - Object.values(holdings), - userSettings.language - ) - ], - userSettings - ) - : undefined, - economicMarketClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new EconomicMarketClusterRiskDevelopedMarkets( - this.exchangeRateDataService, - this.i18nService, - marketsTotalInBaseCurrency, - markets.developedMarkets.valueInBaseCurrency, - userSettings.language - ), - new EconomicMarketClusterRiskEmergingMarkets( - this.exchangeRateDataService, - this.i18nService, - marketsTotalInBaseCurrency, - markets.emergingMarkets.valueInBaseCurrency, - userSettings.language - ) - ], - userSettings + ], + userSettings + ) + }, + { + key: 'emergencyFund', + name: this.i18nService.getTranslation({ + id: 'rule.emergencyFund.category', + languageCode: userSettings.language + }), + rules: await this.rulesService.evaluate( + [ + new EmergencyFundSetup( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + this.getTotalEmergencyFund({ + userSettings, + emergencyFundHoldingsValueInBaseCurrency: + this.getEmergencyFundHoldingsValueInBaseCurrency({ holdings }) + }).toNumber() ) - : undefined, - emergencyFund: await this.rulesService.evaluate( - [ - new EmergencyFundSetup( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - this.getTotalEmergencyFund({ - userSettings, - emergencyFundHoldingsValueInBaseCurrency: - this.getEmergencyFundHoldingsValueInBaseCurrency({ holdings }) - }).toNumber() - ) - ], - userSettings - ), - fees: await this.rulesService.evaluate( - [ - new FeeRatioInitialInvestment( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - summary.committedFunds, - summary.fees - ) - ], - userSettings - ), - liquidity: await this.rulesService.evaluate( - [ - new BuyingPower( - this.exchangeRateDataService, - this.i18nService, - summary.cash, - userSettings.language - ) - ], - userSettings - ), - regionalMarketClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new RegionalMarketClusterRiskAsiaPacific( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.asiaPacific.valueInBaseCurrency - ), - new RegionalMarketClusterRiskEmergingMarkets( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.emergingMarkets.valueInBaseCurrency - ), - new RegionalMarketClusterRiskEurope( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.europe.valueInBaseCurrency - ), - new RegionalMarketClusterRiskJapan( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.japan.valueInBaseCurrency - ), - new RegionalMarketClusterRiskNorthAmerica( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.northAmerica.valueInBaseCurrency - ) - ], - userSettings + ], + userSettings + ) + }, + { + key: 'currencyClusterRisk', + name: this.i18nService.getTranslation({ + id: 'rule.currencyClusterRisk.category', + languageCode: userSettings.language + }), + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new CurrencyClusterRiskBaseCurrencyCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + Object.values(holdings), + userSettings.language + ), + new CurrencyClusterRiskCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + Object.values(holdings), + userSettings.language + ) + ], + userSettings + ) + : undefined + }, + { + key: 'assetClassClusterRisk', + name: this.i18nService.getTranslation({ + id: 'rule.assetClassClusterRisk.category', + languageCode: userSettings.language + }), + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new AssetClassClusterRiskEquity( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + Object.values(holdings) + ), + new AssetClassClusterRiskFixedIncome( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + Object.values(holdings) + ) + ], + userSettings + ) + : undefined + }, + { + key: 'accountClusterRisk', + name: this.i18nService.getTranslation({ + id: 'rule.accountClusterRisk.category', + languageCode: userSettings.language + }), + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new AccountClusterRiskCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + accounts + ), + new AccountClusterRiskSingleAccount( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + accounts + ) + ], + userSettings + ) + : undefined + }, + { + key: 'economicMarketClusterRisk', + name: this.i18nService.getTranslation({ + id: 'rule.economicMarketClusterRisk.category', + languageCode: userSettings.language + }), + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new EconomicMarketClusterRiskDevelopedMarkets( + this.exchangeRateDataService, + this.i18nService, + marketsTotalInBaseCurrency, + markets.developedMarkets.valueInBaseCurrency, + userSettings.language + ), + new EconomicMarketClusterRiskEmergingMarkets( + this.exchangeRateDataService, + this.i18nService, + marketsTotalInBaseCurrency, + markets.emergingMarkets.valueInBaseCurrency, + userSettings.language + ) + ], + userSettings + ) + : undefined + }, + { + key: 'regionalMarketClusterRisk', + name: this.i18nService.getTranslation({ + id: 'rule.regionalMarketClusterRisk.category', + languageCode: userSettings.language + }), + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new RegionalMarketClusterRiskAsiaPacific( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.asiaPacific.valueInBaseCurrency + ), + new RegionalMarketClusterRiskEmergingMarkets( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.emergingMarkets.valueInBaseCurrency + ), + new RegionalMarketClusterRiskEurope( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.europe.valueInBaseCurrency + ), + new RegionalMarketClusterRiskJapan( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.japan.valueInBaseCurrency + ), + new RegionalMarketClusterRiskNorthAmerica( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.northAmerica.valueInBaseCurrency + ) + ], + userSettings + ) + : undefined + }, + { + key: 'fees', + name: this.i18nService.getTranslation({ + id: 'rule.fees.category', + languageCode: userSettings.language + }), + rules: await this.rulesService.evaluate( + [ + new FeeRatioInitialInvestment( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + summary.committedFunds, + summary.fees ) - : undefined - }; + ], + userSettings + ) + } + ]; return { xRay: { - rules, - statistics: this.getReportStatistics(rules) + categories, + statistics: this.getReportStatistics( + categories.flatMap(({ rules }) => { + return rules ?? []; + }) + ) } }; } @@ -1822,7 +1883,7 @@ export class PortfolioService { } private getReportStatistics( - evaluatedRules: PortfolioReportResponse['xRay']['rules'] + evaluatedRules: PortfolioReportRule[] ): PortfolioReportResponse['xRay']['statistics'] { const rulesActiveCount = Object.values(evaluatedRules) .flat() diff --git a/apps/api/src/app/portfolio/rules.service.ts b/apps/api/src/app/portfolio/rules.service.ts index 7f6d964f5..48d1658aa 100644 --- a/apps/api/src/app/portfolio/rules.service.ts +++ b/apps/api/src/app/portfolio/rules.service.ts @@ -22,7 +22,6 @@ export class RulesService { return { evaluation, value, - categoryName: rule.getCategoryName(), configuration: rule.getConfiguration(), isActive: true, key: rule.getKey(), @@ -30,7 +29,6 @@ export class RulesService { }; } else { return { - categoryName: rule.getCategoryName(), isActive: false, key: rule.getKey(), name: rule.getName() diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts index 6fcbd2d38..9811a6564 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts @@ -4,6 +4,7 @@ import { } from '@ghostfolio/common/interfaces'; export interface IRuleSettingsDialogParams { + categoryName: string; rule: PortfolioReportRule; settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; } diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html index f5903e6d5..9fdc0cd57 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -1,4 +1,4 @@ -