Feature/integrate wealth items into transaction point concept (#3084)
* Integrate (wealth) items into transaction point concept * Update changelog
This commit is contained in:
parent
66992ef915
commit
5596e5f03b
@ -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…
x
Reference in New Issue
Block a user