Browse Source

Merge d6a9c22638 into b2634db99f

pull/4486/merge
csehatt741 2 days ago
committed by GitHub
parent
commit
8a85391d5d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 3
      apps/api/src/app/export/export.service.ts
  3. 65
      apps/api/src/app/import/import.service.ts
  4. 3
      apps/api/src/app/order/interfaces/activities.interface.ts
  5. 13
      apps/api/src/app/order/order.service.ts
  6. 3
      apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts
  7. 8
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  8. 12
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  9. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  10. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  11. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  12. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
  13. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  14. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts
  15. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts
  16. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  17. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  18. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  19. 86
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  20. 74
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  21. 3
      apps/client/src/app/services/import-activities.service.ts
  22. 2
      libs/ui/src/lib/activities-table/activities-table.component.html
  23. 2
      prisma/migrations/20250401084916_set_value_of_currency_to_null_in_order/migration.sql
  24. 29
      test/import/ok-btceur.json
  25. 29
      test/import/ok-btcusd.json

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added the data gathering status column to the historical market data table of the admin control
- Added support for activities in a custom currency
### Changed

3
apps/api/src/app/export/export.service.ts

@ -120,6 +120,7 @@ export class ExportService {
({
accountId,
comment,
currency,
date,
fee,
id,
@ -137,7 +138,7 @@ export class ExportService {
quantity,
type,
unitPrice,
currency: SymbolProfile.currency,
currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toISOString(),
symbol: ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(type)

65
apps/api/src/app/import/import.service.ts

@ -15,7 +15,6 @@ import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathe
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import {
DATE_FORMAT,
getAssetProfileIdentifier,
parseDate
} from '@ghostfolio/common/helper';
@ -29,8 +28,8 @@ import {
import { Injectable } from '@nestjs/common';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { endOfToday, format, isAfter, isSameSecond, parseISO } from 'date-fns';
import { isNumber, uniqBy } from 'lodash';
import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns';
import { uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
@ -121,13 +120,14 @@ export class ImportService {
currency: undefined,
createdAt: undefined,
fee: 0,
feeInBaseCurrency: 0,
feeInAssetProfileCurrency: 0,
id: assetProfile.id,
isDraft: false,
SymbolProfile: assetProfile,
symbolProfileId: assetProfile.id,
type: 'DIVIDEND',
unitPrice: marketPrice,
unitPriceInAssetProfileCurrency: marketPrice,
updatedAt: undefined,
userId: Account?.userId,
valueInBaseCurrency:
@ -266,17 +266,17 @@ export class ImportService {
const activities: Activity[] = [];
for (const [index, activity] of activitiesExtendedWithErrors.entries()) {
for (const activity of activitiesExtendedWithErrors) {
const accountId = activity.accountId;
const comment = activity.comment;
const currency = activity.currency;
const date = activity.date;
const error = activity.error;
let fee = activity.fee;
const fee = activity.fee;
const quantity = activity.quantity;
const SymbolProfile = activity.SymbolProfile;
const type = activity.type;
let unitPrice = activity.unitPrice;
const unitPrice = activity.unitPrice;
const assetProfile = assetProfiles[
getAssetProfileIdentifier({
@ -284,7 +284,6 @@ export class ImportService {
symbol: SymbolProfile.symbol
})
] ?? {
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
symbol: SymbolProfile.symbol
};
@ -320,35 +319,6 @@ export class ImportService {
Account?: { id: string; name: string };
});
if (SymbolProfile.currency !== assetProfile.currency) {
// Convert the unit price and fee to the asset currency if the imported
// activity is in a different currency
unitPrice = await this.exchangeRateDataService.toCurrencyAtDate(
unitPrice,
SymbolProfile.currency,
assetProfile.currency,
date
);
if (!isNumber(unitPrice)) {
throw new Error(
`activities.${index} historical exchange rate at ${format(
date,
DATE_FORMAT
)} is not available from "${SymbolProfile.currency}" to "${
assetProfile.currency
}"`
);
}
fee = await this.exchangeRateDataService.toCurrencyAtDate(
fee,
SymbolProfile.currency,
assetProfile.currency,
date
);
}
if (isDryRun) {
order = {
comment,
@ -400,6 +370,7 @@ export class ImportService {
order = await this.orderService.createOrder({
comment,
currency,
date,
fee,
quantity,
@ -439,18 +410,12 @@ export class ImportService {
...order,
error,
value,
feeInBaseCurrency: await this.exchangeRateDataService.toCurrencyAtDate(
fee,
assetProfile.currency,
userCurrency,
date
),
// @ts-ignore
SymbolProfile: assetProfile,
valueInBaseCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
value,
assetProfile.currency,
currency ?? assetProfile.currency,
userCurrency,
date
)
@ -520,7 +485,8 @@ export class ImportService {
return (
activity.accountId === accountId &&
activity.comment === comment &&
activity.SymbolProfile.currency === currency &&
(activity.currency === currency ||
activity.SymbolProfile.currency === currency) &&
activity.SymbolProfile.dataSource === dataSource &&
isSameSecond(activity.date, date) &&
activity.fee === fee &&
@ -538,6 +504,7 @@ export class ImportService {
return {
accountId,
comment,
currency,
date,
error,
fee,
@ -545,7 +512,6 @@ export class ImportService {
type,
unitPrice,
SymbolProfile: {
currency,
dataSource,
symbol,
activitiesCount: undefined,
@ -553,6 +519,7 @@ export class ImportService {
assetSubClass: undefined,
countries: undefined,
createdAt: undefined,
currency: undefined,
holdings: undefined,
id: undefined,
isActive: true,
@ -633,12 +600,6 @@ export class ImportService {
`activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
);
}
if (assetProfile.currency !== currency) {
throw new Error(
`activities.${index}.currency ("${currency}") does not match with currency of ${assetProfile.symbol} ("${assetProfile.currency}")`
);
}
}
assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })] =

3
apps/api/src/app/order/interfaces/activities.interface.ts

@ -11,9 +11,10 @@ export interface Activities {
export interface Activity extends Order {
Account?: AccountWithPlatform;
error?: ActivityError;
feeInBaseCurrency: number;
feeInAssetProfileCurrency: number;
SymbolProfile?: EnhancedSymbolProfile;
tags?: Tag[];
unitPriceInAssetProfileCurrency: number;
updateAccountBalance?: boolean;
value: number;
valueInBaseCurrency: number;

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

@ -534,18 +534,25 @@ export class OrderService {
return {
...order,
value,
feeInBaseCurrency:
feeInAssetProfileCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
order.fee,
order.currency ?? order.SymbolProfile.currency,
order.SymbolProfile.currency,
userCurrency,
order.date
),
SymbolProfile: assetProfile,
unitPriceInAssetProfileCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
order.unitPrice,
order.currency ?? order.SymbolProfile.currency,
order.SymbolProfile.currency,
order.date
),
valueInBaseCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
value,
order.SymbolProfile.currency,
order.currency ?? order.SymbolProfile.currency,
userCurrency,
order.date
)

3
apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts

@ -6,10 +6,11 @@ export const activityDummyData = {
comment: undefined,
createdAt: new Date(),
currency: undefined,
feeInBaseCurrency: undefined,
fee: undefined,
id: undefined,
isDraft: false,
symbolProfileId: undefined,
unitPrice: undefined,
updatedAt: new Date(),
userId: undefined,
value: undefined,

8
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -112,12 +112,12 @@ export abstract class PortfolioCalculator {
.map(
({
date,
fee,
feeInAssetProfileCurrency,
quantity,
SymbolProfile,
tags = [],
type,
unitPrice
unitPriceInAssetProfileCurrency
}) => {
if (isBefore(date, dateOfFirstActivity)) {
dateOfFirstActivity = date;
@ -134,9 +134,9 @@ export abstract class PortfolioCalculator {
tags,
type,
date: format(date, DATE_FORMAT),
fee: new Big(fee),
fee: new Big(feeInAssetProfileCurrency),
quantity: new Big(quantity),
unitPrice: new Big(unitPrice)
unitPrice: new Big(unitPriceInAssetProfileCurrency)
};
}
)

12
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2021-11-22'),
fee: 1.55,
feeInAssetProfileCurrency: 1.55,
quantity: 2,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,12 +101,12 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'BUY',
unitPrice: 142.9
unitPriceInAssetProfileCurrency: 142.9
},
{
...activityDummyData,
date: new Date('2021-11-30'),
fee: 1.65,
feeInAssetProfileCurrency: 1.65,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -116,12 +116,12 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'SELL',
unitPrice: 136.6
unitPriceInAssetProfileCurrency: 136.6
},
{
...activityDummyData,
date: new Date('2021-11-30'),
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -131,7 +131,7 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'SELL',
unitPrice: 136.6
unitPriceInAssetProfileCurrency: 136.6
}
];

8
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2021-11-22'),
fee: 1.55,
feeInAssetProfileCurrency: 1.55,
quantity: 2,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,12 +101,12 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'BUY',
unitPrice: 142.9
unitPriceInAssetProfileCurrency: 142.9
},
{
...activityDummyData,
date: new Date('2021-11-30'),
fee: 1.65,
feeInAssetProfileCurrency: 1.65,
quantity: 2,
SymbolProfile: {
...symbolProfileDummyData,
@ -116,7 +116,7 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'SELL',
unitPrice: 136.6
unitPriceInAssetProfileCurrency: 136.6
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2021-11-30'),
fee: 1.55,
feeInAssetProfileCurrency: 1.55,
quantity: 2,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,7 +101,7 @@ describe('PortfolioCalculator', () => {
symbol: 'BALN.SW'
},
type: 'BUY',
unitPrice: 136.6
unitPriceInAssetProfileCurrency: 136.6
}
];

8
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -105,7 +105,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2015-01-01'),
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 2,
SymbolProfile: {
...symbolProfileDummyData,
@ -115,12 +115,12 @@ describe('PortfolioCalculator', () => {
symbol: 'BTCUSD'
},
type: 'BUY',
unitPrice: 320.43
unitPriceInAssetProfileCurrency: 320.43
},
{
...activityDummyData,
date: new Date('2017-12-31'),
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -130,7 +130,7 @@ describe('PortfolioCalculator', () => {
symbol: 'BTCUSD'
},
type: 'SELL',
unitPrice: 14156.4
unitPriceInAssetProfileCurrency: 14156.4
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2021-09-01'),
fee: 49,
feeInAssetProfileCurrency: 49,
quantity: 0,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,7 +101,7 @@ describe('PortfolioCalculator', () => {
symbol: '2c463fb3-af07-486e-adb0-8301b3d72141'
},
type: 'FEE',
unitPrice: 0
unitPriceInAssetProfileCurrency: 0
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts

@ -104,7 +104,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2023-01-03'),
fee: 1,
feeInAssetProfileCurrency: 1,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -114,7 +114,7 @@ describe('PortfolioCalculator', () => {
symbol: 'GOOGL'
},
type: 'BUY',
unitPrice: 89.12
unitPriceInAssetProfileCurrency: 89.12
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2022-01-01'),
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,7 +101,7 @@ describe('PortfolioCalculator', () => {
symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde'
},
type: 'ITEM',
unitPrice: 500000
unitPriceInAssetProfileCurrency: 500000
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts

@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2023-01-01'), // Date in future
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -101,7 +101,7 @@ describe('PortfolioCalculator', () => {
symbol: '55196015-1365-4560-aa60-8751ae6d18f8'
},
type: 'LIABILITY',
unitPrice: 3000
unitPriceInAssetProfileCurrency: 3000
}
];

8
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -104,7 +104,7 @@ describe('PortfolioCalculator', () => {
{
...activityDummyData,
date: new Date('2021-09-16'),
fee: 19,
feeInAssetProfileCurrency: 19,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -114,12 +114,12 @@ describe('PortfolioCalculator', () => {
symbol: 'MSFT'
},
type: 'BUY',
unitPrice: 298.58
unitPriceInAssetProfileCurrency: 298.58
},
{
...activityDummyData,
date: new Date('2021-11-16'),
fee: 0,
feeInAssetProfileCurrency: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
@ -129,7 +129,7 @@ describe('PortfolioCalculator', () => {
symbol: 'MSFT'
},
type: 'DIVIDEND',
unitPrice: 0.62
unitPriceInAssetProfileCurrency: 0.62
}
];

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -105,13 +105,15 @@ describe('PortfolioCalculator', () => {
...activityDummyData,
...activity,
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
SymbolProfile: {
...symbolProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Novartis AG',
symbol: activity.symbol
}
},
unitPriceInAssetProfileCurrency: activity.unitPrice
}));
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -105,13 +105,15 @@ describe('PortfolioCalculator', () => {
...activityDummyData,
...activity,
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
SymbolProfile: {
...symbolProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Novartis AG',
symbol: activity.symbol
}
},
unitPriceInAssetProfileCurrency: activity.unitPrice
}));
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({

86
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -15,7 +15,7 @@ import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client';
import { isAfter, isToday } from 'date-fns';
import { EMPTY, Subject, lastValueFrom } from 'rxjs';
import { EMPTY, Subject } from 'rxjs';
import { catchError, delay, takeUntil } from 'rxjs/operators';
import { DataService } from '../../../../services/data.service';
@ -102,7 +102,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
Validators.required
],
currencyOfUnitPrice: [
this.data.activity?.SymbolProfile?.currency,
this.data.activity?.currency ??
this.data.activity?.SymbolProfile?.currency,
Validators.required
],
dataSource: [
@ -111,7 +112,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
],
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: [
@ -133,10 +133,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
],
type: [undefined, Validators.required], // Set after value changes subscription
unitPrice: [this.data.activity?.unitPrice, Validators.required],
unitPriceInCustomCurrency: [
this.data.activity?.unitPrice,
Validators.required
],
updateAccountBalance: [false]
});
@ -148,57 +144,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
takeUntil(this.unsubscribeSubject)
)
.subscribe(async () => {
let exchangeRateOfUnitPrice = 1;
this.activityForm.get('feeInCustomCurrency').setErrors(null);
this.activityForm.get('unitPriceInCustomCurrency').setErrors(null);
const currency = this.activityForm.get('currency').value;
const currencyOfUnitPrice = this.activityForm.get(
'currencyOfUnitPrice'
).value;
const date = this.activityForm.get('date').value;
if (
currency &&
currencyOfUnitPrice &&
currency !== currencyOfUnitPrice &&
date
) {
try {
const { marketPrice } = await lastValueFrom(
this.dataService
.fetchExchangeRateForDate({
date,
symbol: `${currencyOfUnitPrice}-${currency}`
})
.pipe(takeUntil(this.unsubscribeSubject))
);
exchangeRateOfUnitPrice = marketPrice;
} catch {
this.activityForm.get('unitPriceInCustomCurrency').setErrors({
invalid: true
});
}
}
const feeInCustomCurrency =
this.activityForm.get('feeInCustomCurrency').value *
exchangeRateOfUnitPrice;
const unitPriceInCustomCurrency =
this.activityForm.get('unitPriceInCustomCurrency').value *
exchangeRateOfUnitPrice;
this.activityForm.get('fee').setValue(feeInCustomCurrency, {
emitEvent: false
});
this.activityForm.get('unitPrice').setValue(unitPriceInCustomCurrency, {
emitEvent: false
});
if (
this.activityForm.get('type').value === 'BUY' ||
this.activityForm.get('type').value === 'FEE' ||
@ -265,10 +210,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.activityForm.get('type').value
)
) {
this.activityForm
.get('dataSource')
.setValue(this.activityForm.get('searchSymbol').value.dataSource);
this.updateSymbol();
}
@ -297,7 +238,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
.get('dataSource')
.removeValidators(Validators.required);
this.activityForm.get('dataSource').updateValueAndValidity();
this.activityForm.get('feeInCustomCurrency').reset();
this.activityForm.get('fee').reset();
this.activityForm.get('name').setValidators(Validators.required);
this.activityForm.get('name').updateValueAndValidity();
this.activityForm.get('quantity').setValue(1);
@ -331,12 +272,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.activityForm.get('dataSource').updateValueAndValidity();
if (
(type === 'FEE' &&
this.activityForm.get('feeInCustomCurrency').value === 0) ||
(type === 'FEE' && this.activityForm.get('fee').value === 0) ||
type === 'INTEREST' ||
type === 'LIABILITY'
) {
this.activityForm.get('feeInCustomCurrency').reset();
this.activityForm.get('fee').reset();
}
this.activityForm.get('name').setValidators(Validators.required);
@ -354,7 +294,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.activityForm.get('searchSymbol').updateValueAndValidity();
if (type === 'FEE') {
this.activityForm.get('unitPriceInCustomCurrency').setValue(0);
this.activityForm.get('unitPrice').setValue(0);
}
if (
@ -410,7 +350,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
public applyCurrentMarketPrice() {
this.activityForm.patchValue({
currencyOfUnitPrice: this.activityForm.get('currency').value,
unitPriceInCustomCurrency: this.currentMarketPrice
unitPrice: this.currentMarketPrice
});
}
@ -496,7 +436,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.dataService
.fetchSymbolItem({
dataSource: this.activityForm.get('dataSource').value,
dataSource: this.activityForm.get('searchSymbol').value.dataSource,
symbol: this.activityForm.get('searchSymbol').value.symbol
})
.pipe(
@ -512,9 +452,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
takeUntil(this.unsubscribeSubject)
)
.subscribe(({ currency, dataSource, marketPrice }) => {
this.activityForm.get('currency').setValue(currency);
this.activityForm.get('currencyOfUnitPrice').setValue(currency);
this.activityForm.get('dataSource').setValue(dataSource);
if (this.mode === 'create') {
this.activityForm.get('currency').setValue(currency);
this.activityForm.get('currencyOfUnitPrice').setValue(currency);
this.activityForm.get('dataSource').setValue(dataSource);
}
this.currentMarketPrice = marketPrice;

74
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -214,11 +214,7 @@
}
}
</mat-label>
<input
formControlName="unitPriceInCustomCurrency"
matInput
type="number"
/>
<input formControlName="unitPrice" matInput type="number" />
<div
class="ml-2"
matTextSuffix
@ -232,19 +228,6 @@
}
</mat-select>
</div>
@if (
activityForm.get('unitPriceInCustomCurrency').hasError('invalid')
) {
<mat-error
><ng-container i18n
>Oops! Could not get the historical exchange rate
from</ng-container
>
{{
activityForm.get('date')?.value | date: defaultDateFormat
}}</mat-error
>
}
</mat-form-field>
@if (
currentMarketPrice &&
@ -263,36 +246,6 @@
}
</div>
</div>
<div class="d-none">
<mat-form-field appearance="outline" class="w-100">
<mat-label>
@switch (activityForm.get('type')?.value) {
@case ('DIVIDEND') {
<ng-container i18n>Dividend</ng-container>
}
@case ('FEE') {
<ng-container i18n>Value</ng-container>
}
@case ('INTEREST') {
<ng-container i18n>Value</ng-container>
}
@case ('ITEM') {
<ng-container i18n>Value</ng-container>
}
@case ('LIABILITY') {
<ng-container i18n>Value</ng-container>
}
@default {
<ng-container i18n>Unit Price</ng-container>
}
}
</mat-label>
<input formControlName="unitPrice" matInput type="number" />
<span class="ml-2" matTextSuffix>{{
activityForm.get('currency').value
}}</span>
</mat-form-field>
</div>
<div
class="mb-3"
[ngClass]="{
@ -304,7 +257,7 @@
>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Fee</mat-label>
<input formControlName="feeInCustomCurrency" matInput type="number" />
<input formControlName="fee" matInput type="number" />
<div
class="ml-2"
matTextSuffix
@ -312,26 +265,6 @@
>
{{ activityForm.get('currencyOfUnitPrice').value }}
</div>
@if (activityForm.get('feeInCustomCurrency').hasError('invalid')) {
<mat-error
><ng-container i18n
>Oops! Could not get the historical exchange rate
from</ng-container
>
{{
activityForm.get('date')?.value | date: defaultDateFormat
}}</mat-error
>
}
</mat-form-field>
</div>
<div class="d-none">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Fee</mat-label>
<input formControlName="fee" matInput type="number" />
<span class="ml-2" matTextSuffix>{{
activityForm.get('currency').value
}}</span>
</mat-form-field>
</div>
<div class="mb-3">
@ -392,7 +325,8 @@
[isCurrency]="true"
[locale]="data.user?.settings?.locale"
[unit]="
activityForm.get('currency')?.value ?? data.user?.settings?.baseCurrency
activityForm.get('currencyOfUnitPrice')?.value ??
data.user?.settings?.baseCurrency
"
[value]="total"
/>

3
apps/client/src/app/services/import-activities.service.ts

@ -126,6 +126,7 @@ export class ImportActivitiesService {
private convertToCreateOrderDto({
accountId,
comment,
currency,
date,
fee,
quantity,
@ -137,12 +138,12 @@ export class ImportActivitiesService {
return {
accountId,
comment,
currency,
fee,
quantity,
type,
unitPrice,
updateAccountBalance,
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toString(),
symbol: SymbolProfile.symbol

2
libs/ui/src/lib/activities-table/activities-table.component.html

@ -280,7 +280,7 @@
class="d-none d-lg-table-cell px-1"
mat-cell
>
{{ element.SymbolProfile?.currency }}
{{ element.currency ?? element.SymbolProfile?.currency }}
</td>
</ng-container>

2
prisma/migrations/20250401084916_set_value_of_currency_to_null_in_order/migration.sql

@ -0,0 +1,2 @@
-- AlterTable
UPDATE "Order" SET "currency" = NULL;

29
test/import/ok-btceur.json

@ -0,0 +1,29 @@
{
"meta": {
"date": "2021-12-12T00:00:00.000Z",
"version": "dev"
},
"accounts": [],
"platforms": [],
"tags": [],
"activities": [
{
"accountId": null,
"comment": null,
"fee": 0,
"quantity": 1,
"type": "BUY",
"unitPrice": 39378.5,
"currency": "EUR",
"dataSource": "YAHOO",
"date": "2021-12-12T00:00:00.000Z",
"symbol": "BTCUSD",
"tags": []
}
],
"user": {
"settings": {
"currency": "USD"
}
}
}

29
test/import/ok-btcusd.json

@ -0,0 +1,29 @@
{
"meta": {
"date": "2021-12-12T00:00:00.000Z",
"version": "dev"
},
"accounts": [],
"platforms": [],
"tags": [],
"activities": [
{
"accountId": null,
"comment": null,
"fee": 0,
"quantity": 1,
"type": "BUY",
"unitPrice": 44558.42,
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-12-12T00:00:00.000Z",
"symbol": "BTCUSD",
"tags": []
}
],
"user": {
"settings": {
"currency": "USD"
}
}
}
Loading…
Cancel
Save