diff --git a/CHANGELOG.md b/CHANGELOG.md index 98db3b039..226f81fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added alternative investment as an asset class +- Added collectible as an asset sub class + ### Changed +- Respected the filter by account for accounts when exporting activities on the portfolio activities page +- Improved the label for asset profiles with `MANUAL` data source in the chart of the holdings tab on the home page - Improved the language localization for Catalan (`ca`) +- Improved the language localization for Spanish (`es`) + +### Fixed + +- Fixed the export functionality for accounts without activities ## 2.179.0 - 2025-07-07 diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 565dd1340..f0f52bfe2 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -5,7 +5,8 @@ import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { Filter, Export } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; -import { Platform } from '@prisma/client'; +import { Platform, Prisma } from '@prisma/client'; +import { groupBy } from 'lodash'; @Injectable() export class ExportService { @@ -26,6 +27,9 @@ export class ExportService { userCurrency: string; userId: string; }): Promise { + const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => { + return type; + }); const platformsMap: { [platformId: string]: Platform } = {}; let { activities } = await this.orderService.getOrders({ @@ -44,20 +48,30 @@ export class ExportService { }); } + const where: Prisma.AccountWhereInput = { userId }; + + if (filtersByAccount?.length > 0) { + where.id = { + in: filtersByAccount.map(({ id }) => { + return id; + }) + }; + } + const accounts = ( await this.accountService.accounts({ + where, include: { balances: true, platform: true }, orderBy: { name: 'asc' - }, - where: { userId } + } }) ) .filter(({ id }) => { - return activities.length > 0 + return activityIds?.length > 0 ? activities.some(({ accountId }) => { return accountId === id; }) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index e50d2545f..30ab1aa04 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -68,16 +68,10 @@ export class AdminMarketDataComponent @ViewChild(MatSort) sort: MatSort; public activeFilters: Filter[] = []; - public allFilters: Filter[] = [ - AssetSubClass.BOND, - AssetSubClass.COMMODITY, - AssetSubClass.CRYPTOCURRENCY, - AssetSubClass.ETF, - AssetSubClass.MUTUALFUND, - AssetSubClass.PRECIOUS_METAL, - AssetSubClass.PRIVATE_EQUITY, - AssetSubClass.STOCK - ] + public allFilters: Filter[] = Object.keys(AssetSubClass) + .filter((assetSubClass) => { + return assetSubClass !== 'CASH'; + }) .map((assetSubClass) => { return { id: assetSubClass.toString(), diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index e0ef4dd37..a00d6d054 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -6527,7 +6527,7 @@ Italy - Italy + Italia libs/ui/src/lib/i18n.ts 85 @@ -6535,7 +6535,7 @@ Netherlands - Netherlands + Países Bajos libs/ui/src/lib/i18n.ts 87 @@ -6543,7 +6543,7 @@ New Zealand - New Zealand + Nueva Zelanda libs/ui/src/lib/i18n.ts 88 @@ -6551,7 +6551,7 @@ Poland - Poland + Polonia libs/ui/src/lib/i18n.ts 89 @@ -6559,7 +6559,7 @@ Romania - Romania + Rumanía libs/ui/src/lib/i18n.ts 90 @@ -6567,7 +6567,7 @@ South Africa - South Africa + Sudáfrica libs/ui/src/lib/i18n.ts 92 @@ -6575,7 +6575,7 @@ Thailand - Thailand + Tailandia libs/ui/src/lib/i18n.ts 94 @@ -6583,7 +6583,7 @@ United States - United States + Estados Unidos libs/ui/src/lib/i18n.ts 97 diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index 457df01ff..fe8530d7b 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -1,4 +1,5 @@ import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; +import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { SearchMode } from '@ghostfolio/ui/assistant/enums/search-mode'; import { IAssetSearchResultItem, @@ -54,13 +55,16 @@ export class GfAssistantListItemComponent dataSource: this.item?.dataSource, symbol: this.item?.symbol }; - this.routerLink = ['/admin', 'market-data']; + + this.routerLink = + internalRoutes.adminControl.subRoutes.marketData.routerLink; } else if (this.item?.mode === SearchMode.HOLDING) { this.queryParams = { dataSource: this.item?.dataSource, holdingDetailDialog: true, symbol: this.item?.symbol }; + this.routerLink = []; } else if (this.item?.mode === SearchMode.QUICK_LINK) { this.queryParams = {}; diff --git a/libs/ui/src/lib/i18n.ts b/libs/ui/src/lib/i18n.ts index 62b7d162a..749509f82 100644 --- a/libs/ui/src/lib/i18n.ts +++ b/libs/ui/src/lib/i18n.ts @@ -41,7 +41,7 @@ const locales = { SELL: $localize`Sell`, // AssetClass (enum) - CASH: $localize`Cash`, + ALTERNATIVE_INVESTMENT: $localize`Alternative Investment`, COMMODITY: $localize`Commodity`, EQUITY: $localize`Equity`, FIXED_INCOME: $localize`Fixed Income`, @@ -50,6 +50,8 @@ const locales = { // AssetSubClass (enum) BOND: $localize`Bond`, + CASH: $localize`Cash`, + COLLECTIBLE: $localize`Collectible`, CRYPTOCURRENCY: $localize`Cryptocurrency`, ETF: $localize`ETF`, MUTUALFUND: $localize`Mutual Fund`, diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index 8c5ee94de..4e06d49cc 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -29,6 +29,7 @@ import { ChartConfiguration } from 'chart.js'; import { LinearScale } from 'chart.js'; import { Chart, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; +import { isUUID } from 'class-validator'; import { differenceInDays, max } from 'date-fns'; import { orderBy } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -199,18 +200,18 @@ export class GfTreemapChartComponent const data: ChartConfiguration<'treemap'>['data'] = { datasets: [ { - backgroundColor: (ctx) => { + backgroundColor: (context) => { let annualizedNetPerformancePercent = getAnnualizedPerformancePercent({ daysInMarket: differenceInDays( endDate, max([ - ctx.raw._data.dateOfFirstActivity ?? new Date(0), + context.raw._data.dateOfFirstActivity ?? new Date(0), startDate ]) ), netPerformancePercentage: new Big( - ctx.raw._data.netPerformancePercentWithCurrencyEffect + context.raw._data.netPerformancePercentWithCurrencyEffect ) }).toNumber(); @@ -230,18 +231,18 @@ export class GfTreemapChartComponent key: 'allocationInPercentage', labels: { align: 'left', - color: (ctx) => { + color: (context) => { let annualizedNetPerformancePercent = getAnnualizedPerformancePercent({ daysInMarket: differenceInDays( endDate, max([ - ctx.raw._data.dateOfFirstActivity ?? new Date(0), + context.raw._data.dateOfFirstActivity ?? new Date(0), startDate ]) ), netPerformancePercentage: new Big( - ctx.raw._data.netPerformancePercentWithCurrencyEffect + context.raw._data.netPerformancePercentWithCurrencyEffect ) }).toNumber(); @@ -259,11 +260,11 @@ export class GfTreemapChartComponent }, display: true, font: [{ size: 16 }, { lineHeight: 1.5, size: 14 }], - formatter: (ctx) => { + formatter: ({ raw }) => { // Round to 4 decimal places let netPerformancePercentWithCurrencyEffect = Math.round( - ctx.raw._data.netPerformancePercentWithCurrencyEffect * 10000 + raw._data.netPerformancePercentWithCurrencyEffect * 10000 ) / 10000; if (Math.abs(netPerformancePercentWithCurrencyEffect) === 0) { @@ -272,8 +273,11 @@ export class GfTreemapChartComponent ); } + const name = raw._data.name; + const symbol = raw._data.symbol; + return [ - ctx.raw._data.symbol, + isUUID(symbol) ? (name ?? symbol) : symbol, `${netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''}${(netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%` ]; }, @@ -341,19 +345,17 @@ export class GfTreemapChartComponent locale: this.locale }), callbacks: { - label: (context) => { - const allocationInPercentage = `${((context.raw._data.allocationInPercentage as number) * 100).toFixed(2)}%`; - const name = context.raw._data.name; + label: ({ raw }) => { + const allocationInPercentage = `${((raw._data.allocationInPercentage as number) * 100).toFixed(2)}%`; + const name = raw._data.name; const sign = - context.raw._data.netPerformancePercentWithCurrencyEffect > 0 - ? '+' - : ''; - const symbol = context.raw._data.symbol; + raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''; + const symbol = raw._data.symbol; - const netPerformanceInPercentageWithSign = `${sign}${(context.raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`; + const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`; - if (context.raw._data.valueInBaseCurrency !== null) { - const value = context.raw._data.valueInBaseCurrency as number; + if (raw._data.valueInBaseCurrency !== null) { + const value = raw._data.valueInBaseCurrency as number; return [ `${name ?? symbol} (${allocationInPercentage})`, @@ -363,7 +365,7 @@ export class GfTreemapChartComponent })} ${this.baseCurrency}`, '', $localize`Change` + ' (' + $localize`Performance` + ')', - `${sign}${context.raw._data.netPerformanceWithCurrencyEffect.toLocaleString( + `${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString( this.locale, { maximumFractionDigits: 2, diff --git a/prisma/migrations/20250708090630_added_alternative_investment_to_asset_class/migration.sql b/prisma/migrations/20250708090630_added_alternative_investment_to_asset_class/migration.sql new file mode 100644 index 000000000..dceb644ff --- /dev/null +++ b/prisma/migrations/20250708090630_added_alternative_investment_to_asset_class/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "AssetClass" ADD VALUE 'ALTERNATIVE_INVESTMENT'; diff --git a/prisma/migrations/20250708090631_added_collectible_to_asset_sub_class/migration.sql b/prisma/migrations/20250708090631_added_collectible_to_asset_sub_class/migration.sql new file mode 100644 index 000000000..ddfa2d9ee --- /dev/null +++ b/prisma/migrations/20250708090631_added_collectible_to_asset_sub_class/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "AssetSubClass" ADD VALUE 'COLLECTIBLE'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bddb0f78c..6080a1aa6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -278,6 +278,7 @@ enum AccessPermission { } enum AssetClass { + ALTERNATIVE_INVESTMENT COMMODITY EQUITY FIXED_INCOME @@ -288,6 +289,7 @@ enum AssetClass { enum AssetSubClass { BOND CASH + COLLECTIBLE COMMODITY CRYPTOCURRENCY ETF