From 1c9b626858fa0b005801a67969a6acdabfe00c91 Mon Sep 17 00:00:00 2001 From: Chirag Dodia Date: Thu, 9 Jul 2026 17:29:59 -0400 Subject: [PATCH] Bugfix/incorrect error flag in portfolio details for undefined optional holding values The portfolio details endpoint derived hasError from a redundant recursive scan (hasNotDefinedValuesInObject) that flagged legitimately-optional undefined holding fields as errors. Rely solely on the authoritative hasErrors flag from the portfolio calculator and remove the now-unused helper. Closes #6974 --- CHANGELOG.md | 4 ++++ apps/api/src/app/portfolio/portfolio.controller.ts | 7 ++----- apps/api/src/helper/object.helper.ts | 14 +------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d96f7dccb..e195714b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set the change detection strategy to `OnPush` in the _Zen Mode_ - Improved the language localization for Chinese (`zh`) +### Fixed + +- Fixed an issue in the portfolio details endpoint that incorrectly reported an error when a holding contained undefined optional values + ## 3.22.0 - 2026-07-08 ### Added diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 13cc0eae7..3a9f68504 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -2,10 +2,7 @@ import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.ser import { UserService } from '@ghostfolio/api/app/user/user.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; -import { - hasNotDefinedValuesInObject, - nullifyValuesInObject -} from '@ghostfolio/api/helper/object.helper'; +import { nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper'; import { PerformanceLoggingInterceptor } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; @@ -126,7 +123,7 @@ export class PortfolioController { withSummary: true }); - if (hasErrors || hasNotDefinedValuesInObject(holdings)) { + if (hasErrors) { hasError = true; } diff --git a/apps/api/src/helper/object.helper.ts b/apps/api/src/helper/object.helper.ts index 350d5fe04..82f1cd8d1 100644 --- a/apps/api/src/helper/object.helper.ts +++ b/apps/api/src/helper/object.helper.ts @@ -1,18 +1,6 @@ import fastRedact from 'fast-redact'; import jsonpath from 'jsonpath'; -import { cloneDeep, isObject } from 'lodash'; - -export function hasNotDefinedValuesInObject(aObject: Object): boolean { - for (const key in aObject) { - if (aObject[key] === null || aObject[key] === undefined) { - return true; - } else if (isObject(aObject[key])) { - return hasNotDefinedValuesInObject(aObject[key]); - } - } - - return false; -} +import { cloneDeep } from 'lodash'; export function nullifyValuesInObject(aObject: T, keys: string[]): T { const object = cloneDeep(aObject);