diff --git a/CHANGELOG.md b/CHANGELOG.md index 19140a7c..7b2f531a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the asset profile icon to the asset profile details dialog of the admin control - Added the platform icon to the create or update platform dialog of the admin control - Extended the rules in the _X-ray_ section by a `key` +- Added `currency` to the `Order` database schema as a preparation to set a custom currency - Extended the content of the _Self-Hosting_ section by the data providers on the Frequently Asked Questions (FAQ) page ### Changed diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index f4551231..cbdff87c 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -112,6 +112,7 @@ export class ImportService { accountId: Account?.id, accountUserId: undefined, comment: undefined, + currency: undefined, createdAt: undefined, fee: 0, feeInBaseCurrency: 0, @@ -261,6 +262,7 @@ export class ImportService { { accountId, comment, + currency, date, error, fee, @@ -285,7 +287,6 @@ export class ImportService { assetSubClass, countries, createdAt, - currency, dataSource, figi, figiComposite, @@ -342,6 +343,7 @@ export class ImportService { if (isDryRun) { order = { comment, + currency, date, fee, quantity, @@ -357,7 +359,6 @@ export class ImportService { assetSubClass, countries, createdAt, - currency, dataSource, figi, figiComposite, @@ -371,6 +372,7 @@ export class ImportService { symbolMapping, updatedAt, url, + currency: assetProfile.currency, comment: assetProfile.comment }, Account: validatedAccount, @@ -394,9 +396,9 @@ export class ImportService { SymbolProfile: { connectOrCreate: { create: { - currency, dataSource, - symbol + symbol, + currency: assetProfile.currency }, where: { dataSource_symbol: { @@ -420,14 +422,14 @@ export class ImportService { value, feeInBaseCurrency: this.exchangeRateDataService.toCurrency( fee, - currency, + assetProfile.currency, userCurrency ), // @ts-ignore SymbolProfile: assetProfile, valueInBaseCurrency: this.exchangeRateDataService.toCurrency( value, - currency, + assetProfile.currency, userCurrency ) }); diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index aecec842..6d36f036 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -42,6 +42,10 @@ export class CreateOrderDto { @IsISO4217CurrencyCode() currency: string; + @IsISO4217CurrencyCode() + @IsOptional() + customCurrency?: string; + @IsOptional() @IsEnum(DataSource, { each: true }) dataSource?: DataSource; diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 2f9825d6..c7fec0da 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -126,13 +126,22 @@ export class OrderController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) public async createOrder(@Body() data: CreateOrderDto): Promise { + const currency = data.currency; + const customCurrency = data.customCurrency; + + if (customCurrency) { + data.currency = customCurrency; + + delete data.customCurrency; + } + const order = await this.orderService.createOrder({ ...data, date: parseISO(data.date), SymbolProfile: { connectOrCreate: { create: { - currency: data.currency, + currency, dataSource: data.dataSource, symbol: data.symbol }, @@ -182,8 +191,16 @@ export class OrderController { const date = parseISO(data.date); const accountId = data.accountId; + const customCurrency = data.customCurrency; + delete data.accountId; + if (customCurrency) { + data.currency = customCurrency; + + delete data.customCurrency; + } + return this.orderService.updateOrder({ data: { ...data, diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 126b04a0..20b2d5f1 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -26,6 +26,7 @@ import { endOfToday, isAfter } from 'date-fns'; import { groupBy, uniqBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; +import { CreateOrderDto } from './create-order.dto'; import { Activities } from './interfaces/activities.interface'; @Injectable() @@ -65,7 +66,6 @@ export class OrderService { } const accountId = data.accountId; - let currency = data.currency; const tags = data.tags ?? []; const updateAccountBalance = data.updateAccountBalance ?? false; const userId = data.userId; @@ -73,7 +73,6 @@ export class OrderService { if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(data.type)) { const assetClass = data.assetClass; const assetSubClass = data.assetSubClass; - currency = data.SymbolProfile.connectOrCreate.create.currency; const dataSource: DataSource = 'MANUAL'; const id = uuidv4(); const name = data.SymbolProfile.connectOrCreate.create.symbol; @@ -81,7 +80,6 @@ export class OrderService { data.id = id; data.SymbolProfile.connectOrCreate.create.assetClass = assetClass; data.SymbolProfile.connectOrCreate.create.assetSubClass = assetSubClass; - data.SymbolProfile.connectOrCreate.create.currency = currency; data.SymbolProfile.connectOrCreate.create.dataSource = dataSource; data.SymbolProfile.connectOrCreate.create.name = name; data.SymbolProfile.connectOrCreate.create.symbol = id; @@ -116,7 +114,6 @@ export class OrderService { delete data.comment; } - delete data.currency; delete data.dataSource; delete data.symbol; delete data.tags; @@ -155,8 +152,8 @@ export class OrderService { await this.accountService.updateAccountBalance({ accountId, amount, - currency, userId, + currency: data.SymbolProfile.connectOrCreate.create.currency, date: data.date as Date }); } @@ -442,7 +439,6 @@ export class OrderService { delete data.assetClass; delete data.assetSubClass; - delete data.currency; delete data.dataSource; delete data.symbol; delete data.tags; diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index c0a400c5..be3c2b6e 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -41,6 +41,10 @@ export class UpdateOrderDto { @IsISO4217CurrencyCode() currency: string; + @IsISO4217CurrencyCode() + @IsOptional() + customCurrency?: string; + @IsString() dataSource: DataSource; diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index 6d1939fc..504b5b17 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts @@ -3,6 +3,7 @@ export const activityDummyData = { accountUserId: undefined, comment: undefined, createdAt: new Date(), + currency: undefined, feeInBaseCurrency: undefined, id: undefined, isDraft: false, 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 4fb8e9d8..21a2ca92 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 @@ -98,10 +98,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.data.activity?.SymbolProfile?.currency, Validators.required ], - currencyOfFee: [ - this.data.activity?.SymbolProfile?.currency, - Validators.required - ], currencyOfUnitPrice: [ this.data.activity?.SymbolProfile?.currency, Validators.required @@ -149,45 +145,16 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { takeUntil(this.unsubscribeSubject) ) .subscribe(async () => { - let exchangeRateOfFee = 1; let exchangeRateOfUnitPrice = 1; this.activityForm.controls['feeInCustomCurrency'].setErrors(null); this.activityForm.controls['unitPriceInCustomCurrency'].setErrors(null); const currency = this.activityForm.controls['currency'].value; - const currencyOfFee = this.activityForm.controls['currencyOfFee'].value; const currencyOfUnitPrice = this.activityForm.controls['currencyOfUnitPrice'].value; const date = this.activityForm.controls['date'].value; - if (currency && currencyOfFee && currency !== currencyOfFee && date) { - try { - const { marketPrice } = await lastValueFrom( - this.dataService - .fetchExchangeRateForDate({ - date, - symbol: `${currencyOfFee}-${currency}` - }) - .pipe(takeUntil(this.unsubscribeSubject)) - ); - - exchangeRateOfFee = marketPrice; - } catch { - this.activityForm.controls['feeInCustomCurrency'].setErrors({ - invalid: true - }); - } - } - - const feeInCustomCurrency = - this.activityForm.controls['feeInCustomCurrency'].value * - exchangeRateOfFee; - - this.activityForm.controls['fee'].setValue(feeInCustomCurrency, { - emitEvent: false - }); - if ( currency && currencyOfUnitPrice && @@ -212,10 +179,18 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { } } + const feeInCustomCurrency = + this.activityForm.controls['feeInCustomCurrency'].value * + exchangeRateOfUnitPrice; + const unitPriceInCustomCurrency = this.activityForm.controls['unitPriceInCustomCurrency'].value * exchangeRateOfUnitPrice; + this.activityForm.controls['fee'].setValue(feeInCustomCurrency, { + emitEvent: false + }); + this.activityForm.controls['unitPrice'].setValue( unitPriceInCustomCurrency, { @@ -258,7 +233,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { })?.currency ?? this.data.user.settings.baseCurrency; this.activityForm.controls['currency'].setValue(currency); - this.activityForm.controls['currencyOfFee'].setValue(currency); this.activityForm.controls['currencyOfUnitPrice'].setValue(currency); if (['FEE', 'INTEREST'].includes(type)) { @@ -328,7 +302,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { })?.currency ?? this.data.user.settings.baseCurrency; this.activityForm.controls['currency'].setValue(currency); - this.activityForm.controls['currencyOfFee'].setValue(currency); this.activityForm.controls['currencyOfUnitPrice'].setValue(currency); this.activityForm.controls['dataSource'].removeValidators( @@ -361,7 +334,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { })?.currency ?? this.data.user.settings.baseCurrency; this.activityForm.controls['currency'].setValue(currency); - this.activityForm.controls['currencyOfFee'].setValue(currency); this.activityForm.controls['currencyOfUnitPrice'].setValue(currency); this.activityForm.controls['dataSource'].removeValidators( @@ -486,6 +458,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { assetSubClass: this.activityForm.controls['assetSubClass'].value, comment: this.activityForm.controls['comment'].value, currency: this.activityForm.controls['currency'].value, + customCurrency: this.activityForm.controls['currencyOfUnitPrice'].value, date: this.activityForm.controls['date'].value, dataSource: this.activityForm.controls['dataSource'].value, fee: this.activityForm.controls['fee'].value, @@ -549,7 +522,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { ) .subscribe(({ currency, dataSource, marketPrice }) => { this.activityForm.controls['currency'].setValue(currency); - this.activityForm.controls['currencyOfFee'].setValue(currency); this.activityForm.controls['currencyOfUnitPrice'].setValue(currency); this.activityForm.controls['dataSource'].setValue(dataSource); 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 9491a440..79ea7647 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 @@ -290,11 +290,7 @@ matTextSuffix [ngClass]="{ 'd-none': !activityForm.controls['currency']?.value }" > - - - {{ currency }} - - + {{ activityForm.controls['currencyOfUnitPrice'].value }}