diff --git a/CHANGELOG.md b/CHANGELOG.md index 1257131cb..b10f4d29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a dialog for the benchmarks in the markets overview +- Extended the asset profile details dialog of the admin control for currencies - Extended the content of the _Self-Hosting_ section by the mobile app question on the Frequently Asked Questions (FAQ) page ### Changed diff --git a/apps/api/src/app/admin/admin.module.ts b/apps/api/src/app/admin/admin.module.ts index 82d03395b..de9aecf3b 100644 --- a/apps/api/src/app/admin/admin.module.ts +++ b/apps/api/src/app/admin/admin.module.ts @@ -1,3 +1,4 @@ +import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; @@ -24,6 +25,7 @@ import { QueueModule } from './queue/queue.module'; DataProviderModule, ExchangeRateDataModule, MarketDataModule, + OrderModule, PrismaModule, PropertyModule, QueueModule, diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index c061c996c..21f0253ee 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -1,3 +1,4 @@ +import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -13,11 +14,13 @@ import { PROPERTY_IS_READ_ONLY_MODE, PROPERTY_IS_USER_SIGNUP_ENABLED } from '@ghostfolio/common/config'; +import { isCurrency, getCurrencyFromSymbol } from '@ghostfolio/common/helper'; import { AdminData, AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + EnhancedSymbolProfile, Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; @@ -42,6 +45,7 @@ export class AdminService { private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, + private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly subscriptionService: SubscriptionService, @@ -295,6 +299,16 @@ export class AdminService { dataSource, symbol }: UniqueAsset): Promise { + let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0; + let currency: EnhancedSymbolProfile['currency'] = '-'; + let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity']; + + if (isCurrency(getCurrencyFromSymbol(symbol))) { + currency = getCurrencyFromSymbol(symbol); + ({ activitiesCount, dateOfFirstActivity } = + await this.orderService.getStatisticsByCurrency(currency)); + } + const [[assetProfile], marketData] = await Promise.all([ this.symbolProfileService.getSymbolProfiles([ { @@ -322,8 +336,11 @@ export class AdminService { return { marketData, assetProfile: assetProfile ?? { - symbol, - currency: '-' + activitiesCount, + currency, + dataSource, + dateOfFirstActivity, + symbol } }; } @@ -413,19 +430,15 @@ export class AdminService { this.exchangeRateDataService .getCurrencyPairs() .map(async ({ dataSource, symbol }) => { - const currency = symbol.replace(DEFAULT_CURRENCY, ''); - - const { _count, _min } = await this.prismaService.order.aggregate({ - _count: true, - _min: { - date: true - }, - where: { - SymbolProfile: { - currency - } - } - }); + let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0; + let currency: EnhancedSymbolProfile['currency'] = '-'; + let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity']; + + if (isCurrency(getCurrencyFromSymbol(symbol))) { + currency = getCurrencyFromSymbol(symbol); + ({ activitiesCount, dateOfFirstActivity } = + await this.orderService.getStatisticsByCurrency(currency)); + } const marketDataItemCount = marketDataItems.find((marketDataItem) => { @@ -436,15 +449,15 @@ export class AdminService { })?._count ?? 0; return { + activitiesCount, currency, dataSource, marketDataItemCount, symbol, - activitiesCount: _count as number, assetClass: AssetClass.LIQUIDITY, assetSubClass: AssetSubClass.CASH, countriesCount: 0, - date: _min.date, + date: dateOfFirstActivity, id: undefined, name: symbol, sectorsCount: 0 diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 4174a9f8d..7a4dd5a4a 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -10,7 +10,11 @@ import { GATHER_ASSET_PROFILE_PROCESS_OPTIONS } from '@ghostfolio/common/config'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; -import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + EnhancedSymbolProfile, + Filter, + UniqueAsset +} from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; @@ -429,6 +433,26 @@ export class OrderService { return { activities, count }; } + public async getStatisticsByCurrency( + currency: EnhancedSymbolProfile['currency'] + ): Promise<{ + activitiesCount: EnhancedSymbolProfile['activitiesCount']; + dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity']; + }> { + const { _count, _min } = await this.prismaService.order.aggregate({ + _count: true, + _min: { + date: true + }, + where: { SymbolProfile: { currency } } + }); + + return { + activitiesCount: _count as number, + dateOfFirstActivity: _min.date + }; + } + public async order( orderWhereUniqueInput: Prisma.OrderWhereUniqueInput ): Promise { diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index af71477b4..2e0de0e65 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -146,7 +146,7 @@ i18n size="medium" [locale]="data.locale" - [value]="assetProfile?.activitiesCount ?? 0" + [value]="assetProfile?.activitiesCount" >Activities diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index e03ea1a1f..f01fec772 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -13,7 +13,12 @@ import { } from 'date-fns'; import { de, es, fr, it, nl, pl, pt, tr, zhCN } from 'date-fns/locale'; -import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; +import { + DEFAULT_CURRENCY, + DERIVED_CURRENCIES, + ghostfolioScraperApiSymbolPrefix, + locale +} from './config'; import { Benchmark, UniqueAsset } from './interfaces'; import { BenchmarkTrend, ColorScheme } from './types'; @@ -161,6 +166,10 @@ export function getCssVariable(aCssVariable: string) { ); } +export function getCurrencyFromSymbol(aSymbol = '') { + return aSymbol.replace(DEFAULT_CURRENCY, ''); +} + export function getDateFnsLocale(aLanguageCode: string) { if (aLanguageCode === 'de') { return de; @@ -322,8 +331,18 @@ export function interpolate(template: string, context: any) { }); } -export function isCurrency(aSymbol = '') { - return currencies[aSymbol]; +export function isCurrency(aCurrency = '') { + return currencies[aCurrency] || isDerivedCurrency(aCurrency); +} + +export function isDerivedCurrency(aCurrency: string) { + if (aCurrency === 'USX') { + return true; + } + + return DERIVED_CURRENCIES.find(({ currency }) => { + return currency === aCurrency; + }); } export function parseDate(date: string): Date | null {