From b6887a74e8aaf7864b0c915cd8e9b5c414938f86 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Dec 2022 13:03:21 +0100 Subject: [PATCH] Support manual currency for fee --- apps/api/src/app/symbol/symbol.controller.ts | 11 +++- .../data-provider/data-provider.service.ts | 16 ++++-- apps/api/src/services/market-data.service.ts | 10 ++-- ...ate-or-update-activity-dialog.component.ts | 50 ++++++++++++++++++- .../create-or-update-activity-dialog.html | 17 +++++++ 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index dd50e0dee..249284ea8 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -91,10 +91,19 @@ export class SymbolController { ); } - return this.symbolService.getForDate({ + const result = await this.symbolService.getForDate({ dataSource, date, symbol }); + + if (!result || isEmpty(result)) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + + return result; } } diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 7f10dc3a0..7092e1112 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -114,9 +114,13 @@ export class DataProviderService { } } - const allData = await Promise.all(promises); - for (const { data, symbol } of allData) { - result[symbol] = data; + try { + const allData = await Promise.all(promises); + for (const { data, symbol } of allData) { + result[symbol] = data; + } + } catch (error) { + Logger.error(error, 'DataProviderService'); } return result; @@ -209,7 +213,9 @@ export class DataProviderService { } Logger.debug( - `Fetched ${symbolsChunk.length} quotes from ${dataSource} in ${( + `Fetched ${symbolsChunk.length} quote${ + symbolsChunk.length > 1 ? 's' : '' + } from ${dataSource} in ${( (performance.now() - startTimeDataSource) / 1000 ).toFixed(3)} seconds` @@ -223,7 +229,7 @@ export class DataProviderService { Logger.debug('------------------------------------------------'); Logger.debug( - `Fetched ${items.length} quotes in ${( + `Fetched ${items.length} quote${items.length > 1 ? 's' : ''} in ${( (performance.now() - startTimeTotal) / 1000 ).toFixed(3)} seconds` diff --git a/apps/api/src/services/market-data.service.ts b/apps/api/src/services/market-data.service.ts index 9dd3e4773..dc8c32319 100644 --- a/apps/api/src/services/market-data.service.ts +++ b/apps/api/src/services/market-data.service.ts @@ -5,6 +5,7 @@ import { resetHours } from '@ghostfolio/common/helper'; import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { DataSource, MarketData, Prisma } from '@prisma/client'; +import { IDataGatheringItem } from './interfaces/interfaces'; @Injectable() export class MarketDataService { @@ -20,14 +21,13 @@ export class MarketDataService { } public async get({ - date, + dataSource, + date = new Date(), symbol - }: { - date: Date; - symbol: string; - }): Promise { + }: IDataGatheringItem): Promise { return await this.prismaService.marketData.findFirst({ where: { + dataSource, symbol, date: resetHours(date) } diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 30d7e64c8..ff4085d65 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -13,12 +13,13 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { translate } from '@ghostfolio/ui/i18n'; import { AssetClass, AssetSubClass, Type } from '@prisma/client'; import { isUUID } from 'class-validator'; import { isString } from 'lodash'; -import { EMPTY, Observable, Subject } from 'rxjs'; +import { EMPTY, lastValueFrom, Observable, Subject } from 'rxjs'; import { catchError, debounceTime, @@ -59,6 +60,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { private unsubscribeSubject = new Subject(); public constructor( + private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams, private dataService: DataService, @@ -86,12 +88,17 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.data.activity?.SymbolProfile?.currency, Validators.required ], + currencyOfFee: [ + this.data.activity?.SymbolProfile?.currency, + Validators.required + ], dataSource: [ this.data.activity?.SymbolProfile?.dataSource, Validators.required ], date: [this.data.activity?.date, Validators.required], fee: [this.data.activity?.fee, Validators.required], + feeInCustomCurrency: [this.data.activity?.fee, Validators.required], name: [this.data.activity?.SymbolProfile?.name, Validators.required], quantity: [this.data.activity?.quantity, Validators.required], searchSymbol: [ @@ -108,7 +115,38 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.activityForm.valueChanges .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { + .subscribe(async () => { + let exchangeRate = 1; + + const currency = this.activityForm.controls['currency'].value; + const currencyOfFee = this.activityForm.controls['currencyOfFee'].value; + const date = this.activityForm.controls['date'].value; + + if (currency && currencyOfFee && currency !== currencyOfFee && date) { + try { + const { marketPrice } = await lastValueFrom( + // TODO: Create endpoint for exchange rate conversion + this.adminService + .fetchSymbolForDate({ + date, + dataSource: 'YAHOO', + symbol: `${currencyOfFee}${currency}` + }) + .pipe(takeUntil(this.unsubscribeSubject)) + ); + + exchangeRate = marketPrice; + } catch {} + } + + const feeInCustomCurrency = + this.activityForm.controls['feeInCustomCurrency'].value * + exchangeRate; + + this.activityForm.controls['fee'].setValue(feeInCustomCurrency, { + emitEvent: false + }); + if ( this.activityForm.controls['type'].value === 'BUY' || this.activityForm.controls['type'].value === 'ITEM' @@ -123,6 +161,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.activityForm.controls['unitPrice'].value - this.activityForm.controls['fee'].value ?? 0; } + + this.changeDetectorRef.markForCheck(); }); this.filteredLookupItemsObservable = this.activityForm.controls[ @@ -160,6 +200,9 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.activityForm.controls['currency'].setValue( this.data.user.settings.baseCurrency ); + this.activityForm.controls['currencyOfFee'].setValue( + this.data.user.settings.baseCurrency + ); this.activityForm.controls['dataSource'].removeValidators( Validators.required ); @@ -189,6 +232,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { ); this.activityForm.controls['searchSymbol'].updateValueAndValidity(); } + + this.changeDetectorRef.markForCheck(); }); this.activityForm.controls['type'].setValue(this.data.activity?.type); @@ -313,6 +358,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { ) .subscribe(({ currency, dataSource, marketPrice }) => { this.activityForm.controls['currency'].setValue(currency); + this.activityForm.controls['currencyOfFee'].setValue(currency); this.activityForm.controls['dataSource'].setValue(dataSource); this.currentMarketPrice = marketPrice; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 45d34a47c..7ecb35b44 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -127,6 +127,23 @@
+ + Fee + +
+ + + {{ currency }} + + +
+
+
+
Fee