Browse Source

Feature/extend asset profile for currency (#3495)

* Extend asset profile for currency

* Update changelog
pull/3508/head
Eduardo Marinho 4 months ago
committed by GitHub
parent
commit
ff121243e4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 2
      apps/api/src/app/admin/admin.module.ts
  3. 43
      apps/api/src/app/admin/admin.service.ts
  4. 26
      apps/api/src/app/order/order.service.ts
  5. 2
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  6. 25
      libs/common/src/lib/helper.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added a dialog for the benchmarks in the markets overview - 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 - Extended the content of the _Self-Hosting_ section by the mobile app question on the Frequently Asked Questions (FAQ) page
### Changed ### Changed

2
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 { 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 { 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'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
@ -24,6 +25,7 @@ import { QueueModule } from './queue/queue.module';
DataProviderModule, DataProviderModule,
ExchangeRateDataModule, ExchangeRateDataModule,
MarketDataModule, MarketDataModule,
OrderModule,
PrismaModule, PrismaModule,
PropertyModule, PropertyModule,
QueueModule, QueueModule,

43
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 { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { environment } from '@ghostfolio/api/environments/environment'; import { environment } from '@ghostfolio/api/environments/environment';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -13,11 +14,13 @@ import {
PROPERTY_IS_READ_ONLY_MODE, PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_IS_USER_SIGNUP_ENABLED PROPERTY_IS_USER_SIGNUP_ENABLED
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { isCurrency, getCurrencyFromSymbol } from '@ghostfolio/common/helper';
import { import {
AdminData, AdminData,
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
EnhancedSymbolProfile,
Filter, Filter,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
@ -42,6 +45,7 @@ export class AdminService {
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly marketDataService: MarketDataService, private readonly marketDataService: MarketDataService,
private readonly orderService: OrderService,
private readonly prismaService: PrismaService, private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService, private readonly propertyService: PropertyService,
private readonly subscriptionService: SubscriptionService, private readonly subscriptionService: SubscriptionService,
@ -295,6 +299,16 @@ export class AdminService {
dataSource, dataSource,
symbol symbol
}: UniqueAsset): Promise<AdminMarketDataDetails> { }: UniqueAsset): Promise<AdminMarketDataDetails> {
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([ const [[assetProfile], marketData] = await Promise.all([
this.symbolProfileService.getSymbolProfiles([ this.symbolProfileService.getSymbolProfiles([
{ {
@ -322,8 +336,11 @@ export class AdminService {
return { return {
marketData, marketData,
assetProfile: assetProfile ?? { assetProfile: assetProfile ?? {
symbol, activitiesCount,
currency: '-' currency,
dataSource,
dateOfFirstActivity,
symbol
} }
}; };
} }
@ -413,19 +430,15 @@ export class AdminService {
this.exchangeRateDataService this.exchangeRateDataService
.getCurrencyPairs() .getCurrencyPairs()
.map(async ({ dataSource, symbol }) => { .map(async ({ dataSource, symbol }) => {
const currency = symbol.replace(DEFAULT_CURRENCY, ''); let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
const { _count, _min } = await this.prismaService.order.aggregate({ if (isCurrency(getCurrencyFromSymbol(symbol))) {
_count: true, currency = getCurrencyFromSymbol(symbol);
_min: { ({ activitiesCount, dateOfFirstActivity } =
date: true await this.orderService.getStatisticsByCurrency(currency));
},
where: {
SymbolProfile: {
currency
}
} }
});
const marketDataItemCount = const marketDataItemCount =
marketDataItems.find((marketDataItem) => { marketDataItems.find((marketDataItem) => {
@ -436,15 +449,15 @@ export class AdminService {
})?._count ?? 0; })?._count ?? 0;
return { return {
activitiesCount,
currency, currency,
dataSource, dataSource,
marketDataItemCount, marketDataItemCount,
symbol, symbol,
activitiesCount: _count as number,
assetClass: AssetClass.LIQUIDITY, assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH, assetSubClass: AssetSubClass.CASH,
countriesCount: 0, countriesCount: 0,
date: _min.date, date: dateOfFirstActivity,
id: undefined, id: undefined,
name: symbol, name: symbol,
sectorsCount: 0 sectorsCount: 0

26
apps/api/src/app/order/order.service.ts

@ -10,7 +10,11 @@ import {
GATHER_ASSET_PROFILE_PROCESS_OPTIONS GATHER_ASSET_PROFILE_PROCESS_OPTIONS
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; 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 { OrderWithAccount } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -429,6 +433,26 @@ export class OrderService {
return { activities, count }; 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( public async order(
orderWhereUniqueInput: Prisma.OrderWhereUniqueInput orderWhereUniqueInput: Prisma.OrderWhereUniqueInput
): Promise<Order | null> { ): Promise<Order | null> {

2
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -146,7 +146,7 @@
i18n i18n
size="medium" size="medium"
[locale]="data.locale" [locale]="data.locale"
[value]="assetProfile?.activitiesCount ?? 0" [value]="assetProfile?.activitiesCount"
>Activities</gf-value >Activities</gf-value
> >
</div> </div>

25
libs/common/src/lib/helper.ts

@ -13,7 +13,12 @@ import {
} from 'date-fns'; } from 'date-fns';
import { de, es, fr, it, nl, pl, pt, tr, zhCN } from 'date-fns/locale'; 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 { Benchmark, UniqueAsset } from './interfaces';
import { BenchmarkTrend, ColorScheme } from './types'; 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) { export function getDateFnsLocale(aLanguageCode: string) {
if (aLanguageCode === 'de') { if (aLanguageCode === 'de') {
return de; return de;
@ -322,8 +331,18 @@ export function interpolate(template: string, context: any) {
}); });
} }
export function isCurrency(aSymbol = '') { export function isCurrency(aCurrency = '') {
return currencies[aSymbol]; 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 { export function parseDate(date: string): Date | null {

Loading…
Cancel
Save