Browse Source

Bugfix/handling of cash positions in portfolio details with filters (#7215)

* Fix handling of cash positions with filters

* Update changelog
pull/7209/head
Thomas Kaul 2 weeks ago
committed by GitHub
parent
commit
c4c3845ef2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 78
      apps/api/src/app/activities/activities.service.ts
  3. 36
      apps/api/src/app/portfolio/portfolio.service.ts

1
CHANGELOG.md

@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Resolved an issue in the treemap chart component when the holdings list is empty - Resolved an issue in the treemap chart component when the holdings list is empty
- Fixed the handling of cash positions in the portfolio calculations when filtering by holding or tag - Fixed the handling of cash positions in the portfolio calculations when filtering by holding or tag
- Fixed the handling of cash positions in the portfolio details when filtering
- Fixed the market condition of the benchmarks in the twitter bot service when values round to zero - Fixed the market condition of the benchmarks in the twitter bot service when values round to zero
## 3.19.1 - 2026-07-03 ## 3.19.1 - 2026-07-03

78
apps/api/src/app/activities/activities.service.ts

@ -63,6 +63,43 @@ export class ActivitiesService {
private readonly symbolProfileService: SymbolProfileService private readonly symbolProfileService: SymbolProfileService
) {} ) {}
public areCashActivitiesExcludedByFilters(filters: Filter[] = []) {
const {
ASSET_CLASS: filtersByAssetClass = [],
DATA_SOURCE: [filterByDataSource] = [],
SYMBOL: [filterBySymbol] = [],
TAG: filtersByTag = []
} = groupBy(filters, ({ type }) => {
return type;
});
const isFilteredByAssetClassOtherThanLiquidity =
filtersByAssetClass.length > 0 &&
!filtersByAssetClass.some(({ id }) => {
return id === AssetClass.LIQUIDITY;
});
const isFilteredByAssetProfile = !!(filterByDataSource || filterBySymbol);
const isFilteredByTag = filtersByTag.length > 0;
const isFilteredByUnsupportedType = filters.some(({ type }) => {
return ![
'ACCOUNT',
'ASSET_CLASS',
'DATA_SOURCE',
'SYMBOL',
'TAG'
].includes(type);
});
return (
isFilteredByAssetClassOtherThanLiquidity ||
isFilteredByAssetProfile ||
isFilteredByTag ||
isFilteredByUnsupportedType
);
}
public async assignTags({ public async assignTags({
dataSource, dataSource,
symbol, symbol,
@ -393,7 +430,7 @@ export class ActivitiesService {
userCurrency: string; userCurrency: string;
userId: string; userId: string;
}): Promise<ActivitiesResponse> { }): Promise<ActivitiesResponse> {
if (this.hasNonMatchingFiltersForCashActivities(filters)) { if (this.areCashActivitiesExcludedByFilters(filters)) {
return { return {
activities: [], activities: [],
count: 0 count: 0
@ -800,7 +837,7 @@ export class ActivitiesService {
withExcludedAccountsAndActivities: false // TODO withExcludedAccountsAndActivities: false // TODO
}); });
if (withCash && !this.hasNonMatchingFiltersForCashActivities(filters)) { if (withCash && !this.areCashActivitiesExcludedByFilters(filters)) {
const cashDetails = await this.accountService.getCashDetails({ const cashDetails = await this.accountService.getCashDetails({
filters, filters,
userId, userId,
@ -935,43 +972,6 @@ export class ActivitiesService {
return activity; return activity;
} }
private hasNonMatchingFiltersForCashActivities(filters: Filter[] = []) {
const {
ASSET_CLASS: filtersByAssetClass = [],
DATA_SOURCE: [filterByDataSource] = [],
SYMBOL: [filterBySymbol] = [],
TAG: filtersByTag = []
} = groupBy(filters, ({ type }) => {
return type;
});
const isFilteredByAssetClassOtherThanLiquidity =
filtersByAssetClass.length > 0 &&
!filtersByAssetClass.some(({ id }) => {
return id === AssetClass.LIQUIDITY;
});
const isFilteredByAssetProfile = !!(filterByDataSource || filterBySymbol);
const isFilteredByTag = filtersByTag.length > 0;
const isFilteredByUnsupportedType = filters.some(({ type }) => {
return ![
'ACCOUNT',
'ASSET_CLASS',
'DATA_SOURCE',
'SYMBOL',
'TAG'
].includes(type);
});
return (
isFilteredByAssetClassOtherThanLiquidity ||
isFilteredByAssetProfile ||
isFilteredByTag ||
isFilteredByUnsupportedType
);
}
private async orders(params: { private async orders(params: {
include?: Prisma.OrderInclude; include?: Prisma.OrderInclude;
skip?: number; skip?: number;

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

@ -528,30 +528,18 @@ export class PortfolioService {
const holdings: PortfolioDetails['holdings'] = {}; const holdings: PortfolioDetails['holdings'] = {};
const totalValueInBaseCurrency = currentValueInBaseCurrency.plus( const {
cashDetails.balanceInBaseCurrency HOLDING_TYPE: [filterByHoldingType] = [],
); TAG: [filterByTag] = []
} = groupBy(filters, ({ type }) => {
const isFilteredByAccount = return type;
filters?.some(({ type }) => { });
return type === 'ACCOUNT';
}) ?? false;
const isFilteredByClosedHoldings = const isFilteredByClosedHoldings = filterByHoldingType?.id === 'CLOSED';
filters?.some(({ id, type }) => {
return id === 'CLOSED' && type === 'HOLDING_TYPE';
}) ?? false;
let filteredValueInBaseCurrency = isFilteredByAccount let filteredValueInBaseCurrency = currentValueInBaseCurrency;
? totalValueInBaseCurrency
: currentValueInBaseCurrency;
if ( if (!this.activitiesService.areCashActivitiesExcludedByFilters(filters)) {
filters?.length === 0 ||
(filters?.length === 1 &&
filters[0].id === AssetClass.LIQUIDITY &&
filters[0].type === 'ASSET_CLASS')
) {
filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus( filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus(
cashDetails.balanceInBaseCurrency cashDetails.balanceInBaseCurrency
); );
@ -703,11 +691,7 @@ export class PortfolioService {
withExcludedAccounts withExcludedAccounts
}); });
if ( if (filters?.length === 1 && filterByTag?.id === TAG_ID_EMERGENCY_FUND) {
filters?.length === 1 &&
filters[0].id === TAG_ID_EMERGENCY_FUND &&
filters[0].type === 'TAG'
) {
const emergencyFundCashPositions = this.getCashPositions({ const emergencyFundCashPositions = this.getCashPositions({
cashDetails, cashDetails,
userCurrency, userCurrency,

Loading…
Cancel
Save