Browse Source

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
pull/7298/head
Chirag Dodia 5 days ago
parent
commit
1c9b626858
  1. 4
      CHANGELOG.md
  2. 7
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 14
      apps/api/src/helper/object.helper.ts

4
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_ - Set the change detection strategy to `OnPush` in the _Zen Mode_
- Improved the language localization for Chinese (`zh`) - 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 ## 3.22.0 - 2026-07-08
### Added ### Added

7
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 { UserService } from '@ghostfolio/api/app/user/user.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { import { nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper';
hasNotDefinedValuesInObject,
nullifyValuesInObject
} from '@ghostfolio/api/helper/object.helper';
import { PerformanceLoggingInterceptor } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; 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 { 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'; 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 withSummary: true
}); });
if (hasErrors || hasNotDefinedValuesInObject(holdings)) { if (hasErrors) {
hasError = true; hasError = true;
} }

14
apps/api/src/helper/object.helper.ts

@ -1,18 +1,6 @@
import fastRedact from 'fast-redact'; import fastRedact from 'fast-redact';
import jsonpath from 'jsonpath'; import jsonpath from 'jsonpath';
import { cloneDeep, isObject } from 'lodash'; import { cloneDeep } 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;
}
export function nullifyValuesInObject<T>(aObject: T, keys: string[]): T { export function nullifyValuesInObject<T>(aObject: T, keys: string[]): T {
const object = cloneDeep(aObject); const object = cloneDeep(aObject);

Loading…
Cancel
Save