Browse Source

Merge remote-tracking branch 'origin/main' into bugfix/improve-portfolio-calc

pull/5130/head
KenTandrian 2 months ago
parent
commit
fdee70f8d8
  1. 12
      CHANGELOG.md
  2. 22
      apps/api/src/app/export/export.service.ts
  3. 14
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  4. 16
      apps/client/src/locales/messages.es.xlf
  5. 6
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts
  6. 4
      libs/ui/src/lib/i18n.ts
  7. 42
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
  8. 2
      prisma/migrations/20250708090630_added_alternative_investment_to_asset_class/migration.sql
  9. 2
      prisma/migrations/20250708090631_added_collectible_to_asset_sub_class/migration.sql
  10. 2
      prisma/schema.prisma

12
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

22
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<Export> {
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;
})

14
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(),

16
apps/client/src/locales/messages.es.xlf

@ -6527,7 +6527,7 @@
</trans-unit>
<trans-unit id="2427223107800831324" datatype="html">
<source>Italy</source>
<target state="new">Italy</target>
<target state="translated">Italia</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">85</context>
@ -6535,7 +6535,7 @@
</trans-unit>
<trans-unit id="3783587393795767345" datatype="html">
<source>Netherlands</source>
<target state="new">Netherlands</target>
<target state="translated">Países Bajos</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">87</context>
@ -6543,7 +6543,7 @@
</trans-unit>
<trans-unit id="3591085113786124083" datatype="html">
<source>New Zealand</source>
<target state="new">New Zealand</target>
<target state="translated">Nueva Zelanda</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">88</context>
@ -6551,7 +6551,7 @@
</trans-unit>
<trans-unit id="2356316679035829946" datatype="html">
<source>Poland</source>
<target state="new">Poland</target>
<target state="translated">Polonia</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">89</context>
@ -6559,7 +6559,7 @@
</trans-unit>
<trans-unit id="545992063382313902" datatype="html">
<source>Romania</source>
<target state="new">Romania</target>
<target state="translated">Rumanía</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">90</context>
@ -6567,7 +6567,7 @@
</trans-unit>
<trans-unit id="8039968326438721789" datatype="html">
<source>South Africa</source>
<target state="new">South Africa</target>
<target state="translated">Sudáfrica</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">92</context>
@ -6575,7 +6575,7 @@
</trans-unit>
<trans-unit id="2040543873210054611" datatype="html">
<source>Thailand</source>
<target state="new">Thailand</target>
<target state="translated">Tailandia</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">94</context>
@ -6583,7 +6583,7 @@
</trans-unit>
<trans-unit id="6133495983093212227" datatype="html">
<source>United States</source>
<target state="new">United States</target>
<target state="translated">Estados Unidos</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">97</context>

6
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 = {};

4
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`,

42
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,

2
prisma/migrations/20250708090630_added_alternative_investment_to_asset_class/migration.sql

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "AssetClass" ADD VALUE 'ALTERNATIVE_INVESTMENT';

2
prisma/migrations/20250708090631_added_collectible_to_asset_sub_class/migration.sql

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "AssetSubClass" ADD VALUE 'COLLECTIBLE';

2
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

Loading…
Cancel
Save