Browse Source

Bugfix/handle exception in holding detail endpoint for cash positions (#7391)

* Handle exception for cash positions

* Update changelog
pull/7390/head^2
Thomas Kaul 1 day ago
committed by GitHub
parent
commit
ca8c4e6236
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 65
      apps/api/src/app/portfolio/portfolio.service.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Resolved an exception in the `GET api/v1/portfolio/holding/:dataSource/:symbol` endpoint for cash positions
- Improved the error handling in the access endpoints (`POST` and `PUT`) to return `400 Bad Request` when granting access to a non-existent user - Improved the error handling in the access endpoints (`POST` and `PUT`) to return `400 Bad Request` when granting access to a non-existent user
## 3.31.0 - 2026-07-20 ## 3.31.0 - 2026-07-20

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

@ -783,10 +783,24 @@ export class PortfolioService {
return undefined; return undefined;
} }
const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
{ dataSource, symbol } { dataSource, symbol }
]); ]);
const assetProfile =
symbolProfile ??
({
dataSource,
symbol,
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
countries: [],
currency: symbol,
holdings: [],
name: symbol,
sectors: []
} as EnhancedSymbolProfile);
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
userId, userId,
@ -829,9 +843,10 @@ export class PortfolioService {
timeWeightedInvestmentWithCurrencyEffect timeWeightedInvestmentWithCurrencyEffect
} = holding; } = holding;
const activitiesOfHolding = activities.filter(({ assetProfile }) => { const activitiesOfHolding = activities.filter((activity) => {
return ( return (
assetProfile.dataSource === dataSource && assetProfile.symbol === symbol activity.assetProfile.dataSource === dataSource &&
activity.assetProfile.symbol === symbol
); );
}); });
@ -863,19 +878,17 @@ export class PortfolioService {
new Date() new Date()
); );
const [firstActivity] = activitiesOfHolding;
const referenceUnitPrice =
firstActivity?.unitPriceInAssetProfileCurrency ?? marketPrice;
const historicalDataArray: HistoricalDataItem[] = []; const historicalDataArray: HistoricalDataItem[] = [];
let marketPriceMax = Math.max( let marketPriceMax = Math.max(referenceUnitPrice, marketPrice);
activitiesOfHolding[0].unitPriceInAssetProfileCurrency,
marketPrice
);
let marketPriceMaxDate = let marketPriceMaxDate =
marketPrice > activitiesOfHolding[0].unitPriceInAssetProfileCurrency marketPrice > referenceUnitPrice
? new Date() ? new Date()
: activitiesOfHolding[0].date; : (firstActivity?.date ?? new Date());
let marketPriceMin = Math.min( let marketPriceMin = Math.min(referenceUnitPrice, marketPrice);
activitiesOfHolding[0].unitPriceInAssetProfileCurrency,
marketPrice
);
const historicalDataItems = const historicalDataItems =
historicalData[getAssetProfileIdentifier({ dataSource, symbol })]; historicalData[getAssetProfileIdentifier({ dataSource, symbol })];
@ -926,10 +939,10 @@ export class PortfolioService {
} else { } else {
// Add historical entry for buy date, if no historical data available // Add historical entry for buy date, if no historical data available
historicalDataArray.push({ historicalDataArray.push({
averagePrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, averagePrice: referenceUnitPrice,
date: dateOfFirstActivity, date: dateOfFirstActivity,
marketPrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, marketPrice: referenceUnitPrice,
quantity: activitiesOfHolding[0].quantity quantity: firstActivity?.quantity ?? quantity.toNumber()
}); });
} }
@ -947,16 +960,16 @@ export class PortfolioService {
marketPriceMin, marketPriceMin,
tags, tags,
assetProfile: { assetProfile: {
assetClass: SymbolProfile.assetClass, assetClass: assetProfile.assetClass,
assetSubClass: SymbolProfile.assetSubClass, assetSubClass: assetProfile.assetSubClass,
countries: SymbolProfile.countries, countries: assetProfile.countries,
currency: SymbolProfile.currency, currency: assetProfile.currency,
dataSource: SymbolProfile.dataSource, dataSource: assetProfile.dataSource,
isin: SymbolProfile.isin, isin: assetProfile.isin,
name: SymbolProfile.name, name: assetProfile.name,
sectors: SymbolProfile.sectors, sectors: assetProfile.sectors,
symbol: SymbolProfile.symbol, symbol: assetProfile.symbol,
userId: SymbolProfile.userId userId: assetProfile.userId
}, },
averagePrice: averagePrice.toNumber(), averagePrice: averagePrice.toNumber(),
dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0],

Loading…
Cancel
Save