Feature/integrate wealth items into transaction point concept (#3084)

* Integrate (wealth) items into transaction point concept

* Update changelog
pull/3085/head^2
Thomas Kaul 3 months ago committed by GitHub
parent 66992ef915
commit 5596e5f03b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the usability of the benchmarks in the markets overview
- Integrated (wealth) items into the transaction point concept in the portfolio service
### Fixed
- Fixed a missing value in the activities table on mobile
- Fixed a missing value on the public page
- Displayed the button to fetch the current market price only if the activity is from today
## 2.59.0 - 2024-02-29

@ -43,7 +43,7 @@ export class ImportController {
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async import(
@Body() importData: ImportDataDto,
@Query('dryRun') isDryRun?: boolean
@Query('dryRun') isDryRun = false
): Promise<ImportResponse> {
if (
!hasPermission(this.request.user.permissions, permissions.createAccount)

@ -8,7 +8,7 @@ import {
GATHER_ASSET_PROFILE_PROCESS_OPTIONS
} from '@ghostfolio/common/config';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter } from '@ghostfolio/common/interfaces';
import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
@ -200,6 +200,17 @@ export class OrderService {
return count;
}
public async getLatestOrder({ dataSource, symbol }: UniqueAsset) {
return this.prismaService.order.findFirst({
orderBy: {
date: 'desc'
},
where: {
SymbolProfile: { dataSource, symbol }
}
});
}
public async getOrders({
filters,
includeDrafts = false,

@ -108,6 +108,7 @@ describe('CurrentRateService', () => {
currentRateService = new CurrentRateService(
dataProviderService,
marketDataService,
null,
null
);
});

@ -1,3 +1,4 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { resetHours } from '@ghostfolio/common/helper';
@ -22,6 +23,7 @@ export class CurrentRateService {
public constructor(
private readonly dataProviderService: DataProviderService,
private readonly marketDataService: MarketDataService,
private readonly orderService: OrderService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@ -121,11 +123,17 @@ export class CurrentRateService {
});
if (!value) {
// Fallback to unit price of latest activity
const latestActivity = await this.orderService.getLatestOrder({
dataSource,
symbol
});
value = {
dataSource,
symbol,
date: today,
marketPrice: 0
marketPrice: latestActivity?.unitPrice ?? 0
};
response.values.push(value);

@ -21,7 +21,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -21,7 +21,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -34,7 +34,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -34,7 +34,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -21,7 +21,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -21,7 +21,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -21,7 +21,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -10,7 +10,7 @@ describe('PortfolioCalculator', () => {
let exchangeRateDataService: ExchangeRateDataService;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null);
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,

@ -825,6 +825,7 @@ export class PortfolioCalculator {
switch (type) {
case 'BUY':
case 'ITEM':
factor = 1;
break;
case 'SELL':

@ -342,7 +342,8 @@ export class PortfolioController {
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string,
@Query('withExcludedAccounts') withExcludedAccounts = false
@Query('withExcludedAccounts') withExcludedAccounts = false,
@Query('withItems') withItems = false
): Promise<PortfolioPerformanceResponse> {
const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({
@ -361,6 +362,7 @@ export class PortfolioController {
filters,
impersonationId,
withExcludedAccounts,
withItems,
userId: this.request.user.id
});
@ -515,7 +517,8 @@ export class PortfolioController {
dateOfFirstActivity: portfolioPosition.dateOfFirstActivity,
markets: hasDetails ? portfolioPosition.markets : undefined,
name: portfolioPosition.name,
netPerformancePercent: portfolioPosition.netPerformancePercent,
netPerformancePercentWithCurrencyEffect:
portfolioPosition.netPerformancePercentWithCurrencyEffect,
sectors: hasDetails ? portfolioPosition.sectors : [],
symbol: portfolioPosition.symbol,
url: portfolioPosition.url,

@ -277,7 +277,8 @@ export class PortfolioService {
await this.getTransactionPoints({
filters,
userId,
includeDrafts: true
includeDrafts: true,
types: ['BUY', 'SELL']
});
if (transactionPoints.length === 0) {
@ -702,7 +703,7 @@ export class PortfolioService {
.filter((order) => {
tags = tags.concat(order.tags);
return order.type === 'BUY' || order.type === 'SELL';
return ['BUY', 'ITEM', 'SELL'].includes(order.type);
})
.map((order) => ({
currency: order.SymbolProfile.currency,
@ -957,7 +958,8 @@ export class PortfolioService {
const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
filters,
userId
userId,
types: ['BUY', 'SELL']
});
if (transactionPoints?.length <= 0) {
@ -1087,13 +1089,15 @@ export class PortfolioService {
filters,
impersonationId,
userId,
withExcludedAccounts = false
withExcludedAccounts = false,
withItems = false
}: {
dateRange?: DateRange;
filters?: Filter[];
impersonationId: string;
userId: string;
withExcludedAccounts?: boolean;
withItems?: boolean;
}): Promise<PortfolioPerformanceResponse> {
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
@ -1128,7 +1132,8 @@ export class PortfolioService {
await this.getTransactionPoints({
filters,
userId,
withExcludedAccounts
withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
});
const portfolioCalculator = new PortfolioCalculator({
@ -1280,7 +1285,8 @@ export class PortfolioService {
const { orders, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
userId
userId,
types: ['BUY', 'SELL']
});
const portfolioCalculator = new PortfolioCalculator({
@ -1913,11 +1919,13 @@ export class PortfolioService {
private async getTransactionPoints({
filters,
includeDrafts = false,
types = ['BUY', 'ITEM', 'SELL'],
userId,
withExcludedAccounts = false
}: {
filters?: Filter[];
includeDrafts?: boolean;
types?: ActivityType[];
userId: string;
withExcludedAccounts?: boolean;
}): Promise<{
@ -1931,10 +1939,10 @@ export class PortfolioService {
const { activities, count } = await this.orderService.getOrders({
filters,
includeDrafts,
types,
userCurrency,
userId,
withExcludedAccounts,
types: ['BUY', 'SELL']
withExcludedAccounts
});
if (count <= 0) {
@ -2006,7 +2014,7 @@ export class PortfolioService {
userCurrency,
userId,
withExcludedAccounts,
types: ['ITEM', 'LIABILITY']
types: ['LIABILITY']
});
const accounts: PortfolioDetails['accounts'] = {};
@ -2094,18 +2102,14 @@ export class PortfolioService {
Account,
quantity,
SymbolProfile,
type,
valueInBaseCurrency
type
} of ordersByAccount) {
const unitPriceInBaseCurrency =
portfolioItemsNow[SymbolProfile.symbol]?.marketPriceInBaseCurrency ??
valueInBaseCurrency ??
0;
let currentValueOfSymbolInBaseCurrency =
quantity * unitPriceInBaseCurrency;
quantity *
portfolioItemsNow[SymbolProfile.symbol]
?.marketPriceInBaseCurrency ?? 0;
if (type === 'LIABILITY' || type === 'SELL') {
if (['LIABILITY', 'SELL'].includes(type)) {
currentValueOfSymbolInBaseCurrency *= -1;
}

@ -39,7 +39,7 @@ export class SymbolController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async lookupSymbol(
@Query('includeIndices') includeIndices: boolean = false,
@Query('includeIndices') includeIndices = false,
@Query('query') query = ''
): Promise<{ items: LookupItem[] }> {
try {

@ -166,13 +166,15 @@ export class ManualService implements DataProviderInterface {
}
});
for (const symbolProfile of symbolProfiles) {
response[symbolProfile.symbol] = {
currency: symbolProfile.currency,
for (const { currency, symbol } of symbolProfiles) {
let marketPrice = marketData.find((marketDataItem) => {
return marketDataItem.symbol === symbol;
})?.marketPrice;
response[symbol] = {
currency,
marketPrice,
dataSource: this.getName(),
marketPrice: marketData.find((marketDataItem) => {
return marketDataItem.symbol === symbolProfile.symbol;
})?.marketPrice,
marketState: 'delayed'
};
}

@ -227,7 +227,8 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
}
],
range: 'max',
withExcludedAccounts: true
withExcludedAccounts: true,
withItems: true
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ chart }) => {

@ -437,11 +437,13 @@ export class DataService {
public fetchPortfolioPerformance({
filters,
range,
withExcludedAccounts = false
withExcludedAccounts = false,
withItems = false
}: {
filters?: Filter[];
range: DateRange;
withExcludedAccounts?: boolean;
withItems?: boolean;
}): Observable<PortfolioPerformanceResponse> {
let params = this.buildFiltersAsQueryParams({ filters });
params = params.append('range', range);
@ -450,6 +452,10 @@ export class DataService {
params = params.append('withExcludedAccounts', withExcludedAccounts);
}
if (withItems) {
params = params.append('withItems', withItems);
}
return this.http
.get<any>(`/api/v2/portfolio/performance`, {
params

@ -13,7 +13,7 @@ export interface PortfolioPublicDetails {
| 'dateOfFirstActivity'
| 'markets'
| 'name'
| 'netPerformancePercent'
| 'netPerformancePercentWithCurrencyEffect'
| 'sectors'
| 'symbol'
| 'url'

Loading…
Cancel
Save