Browse Source

Merge branch 'main' into bugfix/skip-opening-holding-detail-dialog-for-cash-positions

pull/7390/head
Thomas Kaul 1 month ago
committed by GitHub
parent
commit
de862cf2ee
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 4
      apps/api/src/app/access/access.controller.ts
  3. 6
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  4. 65
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 4
      apps/api/src/services/benchmark/benchmark.service.ts
  6. 4
      apps/client/src/app/components/investment-chart/investment-chart.component.ts

2
CHANGELOG.md

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Skipped opening the holding detail dialog for cash positions on the allocations page, the analysis page and the portfolio holdings page
- 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
## 3.31.0 - 2026-07-20

4
apps/api/src/app/access/access.controller.ts

@ -87,7 +87,7 @@ export class AccessController {
}
try {
return this.accessService.createAccess({
return await this.accessService.createAccess({
alias: data.alias || undefined,
granteeUser: data.granteeUserId
? { connect: { id: data.granteeUserId } }
@ -155,7 +155,7 @@ export class AccessController {
}
try {
return this.accessService.updateAccess({
return await this.accessService.updateAccess({
data: {
alias: data.alias,
granteeUser: data.granteeUserId

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

@ -51,6 +51,8 @@ import {
format,
isAfter,
isBefore,
isFuture,
isPast,
isWithinInterval,
min,
startOfDay,
@ -134,7 +136,7 @@ export abstract class PortfolioCalculator {
dateOfFirstActivity = date;
}
if (isAfter(date, new Date())) {
if (isFuture(date)) {
// Adapt date to today if activity is in future (e.g. liability)
// to include it in the interval
date = endOfDay(new Date());
@ -1113,7 +1115,7 @@ export abstract class PortfolioCalculator {
portfolioSnapshot
);
if (isAfter(new Date(), new Date(expiration))) {
if (isPast(new Date(expiration))) {
isCachedPortfolioSnapshotExpired = true;
}
} catch {}

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

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

4
apps/api/src/services/benchmark/benchmark.service.ts

@ -23,7 +23,7 @@ import { BenchmarkTrend } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { addHours, isAfter, subDays } from 'date-fns';
import { addHours, isPast, subDays } from 'date-fns';
import { round, uniqBy } from 'lodash';
import ms from 'ms';
@ -94,7 +94,7 @@ export class BenchmarkService {
this.logger.debug('Fetched benchmarks from cache');
if (isAfter(new Date(), new Date(expiration))) {
if (isPast(new Date(expiration))) {
this.calculateAndCacheBenchmarks({
enableSharing
});

4
apps/client/src/app/components/investment-chart/investment-chart.component.ts

@ -41,7 +41,7 @@ import {
} from 'chart.js';
import 'chartjs-adapter-date-fns';
import { type AnnotationOptions } from 'chartjs-plugin-annotation';
import { isAfter } from 'date-fns';
import { isFuture } from 'date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
@ -311,6 +311,6 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
return undefined;
}
return isAfter(new Date(xValue), new Date()) ? aValue : undefined;
return isFuture(new Date(xValue)) ? aValue : undefined;
}
}

Loading…
Cancel
Save