Browse Source

Merge 82085ff229 into b6dbfeaa26

pull/7253/merge
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
778f5108dd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 2
      apps/api/src/app/export/export.service.ts
  3. 20
      apps/api/src/app/import/import.service.ts
  4. 1
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  5. 3
      apps/api/src/app/portfolio/portfolio.service.spec.ts
  6. 1
      apps/api/src/helper/account.helper.ts
  7. 3
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  8. 4
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  9. 7
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html
  10. 10
      libs/common/src/lib/dtos/create-account-with-balances.dto.ts
  11. 8
      libs/common/src/lib/dtos/create-account.dto.ts
  12. 8
      libs/common/src/lib/dtos/update-account.dto.ts
  13. 6
      libs/common/src/lib/helper.ts
  14. 3
      libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts
  15. 5
      libs/ui/src/lib/activities-table/activities-table.component.stories.ts
  16. 21
      prisma/migrations/20260705120000_removed_is_excluded_from_account/migration.sql
  17. 3
      prisma/schema.prisma

7
CHANGELOG.md

@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Moved the support for tags in the account from experimental to general availability
- Removed the deprecated `isExcluded` attribute of the account in favor of the _Exclude from Analysis_ tag including a data migration
## 3.21.0 - 2026-07-05 ## 3.21.0 - 2026-07-05
### Added ### Added

2
apps/api/src/app/export/export.service.ts

@ -94,7 +94,6 @@ export class ExportService {
comment, comment,
currency, currency,
id, id,
isExcluded,
name, name,
platform, platform,
platformId, platformId,
@ -112,7 +111,6 @@ export class ExportService {
comment, comment,
currency, currency,
id, id,
isExcluded,
name, name,
platformId, platformId,
tags: tags.map(({ id: tagId }) => { tags: tags.map(({ id: tagId }) => {

20
apps/api/src/app/import/import.service.ts

@ -9,7 +9,10 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config'; import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
TAG_ID_EXCLUDE_FROM_ANALYSIS
} from '@ghostfolio/common/config';
import { CreateAssetProfileDto, CreateOrderDto } from '@ghostfolio/common/dtos'; import { CreateAssetProfileDto, CreateOrderDto } from '@ghostfolio/common/dtos';
import { import {
getAssetProfileIdentifier, getAssetProfileIdentifier,
@ -272,7 +275,11 @@ export class ImportService {
// If there is no account or if the account belongs to a different user then create a new account // 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) { if (!accountWithSameId || accountWithSameId.userId !== user.id) {
const account = omit(accountWithBalances, ['balances', 'tags']); const account = omit(accountWithBalances, [
'balances',
'isExcluded',
'tags'
]);
let oldAccountId: string; let oldAccountId: string;
const platformId = account.platformId; const platformId = account.platformId;
@ -292,6 +299,15 @@ export class ImportService {
return existingTagIds.has(tagId); return existingTagIds.has(tagId);
}); });
// Map the legacy isExcluded attribute of old export files to
// the "Exclude from Analysis" tag
if (
accountWithBalances.isExcluded &&
!tagIds.includes(TAG_ID_EXCLUDE_FROM_ANALYSIS)
) {
tagIds.push(TAG_ID_EXCLUDE_FROM_ANALYSIS);
}
let accountObject: Prisma.AccountCreateInput = { let accountObject: Prisma.AccountCreateInput = {
...account, ...account,
balances: { balances: {

1
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -171,7 +171,6 @@ describe('PortfolioCalculator', () => {
createdAt: parseDate('2023-12-31'), createdAt: parseDate('2023-12-31'),
currency: 'USD', currency: 'USD',
id: accountId, id: accountId,
isExcluded: false,
name: 'USD', name: 'USD',
platformId: null, platformId: null,
updatedAt: parseDate('2023-12-31'), updatedAt: parseDate('2023-12-31'),

3
apps/api/src/app/portfolio/portfolio.service.spec.ts

@ -185,7 +185,6 @@ describe('PortfolioService', () => {
createdAt: parseDate('2024-01-01'), createdAt: parseDate('2024-01-01'),
currency: 'USD', currency: 'USD',
id: randomUUID(), id: randomUUID(),
isExcluded: false,
name: 'USD', name: 'USD',
platformId: null, platformId: null,
updatedAt: parseDate('2024-01-01'), updatedAt: parseDate('2024-01-01'),
@ -219,7 +218,6 @@ describe('PortfolioService', () => {
createdAt: parseDate('2024-01-01'), createdAt: parseDate('2024-01-01'),
currency: 'USD', currency: 'USD',
id: accountId, id: accountId,
isExcluded: false,
name: 'USD', name: 'USD',
platformId: null, platformId: null,
updatedAt: parseDate('2024-01-01'), updatedAt: parseDate('2024-01-01'),
@ -351,7 +349,6 @@ describe('PortfolioService', () => {
balance: 100, balance: 100,
currency: 'USD', currency: 'USD',
id: randomUUID(), id: randomUUID(),
isExcluded: false,
name: 'Account 1', name: 'Account 1',
platform: { name: 'Platform 1' }, platform: { name: 'Platform 1' },
platformId: randomUUID() platformId: randomUUID()

1
apps/api/src/helper/account.helper.ts

@ -3,7 +3,6 @@ import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
export const WHERE_ACCOUNT_NOT_EXCLUDED: Prisma.AccountWhereInput = { export const WHERE_ACCOUNT_NOT_EXCLUDED: Prisma.AccountWhereInput = {
isExcluded: false,
tags: { tags: {
none: { none: {
tagId: TAG_ID_EXCLUDE_FROM_ANALYSIS tagId: TAG_ID_EXCLUDE_FROM_ANALYSIS

3
apps/client/src/app/pages/accounts/accounts-page.component.ts

@ -186,7 +186,6 @@ export class GfAccountsPageComponent implements OnInit {
comment, comment,
currency, currency,
id, id,
isExcluded,
name, name,
platformId, platformId,
tags tags
@ -201,7 +200,6 @@ export class GfAccountsPageComponent implements OnInit {
comment, comment,
currency, currency,
id, id,
isExcluded,
name, name,
platformId, platformId,
tags tags
@ -278,7 +276,6 @@ export class GfAccountsPageComponent implements OnInit {
comment: null, comment: null,
currency: this.user?.settings?.baseCurrency ?? null, currency: this.user?.settings?.baseCurrency ?? null,
id: null, id: null,
isExcluded: false,
name: null, name: null,
platformId: null, platformId: null,
tags: [] tags: []

4
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -27,7 +27,6 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { import {
MAT_DIALOG_DATA, MAT_DIALOG_DATA,
MatDialogModule, MatDialogModule,
@ -51,7 +50,6 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
GfTagsSelectorComponent, GfTagsSelectorComponent,
MatAutocompleteModule, MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatCheckboxModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
@ -105,7 +103,6 @@ export class GfCreateOrUpdateAccountDialogComponent {
balance: [this.data.account.balance, Validators.required], balance: [this.data.account.balance, Validators.required],
comment: [this.data.account.comment], comment: [this.data.account.comment],
currency: [this.data.account.currency, Validators.required], currency: [this.data.account.currency, Validators.required],
isExcluded: [this.data.account.isExcluded],
name: [this.data.account.name, Validators.required], name: [this.data.account.name, Validators.required],
platformId: [null, this.autocompleteObjectValidator()], platformId: [null, this.autocompleteObjectValidator()],
tags: [ tags: [
@ -203,7 +200,6 @@ export class GfCreateOrUpdateAccountDialogComponent {
comment: this.accountForm.get('comment')?.value || null, comment: this.accountForm.get('comment')?.value || null,
currency: this.accountForm.get('currency')?.value, currency: this.accountForm.get('currency')?.value,
id: this.accountForm.get('accountId')?.value, id: this.accountForm.get('accountId')?.value,
isExcluded: this.accountForm.get('isExcluded')?.value,
name: this.accountForm.get('name')?.value, name: this.accountForm.get('name')?.value,
platformId: this.accountForm.get('platformId')?.value?.id || null, platformId: this.accountForm.get('platformId')?.value?.id || null,
tags: this.accountForm tags: this.accountForm

7
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html

@ -85,7 +85,6 @@
></textarea> ></textarea>
</mat-form-field> </mat-form-field>
</div> </div>
@if (data.user?.settings?.isExperimentalFeatures) {
<div class="mb-3"> <div class="mb-3">
<gf-tags-selector <gf-tags-selector
formControlName="tags" formControlName="tags"
@ -93,12 +92,6 @@
[tagsAvailable]="tagsAvailable" [tagsAvailable]="tagsAvailable"
/> />
</div> </div>
}
<div class="mb-3 px-2">
<mat-checkbox color="primary" formControlName="isExcluded" i18n
>Exclude from Analysis</mat-checkbox
>
</div>
@if (data.account.id) { @if (data.account.id) {
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">

10
libs/common/src/lib/dtos/create-account-with-balances.dto.ts

@ -1,6 +1,6 @@
import { AccountBalance } from '@ghostfolio/common/interfaces'; import { AccountBalance } from '@ghostfolio/common/interfaces';
import { IsArray, IsOptional } from 'class-validator'; import { IsArray, IsBoolean, IsOptional } from 'class-validator';
import { CreateAccountDto } from './create-account.dto'; import { CreateAccountDto } from './create-account.dto';
@ -8,4 +8,12 @@ export class CreateAccountWithBalancesDto extends CreateAccountDto {
@IsArray() @IsArray()
@IsOptional() @IsOptional()
balances?: AccountBalance[]; balances?: AccountBalance[];
/**
* @deprecated Accepted for backward compatibility with old export files
* and mapped to the "Exclude from Analysis" tag (`TAG_ID_EXCLUDE_FROM_ANALYSIS`)
*/
@IsBoolean()
@IsOptional()
isExcluded?: boolean;
} }

8
libs/common/src/lib/dtos/create-account.dto.ts

@ -4,7 +4,6 @@ import { Transform, TransformFnParams } from 'class-transformer';
import { import {
ArrayUnique, ArrayUnique,
IsArray, IsArray,
IsBoolean,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
@ -30,13 +29,6 @@ export class CreateAccountDto {
@IsString() @IsString()
id?: string; id?: string;
/**
* @deprecated Use the "Exclude from Analysis" tag (`TAG_ID_EXCLUDE_FROM_ANALYSIS`) instead
*/
@IsBoolean()
@IsOptional()
isExcluded?: boolean;
@IsString() @IsString()
name: string; name: string;

8
libs/common/src/lib/dtos/update-account.dto.ts

@ -4,7 +4,6 @@ import { Transform, TransformFnParams } from 'class-transformer';
import { import {
ArrayUnique, ArrayUnique,
IsArray, IsArray,
IsBoolean,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
@ -29,13 +28,6 @@ export class UpdateAccountDto {
@IsString() @IsString()
id: string; id: string;
/**
* @deprecated Use the "Exclude from Analysis" tag (`TAG_ID_EXCLUDE_FROM_ANALYSIS`) instead
*/
@IsBoolean()
@IsOptional()
isExcluded?: boolean;
@IsString() @IsString()
name: string; name: string;

6
libs/common/src/lib/helper.ts

@ -420,12 +420,8 @@ export function interpolate(template: string, context: any) {
}); });
} }
export function isAccountExcluded(account: { export function isAccountExcluded(account: { tags?: { id: string }[] }) {
isExcluded: boolean;
tags?: { id: string }[];
}) {
return ( return (
account.isExcluded ||
account.tags?.some(({ id }) => { account.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS; return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
}) === true }) === true

3
libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts

@ -24,7 +24,6 @@ const accounts = [
createdAt: new Date('2025-06-01T06:52:49.063Z'), createdAt: new Date('2025-06-01T06:52:49.063Z'),
currency: 'USD', currency: 'USD',
id: '460d7401-ca43-4ed4-b08e-349f1822e9db', id: '460d7401-ca43-4ed4-b08e-349f1822e9db',
isExcluded: false,
name: 'Coinbase Account', name: 'Coinbase Account',
platform: { platform: {
id: '8dc24b88-bb92-4152-af25-fe6a31643e26', id: '8dc24b88-bb92-4152-af25-fe6a31643e26',
@ -46,7 +45,6 @@ const accounts = [
createdAt: new Date('2025-06-01T06:48:53.055Z'), createdAt: new Date('2025-06-01T06:48:53.055Z'),
currency: 'USD', currency: 'USD',
id: '6d773e31-0583-4c85-a247-e69870b4f1ee', id: '6d773e31-0583-4c85-a247-e69870b4f1ee',
isExcluded: false,
name: 'Private Banking Account', name: 'Private Banking Account',
platform: { platform: {
id: '43e8fcd1-5b79-4100-b678-d2229bd1660d', id: '43e8fcd1-5b79-4100-b678-d2229bd1660d',
@ -68,7 +66,6 @@ const accounts = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platform: { platform: {
id: '9da3a8a7-4795-43e3-a6db-ccb914189737', id: '9da3a8a7-4795-43e3-a6db-ccb914189737',

5
libs/ui/src/lib/activities-table/activities-table.component.stories.ts

@ -45,7 +45,6 @@ const activities: Activity[] = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737',
updatedAt: new Date('2025-06-01T06:53:10.569Z'), updatedAt: new Date('2025-06-01T06:53:10.569Z'),
@ -112,7 +111,6 @@ const activities: Activity[] = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737',
updatedAt: new Date('2025-06-01T06:53:10.569Z'), updatedAt: new Date('2025-06-01T06:53:10.569Z'),
@ -179,7 +177,6 @@ const activities: Activity[] = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737',
updatedAt: new Date('2025-06-01T06:53:10.569Z'), updatedAt: new Date('2025-06-01T06:53:10.569Z'),
@ -246,7 +243,6 @@ const activities: Activity[] = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737',
updatedAt: new Date('2025-06-01T06:53:10.569Z'), updatedAt: new Date('2025-06-01T06:53:10.569Z'),
@ -313,7 +309,6 @@ const activities: Activity[] = [
createdAt: new Date('2025-05-31T13:00:13.940Z'), createdAt: new Date('2025-05-31T13:00:13.940Z'),
currency: 'USD', currency: 'USD',
id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625', id: '776bd1e9-b2f6-4f7e-933d-18756c2f0625',
isExcluded: false,
name: 'Trading Account', name: 'Trading Account',
platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737', platformId: '9da3a8a7-4795-43e3-a6db-ccb914189737',
updatedAt: new Date('2025-06-01T06:53:10.569Z'), updatedAt: new Date('2025-06-01T06:53:10.569Z'),

21
prisma/migrations/20260705120000_removed_is_excluded_from_account/migration.sql

@ -0,0 +1,21 @@
-- Create the "EXCLUDE_FROM_ANALYSIS" tag if it does not exist yet
INSERT INTO "Tag" ("id", "name")
VALUES ('f2e868af-8333-459f-b161-cbc6544c24bd', 'EXCLUDE_FROM_ANALYSIS')
ON CONFLICT DO NOTHING;
-- Migrate accounts with "isExcluded" to the "EXCLUDE_FROM_ANALYSIS" tag
INSERT INTO "TagsOnAccounts" ("accountId", "tagId", "updatedAt", "userId")
SELECT
"id",
'f2e868af-8333-459f-b161-cbc6544c24bd',
CURRENT_TIMESTAMP,
"userId"
FROM "Account"
WHERE "isExcluded" = true
ON CONFLICT DO NOTHING;
-- DropIndex
DROP INDEX "Account_isExcluded_idx";
-- AlterTable
ALTER TABLE "Account" DROP COLUMN "isExcluded";

3
prisma/schema.prisma

@ -33,8 +33,6 @@ model Account {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
currency String? currency String?
id String @default(uuid()) id String @default(uuid())
/// @deprecated Use the "Exclude from Analysis" tag (`TAG_ID_EXCLUDE_FROM_ANALYSIS`) instead
isExcluded Boolean @default(false)
name String? name String?
platform Platform? @relation(fields: [platformId], references: [id]) platform Platform? @relation(fields: [platformId], references: [id])
platformId String? platformId String?
@ -46,7 +44,6 @@ model Account {
@@id([id, userId]) @@id([id, userId])
@@index([currency]) @@index([currency])
@@index([id]) @@index([id])
@@index([isExcluded])
@@index([name]) @@index([name])
@@index([userId]) @@index([userId])
} }

Loading…
Cancel
Save