diff --git a/CHANGELOG.md b/CHANGELOG.md index a2022ba7bd..ce5781131e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the missing platform icon in the preview step of the activities import +- Fixed the start date of the date range filter on the activities page and in the dividend timeline chart on the analysis page - Fixed the link of the _Manage Asset Profile_ button in the holding detail dialog before the holding has loaded ## 3.85.0 - 2026-10-12 diff --git a/apps/api/src/app/activities/activities.service.spec.ts b/apps/api/src/app/activities/activities.service.spec.ts index 57bd8ab665..7fa771ea2c 100644 --- a/apps/api/src/app/activities/activities.service.spec.ts +++ b/apps/api/src/app/activities/activities.service.spec.ts @@ -9,12 +9,14 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; +import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; 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'; +import { DateRange } from '@ghostfolio/common/types'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { @@ -27,6 +29,7 @@ import { } from '@prisma/client'; import { Big } from 'big.js'; import { isUUID } from 'class-validator'; +import { endOfDay } from 'date-fns'; import { ActivitiesService } from './activities.service'; @@ -148,6 +151,10 @@ describe('ActivitiesService', () => { }); describe('getActivities', () => { + afterEach(() => { + jest.useRealTimers(); + }); + it('returns the activities with the asset profile but without the relation to the symbol profile', async () => { const assetProfile = { ...assetProfileDummyData, @@ -224,6 +231,60 @@ describe('ActivitiesService', () => { expect(activities[0]).not.toHaveProperty('SymbolProfile'); expect(activities[0].assetProfile).toBe(assetProfile); }); + + it('excludes the activities of the start date of a date range', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2024-06-15').getTime()); + + expect(await getDateConditions('ytd')).toEqual( + expect.arrayContaining([ + { date: { gt: endOfDay(parseDate('2023-12-31')) } }, + { date: { lte: endOfDay(parseDate('2024-06-15')) } } + ]) + ); + }); + + it('keeps the start date of the date range max', async () => { + expect(await getDateConditions('max')).toEqual( + expect.arrayContaining([{ date: { gt: new Date(0) } }]) + ); + }); + + async function getDateConditions(dateRange: DateRange) { + const findMany = jest.fn().mockResolvedValue([]); + + const service = new ActivitiesService( + null, + null, + null, + null, + null, + null, + null, + null, + null, + { + order: { + findMany, + count: jest.fn().mockResolvedValue(0) + } + } as unknown as PrismaService, + { + getSymbolProfiles: jest.fn().mockResolvedValue([]) + } as unknown as SymbolProfileService, + null + ); + + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); + + await service.getActivities({ + endDate, + startDate, + userCurrency: 'USD', + userId: 'user-id' + }); + + return findMany.mock.calls[0][0].where.AND; + } }); describe('getActivitiesForPortfolioCalculator', () => { diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index 0dce736f4e..93a37e1a3d 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -61,6 +61,7 @@ import { Type as ActivityType } from '@prisma/client'; import { Big } from 'big.js'; +import { endOfDay, isAfter } from 'date-fns'; import { groupBy, omit, uniqBy } from 'lodash-es'; import { randomUUID } from 'node:crypto'; @@ -1042,7 +1043,11 @@ export class ActivitiesService { } if (startDate) { - andConditions.push({ date: { gt: startDate } }); + andConditions.push({ + date: { + gt: isAfter(startDate, new Date(0)) ? endOfDay(startDate) : startDate + } + }); } const { diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 1dbba4e137..6f036479c7 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -48,7 +48,7 @@ import { Injectable } from '@nestjs/common'; import { Account, DataSource, Prisma } from '@prisma/client'; import { Big } from 'big.js'; import { isISIN } from 'class-validator'; -import { isSameSecond, parseISO } from 'date-fns'; +import { isSameSecond, parseISO, subDays } from 'date-fns'; import { omit, uniqBy } from 'lodash-es'; import { randomUUID } from 'node:crypto'; @@ -112,7 +112,7 @@ export class ImportService { userCurrency, userId, includeDrafts: true, - startDate: parseDate(dateOfFirstActivity), + startDate: subDays(parseDate(dateOfFirstActivity), 1), withExcludedAccountsAndActivities: true }), this.symbolProfileService.getSymbolProfiles([