From 689778f025350f48461069358121f27de1fd588f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:18:04 +0200 Subject: [PATCH] Bugfix/exclude cash positions from portfolio calculations with filters (#7212) * Fix handling of cash positions with filters * Update changelog --- CHANGELOG.md | 1 + .../src/app/activities/activities.service.ts | 51 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3c75705..7450c3e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - 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 market condition of the benchmarks in the twitter bot service when values round to zero ## 3.19.1 - 2026-07-03 diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index 55f76f0c9..87aa13146 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -393,17 +393,7 @@ export class ActivitiesService { userCurrency: string; userId: string; }): Promise { - const filtersByAssetClass = filters.filter(({ type }) => { - return type === 'ASSET_CLASS'; - }); - - if ( - filtersByAssetClass.length > 0 && - !filtersByAssetClass.find(({ id }) => { - return id === AssetClass.LIQUIDITY; - }) - ) { - // If asset class filters are present and none of them is liquidity, return an empty response + if (this.hasNonMatchingFiltersForCashActivities(filters)) { return { activities: [], count: 0 @@ -810,7 +800,7 @@ export class ActivitiesService { withExcludedAccountsAndActivities: false // TODO }); - if (withCash) { + if (withCash && !this.hasNonMatchingFiltersForCashActivities(filters)) { const cashDetails = await this.accountService.getCashDetails({ filters, userId, @@ -945,6 +935,43 @@ export class ActivitiesService { 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: { include?: Prisma.OrderInclude; skip?: number;