From ddd20d30998542dcf80ce3dd31b3dd23206e8eb0 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 7 Aug 2026 09:00:30 +0200 Subject: [PATCH] Task/remove redundant balance attribute from account (#7546) * Remove redundant balance attribute from account * Update changelog --- CHANGELOG.md | 1 + .../api/src/app/account/account.controller.ts | 92 +++++++++---------- apps/api/src/app/account/account.service.ts | 80 +++++++++------- .../interfaces/cash-details.interface.ts | 4 +- apps/api/src/app/export/export.service.ts | 4 +- apps/api/src/app/import/import.service.ts | 12 ++- .../app/portfolio/portfolio.service.spec.ts | 7 +- .../src/app/portfolio/portfolio.service.ts | 4 +- .../holding-detail-dialog.component.ts | 5 +- .../pages/accounts/accounts-page.component.ts | 9 +- .../interfaces/interfaces.ts | 8 +- libs/common/src/lib/config.ts | 2 - .../common/src/lib/dtos/create-account.dto.ts | 7 +- .../common/src/lib/dtos/update-account.dto.ts | 7 +- .../lib/types/account-with-balance.type.ts | 5 + .../src/lib/types/account-with-value.type.ts | 6 +- libs/common/src/lib/types/index.ts | 2 + .../accounts-table.component.stories.ts | 20 ++-- .../accounts-table.component.ts | 14 +-- .../activities-table.component.stories.ts | 5 - .../migration.sql | 2 + prisma/schema.prisma | 1 - test/import/not-ok/invalid-platform.json | 1 - test/import/ok/500-activities.json | 1 - test/import/ok/derived-currency.json | 1 - test/import/ok/sample.json | 1 - 26 files changed, 171 insertions(+), 130 deletions(-) create mode 100644 libs/common/src/lib/types/account-with-balance.type.ts create mode 100644 prisma/migrations/20260805120000_removed_balance_from_account/migration.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1e4b745..4656bdedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the usability of the create watchlist item dialog by setting the initial focus to the search field - Migrated the abstract _Material_ form field from a component to a directive +- Removed the redundant `balance` attribute of the account in favor of the account balances ## 3.43.0 - 2026-08-06 diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 6466a13b2..f43aeedd5 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -156,32 +156,34 @@ export class AccountController { public async createAccount( @Body() data: CreateAccountDto ): Promise { - const { tags: tagIds, ...accountData } = data; + const { balance, tags: tagIds, ...accountData } = data; if (accountData.platformId) { const platformId = accountData.platformId; delete accountData.platformId; - return this.accountService.createAccount( - { + return this.accountService.createAccount({ + balance, + tagIds, + data: { ...accountData, platform: { connect: { id: platformId } }, user: { connect: { id: this.request.user.id } } }, - this.request.user.id, - tagIds - ); + userId: this.request.user.id + }); } else { delete accountData.platformId; - return this.accountService.createAccount( - { + return this.accountService.createAccount({ + balance, + tagIds, + data: { ...accountData, user: { connect: { id: this.request.user.id } } }, - this.request.user.id, - tagIds - ); + userId: this.request.user.id + }); } } @@ -257,52 +259,50 @@ export class AccountController { ); } - const { tags: tagIds, ...accountData } = data; + const { balance, tags: tagIds, ...accountData } = data; if (accountData.platformId) { const platformId = accountData.platformId; delete accountData.platformId; - return this.accountService.updateAccount( - { - data: { - ...accountData, - platform: { connect: { id: platformId } }, - user: { connect: { id: this.request.user.id } } - }, - where: { - id_userId: { - id, - userId: this.request.user.id - } - } + return this.accountService.updateAccount({ + balance, + tagIds, + data: { + ...accountData, + platform: { connect: { id: platformId } }, + user: { connect: { id: this.request.user.id } } }, - this.request.user.id, - tagIds - ); + userId: this.request.user.id, + where: { + id_userId: { + id, + userId: this.request.user.id + } + } + }); } else { // platformId is null, remove it delete accountData.platformId; - return this.accountService.updateAccount( - { - data: { - ...accountData, - platform: originalAccount.platformId - ? { disconnect: true } - : undefined, - user: { connect: { id: this.request.user.id } } - }, - where: { - id_userId: { - id, - userId: this.request.user.id - } - } + return this.accountService.updateAccount({ + balance, + tagIds, + data: { + ...accountData, + platform: originalAccount.platformId + ? { disconnect: true } + : undefined, + user: { connect: { id: this.request.user.id } } }, - this.request.user.id, - tagIds - ); + userId: this.request.user.id, + where: { + id_userId: { + id, + userId: this.request.user.id + } + } + }); } } } diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index 7f0451101..3d0bb91bd 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -10,6 +10,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Filter } from '@ghostfolio/common/interfaces'; +import { AccountWithBalance } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -24,7 +25,7 @@ import { } from '@prisma/client'; import { Big } from 'big.js'; import { endOfToday, format } from 'date-fns'; -import { groupBy } from 'lodash'; +import { groupBy, isNil } from 'lodash'; import { CashDetails } from './interfaces/cash-details.interface'; @@ -40,7 +41,7 @@ export class AccountService { public async account({ id_userId - }: Prisma.AccountWhereUniqueInput): Promise { + }: Prisma.AccountWhereUniqueInput): Promise { const account = await this.prismaService.account.findUnique({ include: { balances: { @@ -87,7 +88,7 @@ export class AccountService { where?: Prisma.AccountWhereInput; orderBy?: Prisma.AccountOrderByWithRelationInput; }): Promise< - (Account & { + (AccountWithBalance & { activities?: (Order & { SymbolProfile?: SymbolProfile })[]; balances?: AccountBalance[]; platform?: Platform; @@ -160,12 +161,18 @@ export class AccountService { }); } - public async createAccount( - data: Prisma.AccountCreateInput, - aUserId: string, - tagIds?: string[] - ): Promise { - await this.tagService.validateTagIds({ tagIds, userId: aUserId }); + public async createAccount({ + balance, + data, + tagIds, + userId + }: { + balance?: number; + data: Prisma.AccountCreateInput; + tagIds?: string[]; + userId: string; + }): Promise { + await this.tagService.validateTagIds({ tagIds, userId }); const account = await this.prismaService.account.create({ data: { @@ -182,12 +189,14 @@ export class AccountService { } }); - await this.accountBalanceService.createOrUpdateAccountBalance({ - accountId: account.id, - balance: data.balance, - date: format(new Date(), DATE_FORMAT), - userId: aUserId - }); + if (!isNil(balance)) { + await this.accountBalanceService.createOrUpdateAccountBalance({ + balance, + userId, + accountId: account.id, + date: format(new Date(), DATE_FORMAT) + }); + } this.eventEmitter.emit( PortfolioChangedEvent.getName(), @@ -216,7 +225,7 @@ export class AccountService { return account; } - public async getAccounts(aUserId: string): Promise { + public async getAccounts(aUserId: string): Promise { const accounts = await this.accounts({ include: { activities: true, @@ -295,17 +304,20 @@ export class AccountService { }; } - public async updateAccount( - params: { - data: Prisma.AccountUpdateInput; - where: Prisma.AccountWhereUniqueInput; - }, - aUserId: string, - tagIds?: string[] - ): Promise { - const { data, where } = params; - - await this.tagService.validateTagIds({ tagIds, userId: aUserId }); + public async updateAccount({ + balance, + data, + tagIds, + userId, + where + }: { + balance?: number; + data: Prisma.AccountUpdateInput; + tagIds?: string[]; + userId: string; + where: Prisma.AccountWhereUniqueInput; + }): Promise { + await this.tagService.validateTagIds({ tagIds, userId }); const account = await this.prismaService.account.update({ data: { @@ -324,12 +336,14 @@ export class AccountService { where }); - await this.accountBalanceService.createOrUpdateAccountBalance({ - accountId: account.id, - balance: data.balance as number, - date: format(new Date(), DATE_FORMAT), - userId: aUserId - }); + if (!isNil(balance)) { + await this.accountBalanceService.createOrUpdateAccountBalance({ + balance, + userId, + accountId: account.id, + date: format(new Date(), DATE_FORMAT) + }); + } this.eventEmitter.emit( PortfolioChangedEvent.getName(), diff --git a/apps/api/src/app/account/interfaces/cash-details.interface.ts b/apps/api/src/app/account/interfaces/cash-details.interface.ts index 715343766..b396328a5 100644 --- a/apps/api/src/app/account/interfaces/cash-details.interface.ts +++ b/apps/api/src/app/account/interfaces/cash-details.interface.ts @@ -1,6 +1,6 @@ -import { Account } from '@prisma/client'; +import { AccountWithBalance } from '@ghostfolio/common/types'; export interface CashDetails { - accounts: Account[]; + accounts: AccountWithBalance[]; balanceInBaseCurrency: number; } diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 02ccb46b3..35db20993 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -102,7 +102,6 @@ export class ExportService { }) .map( ({ - balance, balances, comment, currency, @@ -111,13 +110,12 @@ export class ExportService { platform, platformId, tags - }) => { + }): ExportResponse['accounts'][number] => { if (platformId) { platformsMap[platformId] = platform; } return { - balance, balances: balances.map(({ date, value }) => { return { date: date.toISOString(), value }; }), diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 96612522e..9d1e899c3 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -355,6 +355,7 @@ export class ImportService { // If there is no account or if the account belongs to a different user then create a new account if (!accountWithSameId || accountWithSameId.userId !== user.id) { const account = omit(accountWithBalances, [ + 'balance', 'balances', 'isExcluded', 'tags' @@ -408,11 +409,12 @@ export class ImportService { }; } - const newAccount = await this.accountService.createAccount( - accountObject, - user.id, - tagIds - ); + const newAccount = await this.accountService.createAccount({ + tagIds, + balance: accountWithBalances.balance, + data: accountObject, + userId: user.id + }); // Store the new to old account ID mappings for updating activities if (accountWithSameId && oldAccountId) { diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index 97635553f..eed3a27cb 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -16,8 +16,9 @@ import { AssetProfileIdentifier, PortfolioSummary } from '@ghostfolio/common/interfaces'; +import { AccountWithBalance } from '@ghostfolio/common/types'; -import { Account, DataSource } from '@prisma/client'; +import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { randomUUID } from 'node:crypto'; @@ -219,7 +220,7 @@ describe('PortfolioService', () => { it('should return cash holdings when the calculator emits cash positions with the exchange-rate data source', async () => { const accountId = randomUUID(); - const cashAccount: Account = { + const cashAccount: AccountWithBalance = { balance: 2000, comment: null, createdAt: parseDate('2024-01-01'), @@ -444,7 +445,7 @@ describe('PortfolioService', () => { beforeEach(() => { jest .spyOn(accountService, 'getAccounts') - .mockResolvedValue([account] as unknown as Account[]); + .mockResolvedValue([account] as unknown as AccountWithBalance[]); jest .spyOn(exchangeRateDataService, 'toCurrency') diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 70106bdc1..ef79fd717 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -64,6 +64,7 @@ import { } from '@ghostfolio/common/interfaces'; import { TimelinePosition } from '@ghostfolio/common/models'; import { + AccountWithBalance, AccountWithValue, DateRange, GroupBy, @@ -75,7 +76,6 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Inject, Injectable, Logger } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { - Account, Type as ActivityType, AssetClass, AssetSubClass, @@ -2142,7 +2142,7 @@ export class PortfolioService { const accounts: PortfolioDetails['accounts'] = {}; const platforms: PortfolioDetails['platforms'] = {}; - let currentAccounts: (Account & { + let currentAccounts: (AccountWithBalance & { Order?: Order[]; platform?: Platform; tags?: Tag[]; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index d58349fa0..57b8196d6 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -22,6 +22,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; +import { AccountWithValue } from '@ghostfolio/common/types'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits'; @@ -65,7 +66,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { MatTabsModule } from '@angular/material/tabs'; import { NavigationStart, Router, RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; -import { Account, MarketData, Tag } from '@prisma/client'; +import { MarketData, Tag } from '@prisma/client'; import { isUUID } from 'class-validator'; import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { addIcons } from 'ionicons'; @@ -117,7 +118,7 @@ import { templateUrl: 'holding-detail-dialog.html' }) export class GfHoldingDetailDialogComponent implements OnInit { - protected accounts: Account[]; + protected accounts: AccountWithValue[]; protected activitiesCount: number; protected assetClass: string; protected assetProfile: Pick< diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 291a7f3f9..e45c1d003 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -12,6 +12,7 @@ import { } from '@ghostfolio/common/dtos'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { AccountWithValue } from '@ghostfolio/common/types'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { GfFabComponent } from '@ghostfolio/ui/fab'; import { NotificationService } from '@ghostfolio/ui/notifications'; @@ -29,7 +30,7 @@ import { import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import { Account as AccountModel, Tag } from '@prisma/client'; +import { Tag } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -48,7 +49,7 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba templateUrl: './accounts-page.html' }) export class GfAccountsPageComponent implements OnInit { - protected accounts: AccountModel[]; + protected accounts: AccountWithValue[]; protected activitiesCount = 0; protected hasImpersonationId: boolean; protected hasPermissionToCreateAccount: boolean; @@ -155,7 +156,7 @@ export class GfAccountsPageComponent implements OnInit { }); } - protected onUpdateAccount(aAccount: AccountModel) { + protected onUpdateAccount(aAccount: AccountWithValue) { this.router.navigate([], { queryParams: { accountId: aAccount.id, editDialog: true } }); @@ -194,7 +195,7 @@ export class GfAccountsPageComponent implements OnInit { name, platformId, tags - }: AccountModel & { tags?: Tag[] }) { + }: AccountWithValue & { tags?: Tag[] }) { const dialogRef = this.dialog.open< GfCreateOrUpdateAccountDialogComponent, CreateOrUpdateAccountDialogParams diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts index c51503277..43015de00 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts @@ -1,9 +1,13 @@ import { User } from '@ghostfolio/common/interfaces'; +import { AccountWithBalance } from '@ghostfolio/common/types'; -import { Account, Tag } from '@prisma/client'; +import { Tag } from '@prisma/client'; export interface CreateOrUpdateAccountDialogParams { - account: Omit & { + account: Omit< + AccountWithBalance, + 'createdAt' | 'id' | 'updatedAt' | 'userId' + > & { id: string | null; tags?: Tag[]; }; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 08a6701dc..23c8aab69 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -115,7 +115,6 @@ export const DEFAULT_REDACTED_PATHS = [ 'accounts[*].interestInBaseCurrency', 'accounts[*].value', 'accounts[*].valueInBaseCurrency', - 'activities[*].account.balance', 'activities[*].account.comment', 'activities[*].assetProfile.symbolMapping', 'activities[*].assetProfile.watchedByCount', @@ -128,7 +127,6 @@ export const DEFAULT_REDACTED_PATHS = [ 'activities[*].valueInBaseCurrency', 'balance', 'balanceInBaseCurrency', - 'balances[*].account.balance', 'balances[*].account.comment', 'balances[*].value', 'balances[*].valueInBaseCurrency', diff --git a/libs/common/src/lib/dtos/create-account.dto.ts b/libs/common/src/lib/dtos/create-account.dto.ts index cae8293ee..ccadff5f9 100644 --- a/libs/common/src/lib/dtos/create-account.dto.ts +++ b/libs/common/src/lib/dtos/create-account.dto.ts @@ -12,8 +12,13 @@ import { import { isString } from 'lodash'; export class CreateAccountDto { + /** + * The initial balance, stored as the account balance of today. + * Optional because callers may instead supply the full history via `balances`. + */ @IsNumber() - balance: number; + @IsOptional() + balance?: number; @IsOptional() @IsString() diff --git a/libs/common/src/lib/dtos/update-account.dto.ts b/libs/common/src/lib/dtos/update-account.dto.ts index d8bfc7b8d..4e1570aad 100644 --- a/libs/common/src/lib/dtos/update-account.dto.ts +++ b/libs/common/src/lib/dtos/update-account.dto.ts @@ -12,8 +12,13 @@ import { import { isString } from 'lodash'; export class UpdateAccountDto { + /** + * The balance, stored as the account balance of today. + * Optional because the account balances are the source of truth. + */ @IsNumber() - balance: number; + @IsOptional() + balance?: number; @IsOptional() @IsString() diff --git a/libs/common/src/lib/types/account-with-balance.type.ts b/libs/common/src/lib/types/account-with-balance.type.ts new file mode 100644 index 000000000..72732f3a6 --- /dev/null +++ b/libs/common/src/lib/types/account-with-balance.type.ts @@ -0,0 +1,5 @@ +import { Account as AccountModel } from '@prisma/client'; + +export type AccountWithBalance = AccountModel & { + balance: number; +}; diff --git a/libs/common/src/lib/types/account-with-value.type.ts b/libs/common/src/lib/types/account-with-value.type.ts index a13530632..27b7541dc 100644 --- a/libs/common/src/lib/types/account-with-value.type.ts +++ b/libs/common/src/lib/types/account-with-value.type.ts @@ -1,6 +1,8 @@ -import { Account as AccountModel, Platform, Tag } from '@prisma/client'; +import { Platform, Tag } from '@prisma/client'; -export type AccountWithValue = AccountModel & { +import { AccountWithBalance } from './account-with-balance.type'; + +export type AccountWithValue = AccountWithBalance & { activitiesCount: number; allocationInPercentage: number; balanceInBaseCurrency: number; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index aa6893bc6..f61bfdca7 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -1,5 +1,6 @@ import type { AccessType } from './access-type.type'; import type { AccessWithGranteeUser } from './access-with-grantee-user.type'; +import type { AccountWithBalance } from './account-with-balance.type'; import type { AccountWithPlatform } from './account-with-platform.type'; import type { AccountWithValue } from './account-with-value.type'; import type { AiPromptMode } from './ai-prompt-mode.type'; @@ -28,6 +29,7 @@ import type { ViewMode } from './view-mode.type'; export type { AccessType, AccessWithGranteeUser, + AccountWithBalance, AccountWithPlatform, AccountWithValue, AiPromptMode, diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts index a38ca826c..68da20ec6 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts @@ -1,3 +1,5 @@ +import { AccountWithValue } from '@ghostfolio/common/types'; + import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatMenuModule } from '@angular/material/menu'; @@ -14,16 +16,18 @@ import { NotificationService } from '../notifications'; import { GfValueComponent } from '../value'; import { GfAccountsTableComponent } from './accounts-table.component'; -const accounts = [ +const accounts: AccountWithValue[] = [ { activitiesCount: 0, - allocationInPercentage: null, + allocationInPercentage: 0.002574748676949956, balance: 278, balanceInBaseCurrency: 278, comment: null, createdAt: new Date('2025-06-01T06:52:49.063Z'), currency: 'USD', + dividendInBaseCurrency: 0, id: '460d7401-ca43-4ed4-b08e-349f1822e9db', + interestInBaseCurrency: 0, name: 'Coinbase Account', platform: { id: '8dc24b88-bb92-4152-af25-fe6a31643e26', @@ -38,13 +42,15 @@ const accounts = [ }, { activitiesCount: 0, - allocationInPercentage: null, + allocationInPercentage: 0.11114023065971035, balance: 12000, balanceInBaseCurrency: 12000, comment: null, createdAt: new Date('2025-06-01T06:48:53.055Z'), currency: 'USD', + dividendInBaseCurrency: 0, id: '6d773e31-0583-4c85-a247-e69870b4f1ee', + interestInBaseCurrency: 0, name: 'Private Banking Account', platform: { id: '43e8fcd1-5b79-4100-b678-d2229bd1660d', @@ -59,13 +65,15 @@ const accounts = [ }, { activitiesCount: 12, - allocationInPercentage: null, + allocationInPercentage: 0.8862850206633397, balance: 150.2, balanceInBaseCurrency: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', + dividendInBaseCurrency: 0, id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', + interestInBaseCurrency: 0, name: 'Trading Account', platform: { id: '9da3a8a7-4795-43e3-a6db-ccb914189737', @@ -73,10 +81,10 @@ const accounts = [ url: 'https://interactivebrokers.com' }, platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', - valueInBaseCurrency: 95693.70321466809, updatedAt: new Date('2025-06-01T06:53:10.569Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', - value: 95693.70321466809 + value: 95693.70321466809, + valueInBaseCurrency: 95693.70321466809 } ]; diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index 3e531e844..ab49c8cb5 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -4,6 +4,7 @@ import { getLowercase, isAccountExcluded } from '@ghostfolio/common/helper'; +import { AccountWithValue } from '@ghostfolio/common/types'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -24,7 +25,6 @@ import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { Router, RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; -import { Account } from '@prisma/client'; import { addIcons } from 'ionicons'; import { arrowRedoOutline, @@ -55,7 +55,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; templateUrl: './accounts-table.component.html' }) export class GfAccountsTableComponent { - public readonly accounts = input.required(); + public readonly accounts = input.required(); public readonly activitiesCount = input(); public readonly baseCurrency = input(); public readonly hasPermissionToOpenDetails = input(true); @@ -71,12 +71,12 @@ export class GfAccountsTableComponent { public readonly totalValueInBaseCurrency = input(); public readonly accountDeleted = output(); - public readonly accountToUpdate = output(); + public readonly accountToUpdate = output(); public readonly transferBalance = output(); public readonly sort = viewChild.required(MatSort); - protected readonly dataSource = new MatTableDataSource([]); + protected readonly dataSource = new MatTableDataSource([]); protected readonly displayedColumns = computed(() => { const columns = ['status', 'account', 'platform']; @@ -141,7 +141,9 @@ export class GfAccountsTableComponent { }); } - protected isExcluded(account: Account & { tags?: { id: string }[] }) { + protected isExcluded( + account: AccountWithValue & { tags?: { id: string }[] } + ) { return isAccountExcluded(account); } @@ -173,7 +175,7 @@ export class GfAccountsTableComponent { this.transferBalance.emit(); } - protected onUpdateAccount(aAccount: Account) { + protected onUpdateAccount(aAccount: AccountWithValue) { this.accountToUpdate.emit(aAccount); } } diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts index 0136545cf..929c71787 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts @@ -39,7 +39,6 @@ const activities: Activity[] = [ updatedAt: new Date('2025-05-31T18:43:01.840Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', account: { - balance: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', @@ -105,7 +104,6 @@ const activities: Activity[] = [ updatedAt: new Date('2025-05-31T18:46:14.175Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', account: { - balance: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', @@ -171,7 +169,6 @@ const activities: Activity[] = [ updatedAt: new Date('2025-05-31T18:49:54.064Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', account: { - balance: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', @@ -237,7 +234,6 @@ const activities: Activity[] = [ updatedAt: new Date('2025-05-31T18:48:48.209Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', account: { - balance: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', @@ -303,7 +299,6 @@ const activities: Activity[] = [ updatedAt: new Date('2025-05-31T18:46:44.616Z'), userId: '081aa387-487d-4438-83a4-3060eb2a016e', account: { - balance: 150.2, comment: null, createdAt: new Date('2025-05-31T13:00:13.940Z'), currency: 'USD', diff --git a/prisma/migrations/20260805120000_removed_balance_from_account/migration.sql b/prisma/migrations/20260805120000_removed_balance_from_account/migration.sql new file mode 100644 index 000000000..2371cb748 --- /dev/null +++ b/prisma/migrations/20260805120000_removed_balance_from_account/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Account" DROP COLUMN "balance"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 77faccd49..4451be4e2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,7 +27,6 @@ model Access { model Account { activities Order[] - balance Float @default(0) balances AccountBalance[] comment String? createdAt DateTime @default(now()) diff --git a/test/import/not-ok/invalid-platform.json b/test/import/not-ok/invalid-platform.json index 69a8e29ca..d280228b8 100644 --- a/test/import/not-ok/invalid-platform.json +++ b/test/import/not-ok/invalid-platform.json @@ -5,7 +5,6 @@ }, "accounts": [ { - "balance": 0, "balances": [], "currency": "USD", "id": "e62be662-a2c8-4cff-8b79-dc0a46576659", diff --git a/test/import/ok/500-activities.json b/test/import/ok/500-activities.json index 03aabca33..3c74d8517 100644 --- a/test/import/ok/500-activities.json +++ b/test/import/ok/500-activities.json @@ -5,7 +5,6 @@ }, "accounts": [ { - "balance": 2000, "currency": "USD", "id": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", "name": "My Online Trading Account", diff --git a/test/import/ok/derived-currency.json b/test/import/ok/derived-currency.json index 4b7aa46c3..100ab6739 100644 --- a/test/import/ok/derived-currency.json +++ b/test/import/ok/derived-currency.json @@ -5,7 +5,6 @@ }, "accounts": [ { - "balance": 2000, "currency": "USD", "id": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", "name": "My Online Trading Account", diff --git a/test/import/ok/sample.json b/test/import/ok/sample.json index feca8a379..3c75c492b 100644 --- a/test/import/ok/sample.json +++ b/test/import/ok/sample.json @@ -5,7 +5,6 @@ }, "accounts": [ { - "balance": 2000, "balances": [ { "date": "2024-12-31T00:00:00.000Z",