From 615e68d894d0bf117314502c054ea558ec081b0c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:51:19 +0200 Subject: [PATCH] Fix start date of date range filter --- .../app/activities/activities.service.spec.ts | 32 +++++++++++++++++++ .../src/app/activities/activities.service.ts | 8 +++-- apps/api/src/app/import/import.service.ts | 4 +-- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/api/src/app/activities/activities.service.spec.ts b/apps/api/src/app/activities/activities.service.spec.ts index 57bd8ab665..d7d0789791 100644 --- a/apps/api/src/app/activities/activities.service.spec.ts +++ b/apps/api/src/app/activities/activities.service.spec.ts @@ -9,6 +9,7 @@ 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 @@ -27,6 +28,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'; @@ -522,6 +524,36 @@ describe('ActivitiesService', () => { }); }); + describe('getWhereClause', () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it('excludes the activities of the start date of a date range', () => { + jest.useFakeTimers().setSystemTime(new Date(2024, 5, 15)); + + const { endDate, startDate } = getIntervalFromDateRange({ + dateRange: 'ytd' + }); + + const { AND } = activitiesService['getWhereClause']({ + endDate, + startDate, + includeDrafts: false, + userId: 'user-id', + withExcludedAccountsAndActivities: false + }); + + // The same limits as for the calendar year 2024 + expect(AND).toEqual( + expect.arrayContaining([ + { date: { gt: endOfDay(new Date(2023, 11, 31)) } }, + { date: { lte: endOfDay(new Date(2024, 5, 15)) } } + ]) + ); + }); + }); + describe('updateActivity', () => { it('updates the custom asset profile of the user', async () => { const data = await getUpdatedActivityData({ userId: 'user-id' }); diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index 0dce736f4e..d8b79cbb80 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 } from 'date-fns'; import { groupBy, omit, uniqBy } from 'lodash-es'; import { randomUUID } from 'node:crypto'; @@ -1037,12 +1038,15 @@ export class ActivitiesService { const andConditions: Prisma.OrderWhereInput[] = []; const where: Prisma.OrderWhereInput = { userId, AND: andConditions }; + // The day of an activity is its calendar date in the time zone of the + // server, as in the portfolio calculator. The start date is excluded and + // the end date is included. if (endDate) { - andConditions.push({ date: { lte: endDate } }); + andConditions.push({ date: { lte: endOfDay(endDate) } }); } if (startDate) { - andConditions.push({ date: { gt: startDate } }); + andConditions.push({ date: { gt: endOfDay(startDate) } }); } const { diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 7616cd927b..ce6d911641 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -47,7 +47,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'; @@ -111,7 +111,7 @@ export class ImportService { userCurrency, userId, includeDrafts: true, - startDate: parseDate(dateOfFirstActivity), + startDate: subDays(parseDate(dateOfFirstActivity), 1), withExcludedAccountsAndActivities: true }), this.symbolProfileService.getSymbolProfiles([