From aead44e8d4d91f367101b27f668497cf5ed6bc6a Mon Sep 17 00:00:00 2001 From: David Requeno Date: Mon, 10 Aug 2026 22:11:42 -0600 Subject: [PATCH] Refine stock split activity handling and dependencies --- .../app/activities/activities.service.spec.ts | 28 ++++++++++++++++--- .../src/app/activities/activities.service.ts | 4 +-- .../roai/portfolio-calculator-cash.spec.ts | 8 +++--- .../app/portfolio/portfolio.service.spec.ts | 2 +- .../asset-profile-split.helper.ts | 5 ++++ 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/api/src/app/activities/activities.service.spec.ts b/apps/api/src/app/activities/activities.service.spec.ts index 16a774da4..2b4058ecb 100644 --- a/apps/api/src/app/activities/activities.service.spec.ts +++ b/apps/api/src/app/activities/activities.service.spec.ts @@ -4,6 +4,10 @@ import { assetProfileDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { AssetProfileSplitService } from '@ghostfolio/api/services/asset-profile-split/asset-profile-split.service'; +import { + INVESTMENT_ACTIVITY_TYPES, + NON_INVESTMENT_ACTIVITY_TYPES +} from '@ghostfolio/common/config'; import { parseDate } from '@ghostfolio/common/helper'; import { Activity, Filter } from '@ghostfolio/common/interfaces'; @@ -24,6 +28,7 @@ describe('ActivitiesService', () => { activitiesService = new ActivitiesService( null, accountService as unknown as AccountService, + { getSplitsBySymbolProfileIds } as unknown as AssetProfileSplitService, null, null, null, @@ -32,8 +37,7 @@ describe('ActivitiesService', () => { null, null, null, - null, - { getSplitsBySymbolProfileIds } as unknown as AssetProfileSplitService + null ); }); @@ -150,8 +154,8 @@ describe('ActivitiesService', () => { expect(result.activities[0].unitPrice).toBe(100); }); - it.each(['DIVIDEND', 'INTEREST', 'LIABILITY'])( - 'preserves the value of %s activities', + it.each(INVESTMENT_ACTIVITY_TYPES)( + 'adjusts %s activities', async (type) => { const activity = createActivity({ symbol: 'AAPL' }); activity.type = type as Activity['type']; @@ -166,6 +170,22 @@ describe('ActivitiesService', () => { } ); + it.each(NON_INVESTMENT_ACTIVITY_TYPES)( + 'leaves %s activities unchanged', + async (type) => { + const activity = createActivity({ symbol: 'AAPL' }); + activity.type = type as Activity['type']; + + const result = await getAdjustedActivity(activity, [ + createSplit('2021-01-01', 2, 1) + ]); + + expect(result).toBe(activity); + expect(result.quantity).toBe(10); + expect(result.unitPrice).toBe(100); + } + ); + it('loads and applies splits to standard activities while preserving filters', async () => { const activity = createActivity({ symbol: 'AAPL' }); const filters = [{ id: 'AAPL', type: 'SYMBOL' }] as Filter[]; diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index f17a2994d..bd8cc4a80 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -68,6 +68,7 @@ export class ActivitiesService { public constructor( private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, + private readonly assetProfileSplitService: AssetProfileSplitService, private readonly benchmarkService: BenchmarkService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, @@ -76,8 +77,7 @@ export class ActivitiesService { private readonly marketDataService: MarketDataService, private readonly prismaService: PrismaService, private readonly symbolProfileService: SymbolProfileService, - private readonly tagService: TagService, - private readonly assetProfileSplitService: AssetProfileSplitService + private readonly tagService: TagService ) {} public areCashActivitiesExcludedByFilters(filters: Filter[] = []) { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index 082857307..faf46de89 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -125,6 +125,9 @@ describe('PortfolioCalculator', () => { activitiesService = new ActivitiesService( accountBalanceService, accountService, + { + getSplitsBySymbolProfileIds: jest.fn().mockResolvedValue(new Map()) + } as unknown as AssetProfileSplitService, null, null, dataProviderService, @@ -133,10 +136,7 @@ describe('PortfolioCalculator', () => { null, null, null, - null, - { - getSplitsBySymbolProfileIds: jest.fn().mockResolvedValue(new Map()) - } as unknown as AssetProfileSplitService + null ); portfolioSnapshotService = new PortfolioSnapshotService(null, null); diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index 3433d08c6..5b80c4c4a 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -68,13 +68,13 @@ describe('PortfolioService', () => { accountService, null, null, + null, dataProviderService, null, exchangeRateDataService, null, null, null, - null, null ); diff --git a/apps/api/src/services/asset-profile-split/asset-profile-split.helper.ts b/apps/api/src/services/asset-profile-split/asset-profile-split.helper.ts index cdb0e1808..34154d441 100644 --- a/apps/api/src/services/asset-profile-split/asset-profile-split.helper.ts +++ b/apps/api/src/services/asset-profile-split/asset-profile-split.helper.ts @@ -1,3 +1,4 @@ +import { INVESTMENT_ACTIVITY_TYPES } from '@ghostfolio/common/config'; import { resetHours } from '@ghostfolio/common/helper'; import { Activity } from '@ghostfolio/common/interfaces'; @@ -9,6 +10,10 @@ export function adjustActivityBySplits( activity: Activity, splits: AssetProfileSplit[] ): Activity { + if (!INVESTMENT_ACTIVITY_TYPES.includes(activity.type)) { + return activity; + } + const activityDate = resetHours(activity.date); let splitFactor = new Big(1);