Feature/refactor unique asset type to asset profile identifier (#3636)

* Refactoring
pull/3637/head
Thomas Kaul 6 months ago committed by GitHub
parent 4c7657a90e
commit 41f5801b5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,9 +21,9 @@ import {
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem,
AssetProfileIdentifier,
EnhancedSymbolProfile,
Filter,
UniqueAsset
Filter
} from '@ghostfolio/common/interfaces';
import { MarketDataPreset } from '@ghostfolio/common/types';
@ -59,7 +59,9 @@ export class AdminService {
currency,
dataSource,
symbol
}: UniqueAsset & { currency?: string }): Promise<SymbolProfile | never> {
}: AssetProfileIdentifier & { currency?: string }): Promise<
SymbolProfile | never
> {
try {
if (dataSource === 'MANUAL') {
return this.symbolProfileService.add({
@ -96,7 +98,10 @@ export class AdminService {
}
}
public async deleteProfileData({ dataSource, symbol }: UniqueAsset) {
public async deleteProfileData({
dataSource,
symbol
}: AssetProfileIdentifier) {
await this.marketDataService.deleteMany({ dataSource, symbol });
await this.symbolProfileService.delete({ dataSource, symbol });
}
@ -325,7 +330,7 @@ export class AdminService {
public async getMarketDataBySymbol({
dataSource,
symbol
}: UniqueAsset): Promise<AdminMarketDataDetails> {
}: AssetProfileIdentifier): Promise<AdminMarketDataDetails> {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
@ -386,7 +391,7 @@ export class AdminService {
symbol,
symbolMapping,
url
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
}: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) {
const symbolProfileOverrides = {
assetClass: assetClass as AssetClass,
assetSubClass: assetSubClass as AssetSubClass,
@ -394,28 +399,28 @@ export class AdminService {
url: url as string
};
const updatedSymbolProfile: Prisma.SymbolProfileUpdateInput & UniqueAsset =
{
comment,
countries,
currency,
dataSource,
holdings,
scraperConfiguration,
sectors,
symbol,
symbolMapping,
...(dataSource === 'MANUAL'
? { assetClass, assetSubClass, name, url }
: {
SymbolProfileOverrides: {
upsert: {
create: symbolProfileOverrides,
update: symbolProfileOverrides
}
const updatedSymbolProfile: AssetProfileIdentifier &
Prisma.SymbolProfileUpdateInput = {
comment,
countries,
currency,
dataSource,
holdings,
scraperConfiguration,
sectors,
symbol,
symbolMapping,
...(dataSource === 'MANUAL'
? { assetClass, assetSubClass, name, url }
: {
SymbolProfileOverrides: {
upsert: {
create: symbolProfileOverrides,
update: symbolProfileOverrides
}
})
};
}
})
};
await this.symbolProfileService.updateSymbolProfile(updatedSymbolProfile);

@ -4,9 +4,9 @@ import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import type {
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
BenchmarkResponse,
UniqueAsset
BenchmarkResponse
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
@ -41,7 +41,9 @@ export class BenchmarkController {
@HasPermission(permissions.accessAdminControl)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
public async addBenchmark(
@Body() { dataSource, symbol }: AssetProfileIdentifier
) {
try {
const benchmark = await this.benchmarkService.addBenchmark({
dataSource,

@ -17,11 +17,11 @@ import {
resetHours
} from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
Benchmark,
BenchmarkMarketDataDetails,
BenchmarkProperty,
BenchmarkResponse,
UniqueAsset
BenchmarkResponse
} from '@ghostfolio/common/interfaces';
import { BenchmarkTrend } from '@ghostfolio/common/types';
@ -61,7 +61,10 @@ export class BenchmarkService {
return 0;
}
public async getBenchmarkTrends({ dataSource, symbol }: UniqueAsset) {
public async getBenchmarkTrends({
dataSource,
symbol
}: AssetProfileIdentifier) {
const historicalData = await this.marketDataService.marketDataItems({
orderBy: {
date: 'desc'
@ -228,7 +231,7 @@ export class BenchmarkService {
endDate?: Date;
startDate: Date;
userCurrency: string;
} & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
} & AssetProfileIdentifier): Promise<BenchmarkMarketDataDetails> {
const marketData: { date: string; value: number }[] = [];
const days = differenceInDays(endDate, startDate) + 1;
@ -348,7 +351,7 @@ export class BenchmarkService {
public async addBenchmark({
dataSource,
symbol
}: UniqueAsset): Promise<Partial<SymbolProfile>> {
}: AssetProfileIdentifier): Promise<Partial<SymbolProfile>> {
const assetProfile = await this.prismaService.symbolProfile.findFirst({
where: {
dataSource,
@ -385,7 +388,7 @@ export class BenchmarkService {
public async deleteBenchmark({
dataSource,
symbol
}: UniqueAsset): Promise<Partial<SymbolProfile>> {
}: AssetProfileIdentifier): Promise<Partial<SymbolProfile>> {
const assetProfile = await this.prismaService.symbolProfile.findFirst({
where: {
dataSource,

@ -19,7 +19,7 @@ import {
getAssetProfileIdentifier,
parseDate
} from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import {
AccountWithPlatform,
OrderWithAccount,
@ -51,7 +51,7 @@ export class ImportService {
dataSource,
symbol,
userCurrency
}: UniqueAsset & { userCurrency: string }): Promise<Activity[]> {
}: AssetProfileIdentifier & { userCurrency: string }): Promise<Activity[]> {
try {
const { firstBuyDate, historicalData, orders } =
await this.portfolioService.getPosition(dataSource, undefined, symbol);

@ -1,6 +1,6 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { HttpException, Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
@ -17,7 +17,7 @@ export class LogoService {
public async getLogoByDataSourceAndSymbol({
dataSource,
symbol
}: UniqueAsset) {
}: AssetProfileIdentifier) {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),

@ -11,9 +11,9 @@ import {
} from '@ghostfolio/common/config';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
EnhancedSymbolProfile,
Filter,
UniqueAsset
Filter
} from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types';
@ -51,7 +51,7 @@ export class OrderService {
symbol,
tags,
userId
}: { tags: Tag[]; userId: string } & UniqueAsset) {
}: { tags: Tag[]; userId: string } & AssetProfileIdentifier) {
const orders = await this.prismaService.order.findMany({
where: {
userId,
@ -285,7 +285,7 @@ export class OrderService {
return count;
}
public async getLatestOrder({ dataSource, symbol }: UniqueAsset) {
public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) {
return this.prismaService.order.findFirst({
orderBy: {
date: 'desc'
@ -464,7 +464,7 @@ export class OrderService {
this.prismaService.order.count({ where })
]);
const uniqueAssets = uniqBy(
const assetProfileIdentifiers = uniqBy(
orders.map(({ SymbolProfile }) => {
return {
dataSource: SymbolProfile.dataSource,
@ -479,8 +479,9 @@ export class OrderService {
}
);
const assetProfiles =
await this.symbolProfileService.getSymbolProfiles(uniqueAssets);
const assetProfiles = await this.symbolProfileService.getSymbolProfiles(
assetProfileIdentifiers
);
const activities = orders.map((order) => {
const assetProfile = assetProfiles.find(({ dataSource, symbol }) => {

@ -1,5 +1,8 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
export class MWRPortfolioCalculator extends PortfolioCalculator {
@ -27,7 +30,7 @@ export class MWRPortfolioCalculator extends PortfolioCalculator {
};
start: Date;
step?: number;
} & UniqueAsset): SymbolMetrics {
} & AssetProfileIdentifier): SymbolMetrics {
throw new Error('Method not implemented.');
}
}

@ -19,12 +19,12 @@ import {
resetHours
} from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
DataProviderInfo,
HistoricalDataItem,
InvestmentItem,
ResponseError,
SymbolMetrics,
UniqueAsset
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { DateRange, GroupBy } from '@ghostfolio/common/types';
@ -356,15 +356,15 @@ export abstract class PortfolioCalculator {
dataSource: item.dataSource,
fee: item.fee,
firstBuyDate: item.firstBuyDate,
grossPerformance: !hasErrors ? grossPerformance ?? null : null,
grossPerformance: !hasErrors ? (grossPerformance ?? null) : null,
grossPerformancePercentage: !hasErrors
? grossPerformancePercentage ?? null
? (grossPerformancePercentage ?? null)
: null,
grossPerformancePercentageWithCurrencyEffect: !hasErrors
? grossPerformancePercentageWithCurrencyEffect ?? null
? (grossPerformancePercentageWithCurrencyEffect ?? null)
: null,
grossPerformanceWithCurrencyEffect: !hasErrors
? grossPerformanceWithCurrencyEffect ?? null
? (grossPerformanceWithCurrencyEffect ?? null)
: null,
investment: totalInvestment,
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
@ -372,15 +372,15 @@ export abstract class PortfolioCalculator {
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
marketPriceInBaseCurrency:
marketPriceInBaseCurrency?.toNumber() ?? null,
netPerformance: !hasErrors ? netPerformance ?? null : null,
netPerformance: !hasErrors ? (netPerformance ?? null) : null,
netPerformancePercentage: !hasErrors
? netPerformancePercentage ?? null
? (netPerformancePercentage ?? null)
: null,
netPerformancePercentageWithCurrencyEffect: !hasErrors
? netPerformancePercentageWithCurrencyEffect ?? null
? (netPerformancePercentageWithCurrencyEffect ?? null)
: null,
netPerformanceWithCurrencyEffect: !hasErrors
? netPerformanceWithCurrencyEffect ?? null
? (netPerformanceWithCurrencyEffect ?? null)
: null,
quantity: item.quantity,
symbol: item.symbol,
@ -905,7 +905,7 @@ export abstract class PortfolioCalculator {
};
start: Date;
step?: number;
} & UniqueAsset): SymbolMetrics;
} & AssetProfileIdentifier): SymbolMetrics;
public getTransactionPoints() {
return this.transactionPoints;

@ -2,7 +2,10 @@ import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/po
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { Logger } from '@nestjs/common';
@ -151,7 +154,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
};
start: Date;
step?: number;
} & UniqueAsset): SymbolMetrics {
} & AssetProfileIdentifier): SymbolMetrics {
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: { [date: string]: Big } = {};
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};

@ -1,7 +1,7 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData } from '@prisma/client';
@ -24,32 +24,32 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => {
});
},
getRange: ({
assetProfileIdentifiers,
dateRangeEnd,
dateRangeStart,
uniqueAssets
dateRangeStart
}: {
assetProfileIdentifiers: AssetProfileIdentifier[];
dateRangeEnd: Date;
dateRangeStart: Date;
uniqueAssets: UniqueAsset[];
}) => {
return Promise.resolve<MarketData[]>([
{
createdAt: dateRangeStart,
dataSource: uniqueAssets[0].dataSource,
dataSource: assetProfileIdentifiers[0].dataSource,
date: dateRangeStart,
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
marketPrice: 1841.823902,
state: 'CLOSE',
symbol: uniqueAssets[0].symbol
symbol: assetProfileIdentifiers[0].symbol
},
{
createdAt: dateRangeEnd,
dataSource: uniqueAssets[0].dataSource,
dataSource: assetProfileIdentifiers[0].dataSource,
date: dateRangeEnd,
id: '082d6893-df27-4c91-8a5d-092e84315b56',
marketPrice: 1847.839966,
state: 'CLOSE',
symbol: uniqueAssets[0].symbol
symbol: assetProfileIdentifiers[0].symbol
}
]);
}

@ -3,9 +3,9 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { resetHours } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
DataProviderInfo,
ResponseError,
UniqueAsset
ResponseError
} from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -80,17 +80,16 @@ export class CurrentRateService {
);
}
const uniqueAssets: UniqueAsset[] = dataGatheringItems.map(
({ dataSource, symbol }) => {
const assetProfileIdentifiers: AssetProfileIdentifier[] =
dataGatheringItems.map(({ dataSource, symbol }) => {
return { dataSource, symbol };
}
);
});
promises.push(
this.marketDataService
.getRange({
dateQuery,
uniqueAssets
assetProfileIdentifiers,
dateQuery
})
.then((data) => {
return data.map(({ dataSource, date, marketPrice, symbol }) => {

@ -1,6 +1,6 @@
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
export interface GetValueObject extends UniqueAsset {
export interface GetValueObject extends AssetProfileIdentifier {
date: Date;
marketPrice: number;
}

@ -1,6 +1,6 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger } from '@nestjs/common';
@ -28,7 +28,7 @@ export class RedisCacheService {
return `portfolio-snapshot-${userId}`;
}
public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
public getQuoteKey({ dataSource, symbol }: AssetProfileIdentifier) {
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
}

@ -1,6 +1,9 @@
import { HistoricalDataItem, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
export interface SymbolItem extends UniqueAsset {
export interface SymbolItem extends AssetProfileIdentifier {
currency: string;
historicalData: HistoricalDataItem[];
marketPrice: number;

@ -40,13 +40,13 @@ export class SymbolService {
const days = includeHistoricalData;
const marketData = await this.marketDataService.getRange({
dateQuery: { gte: subDays(new Date(), days) },
uniqueAssets: [
assetProfileIdentifiers: [
{
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
}
]
],
dateQuery: { gte: subDays(new Date(), days) }
});
historicalData = marketData.map(({ date, marketPrice: value }) => {

@ -7,7 +7,7 @@ import {
GATHER_HISTORICAL_MARKET_DATA_PROCESS
} from '@ghostfolio/common/config';
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { Process, Processor } from '@nestjs/bull';
import { Injectable, Logger } from '@nestjs/common';
@ -35,7 +35,7 @@ export class DataGatheringProcessor {
) {}
@Process({ concurrency: 1, name: GATHER_ASSET_PROFILE_PROCESS })
public async gatherAssetProfile(job: Job<UniqueAsset>) {
public async gatherAssetProfile(job: Job<AssetProfileIdentifier>) {
try {
Logger.log(
`Asset profile data gathering has been started for ${job.data.symbol} (${job.data.dataSource})`,

@ -20,7 +20,10 @@ import {
getAssetProfileIdentifier,
resetHours
} from '@ghostfolio/common/helper';
import { BenchmarkProperty, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
BenchmarkProperty
} from '@ghostfolio/common/interfaces';
import { InjectQueue } from '@nestjs/bull';
import { Inject, Injectable, Logger } from '@nestjs/common';
@ -91,7 +94,7 @@ export class DataGatheringService {
});
}
public async gatherSymbol({ dataSource, symbol }: UniqueAsset) {
public async gatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
await this.marketDataService.deleteMany({ dataSource, symbol });
const dataGatheringItems = (await this.getSymbolsMax()).filter(
@ -146,23 +149,29 @@ export class DataGatheringService {
}
}
public async gatherAssetProfiles(aUniqueAssets?: UniqueAsset[]) {
let uniqueAssets = aUniqueAssets?.filter((dataGatheringItem) => {
return dataGatheringItem.dataSource !== 'MANUAL';
});
public async gatherAssetProfiles(
aAssetProfileIdentifiers?: AssetProfileIdentifier[]
) {
let assetProfileIdentifiers = aAssetProfileIdentifiers?.filter(
(dataGatheringItem) => {
return dataGatheringItem.dataSource !== 'MANUAL';
}
);
if (!uniqueAssets) {
uniqueAssets = await this.getAllAssetProfileIdentifiers();
if (!assetProfileIdentifiers) {
assetProfileIdentifiers = await this.getAllAssetProfileIdentifiers();
}
if (uniqueAssets.length <= 0) {
if (assetProfileIdentifiers.length <= 0) {
return;
}
const assetProfiles =
await this.dataProviderService.getAssetProfiles(uniqueAssets);
const symbolProfiles =
await this.symbolProfileService.getSymbolProfiles(uniqueAssets);
const assetProfiles = await this.dataProviderService.getAssetProfiles(
assetProfileIdentifiers
);
const symbolProfiles = await this.symbolProfileService.getSymbolProfiles(
assetProfileIdentifiers
);
for (const [symbol, assetProfile] of Object.entries(assetProfiles)) {
const symbolMapping = symbolProfiles.find((symbolProfile) => {
@ -248,7 +257,7 @@ export class DataGatheringService {
'DataGatheringService'
);
if (uniqueAssets.length === 1) {
if (assetProfileIdentifiers.length === 1) {
throw error;
}
}
@ -284,7 +293,9 @@ export class DataGatheringService {
);
}
public async getAllAssetProfileIdentifiers(): Promise<UniqueAsset[]> {
public async getAllAssetProfileIdentifiers(): Promise<
AssetProfileIdentifier[]
> {
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
orderBy: [{ symbol: 'asc' }]
});
@ -305,7 +316,7 @@ export class DataGatheringService {
}
private async getAssetProfileIdentifiersWithCompleteMarketData(): Promise<
UniqueAsset[]
AssetProfileIdentifier[]
> {
return (
await this.prismaService.marketData.groupBy({

@ -20,7 +20,7 @@ import {
getStartOfUtcDate,
isDerivedCurrency
} from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import type { Granularity, UserWithSettings } from '@ghostfolio/common/types';
import { Inject, Injectable, Logger } from '@nestjs/common';
@ -75,7 +75,7 @@ export class DataProviderService {
return false;
}
public async getAssetProfiles(items: UniqueAsset[]): Promise<{
public async getAssetProfiles(items: AssetProfileIdentifier[]): Promise<{
[symbol: string]: Partial<SymbolProfile>;
}> {
const response: {
@ -173,7 +173,7 @@ export class DataProviderService {
}
public async getHistorical(
aItems: UniqueAsset[],
aItems: AssetProfileIdentifier[],
aGranularity: Granularity = 'month',
from: Date,
to: Date
@ -243,7 +243,7 @@ export class DataProviderService {
from,
to
}: {
dataGatheringItems: UniqueAsset[];
dataGatheringItems: AssetProfileIdentifier[];
from: Date;
to: Date;
}): Promise<{
@ -350,7 +350,7 @@ export class DataProviderService {
useCache = true,
user
}: {
items: UniqueAsset[];
items: AssetProfileIdentifier[];
requestTimeout?: number;
useCache?: boolean;
user?: UserWithSettings;
@ -376,7 +376,7 @@ export class DataProviderService {
}
// Get items from cache
const itemsToFetch: UniqueAsset[] = [];
const itemsToFetch: AssetProfileIdentifier[] = [];
for (const { dataSource, symbol } of items) {
if (useCache) {
@ -633,7 +633,7 @@ export class DataProviderService {
dataGatheringItems
}: {
currency: string;
dataGatheringItems: UniqueAsset[];
dataGatheringItems: AssetProfileIdentifier[];
}) {
return dataGatheringItems.some(({ dataSource, symbol }) => {
return (

@ -361,13 +361,13 @@ export class ExchangeRateDataService {
const symbol = `${currencyFrom}${currencyTo}`;
const marketData = await this.marketDataService.getRange({
dateQuery: { gte: startDate, lt: endDate },
uniqueAssets: [
assetProfileIdentifiers: [
{
dataSource,
symbol
}
]
],
dateQuery: { gte: startDate, lt: endDate }
});
if (marketData?.length > 0) {
@ -392,13 +392,13 @@ export class ExchangeRateDataService {
}
} else {
const marketData = await this.marketDataService.getRange({
dateQuery: { gte: startDate, lt: endDate },
uniqueAssets: [
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyFrom}`
}
]
],
dateQuery: { gte: startDate, lt: endDate }
});
for (const { date, marketPrice } of marketData) {
@ -415,16 +415,16 @@ export class ExchangeRateDataService {
}
} else {
const marketData = await this.marketDataService.getRange({
dateQuery: {
gte: startDate,
lt: endDate
},
uniqueAssets: [
assetProfileIdentifiers: [
{
dataSource,
symbol: `${DEFAULT_CURRENCY}${currencyTo}`
}
]
],
dateQuery: {
gte: startDate,
lt: endDate
}
});
for (const { date, marketPrice } of marketData) {

@ -1,4 +1,7 @@
import { DataProviderInfo, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
DataProviderInfo
} from '@ghostfolio/common/interfaces';
import { MarketState } from '@ghostfolio/common/types';
import {
@ -34,6 +37,6 @@ export interface IDataProviderResponse {
marketState: MarketState;
}
export interface IDataGatheringItem extends UniqueAsset {
export interface IDataGatheringItem extends AssetProfileIdentifier {
date?: Date;
}

@ -3,7 +3,7 @@ import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.i
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { resetHours } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import {
@ -17,7 +17,7 @@ import {
export class MarketDataService {
public constructor(private readonly prismaService: PrismaService) {}
public async deleteMany({ dataSource, symbol }: UniqueAsset) {
public async deleteMany({ dataSource, symbol }: AssetProfileIdentifier) {
return this.prismaService.marketData.deleteMany({
where: {
dataSource,
@ -40,7 +40,7 @@ export class MarketDataService {
});
}
public async getMax({ dataSource, symbol }: UniqueAsset) {
public async getMax({ dataSource, symbol }: AssetProfileIdentifier) {
return this.prismaService.marketData.findFirst({
select: {
date: true,
@ -59,11 +59,11 @@ export class MarketDataService {
}
public async getRange({
dateQuery,
uniqueAssets
assetProfileIdentifiers,
dateQuery
}: {
assetProfileIdentifiers: AssetProfileIdentifier[];
dateQuery: DateQuery;
uniqueAssets: UniqueAsset[];
}): Promise<MarketData[]> {
return this.prismaService.marketData.findMany({
orderBy: [
@ -76,13 +76,13 @@ export class MarketDataService {
],
where: {
dataSource: {
in: uniqueAssets.map(({ dataSource }) => {
in: assetProfileIdentifiers.map(({ dataSource }) => {
return dataSource;
})
},
date: dateQuery,
symbol: {
in: uniqueAssets.map(({ symbol }) => {
in: assetProfileIdentifiers.map(({ symbol }) => {
return symbol;
})
}

@ -1,10 +1,10 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import {
AssetProfileIdentifier,
EnhancedSymbolProfile,
Holding,
ScraperConfiguration,
UniqueAsset
ScraperConfiguration
} from '@ghostfolio/common/interfaces';
import { Country } from '@ghostfolio/common/interfaces/country.interface';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
@ -23,7 +23,7 @@ export class SymbolProfileService {
return this.prismaService.symbolProfile.create({ data: assetProfile });
}
public async delete({ dataSource, symbol }: UniqueAsset) {
public async delete({ dataSource, symbol }: AssetProfileIdentifier) {
return this.prismaService.symbolProfile.delete({
where: { dataSource_symbol: { dataSource, symbol } }
});
@ -36,7 +36,7 @@ export class SymbolProfileService {
}
public async getSymbolProfiles(
aUniqueAssets: UniqueAsset[]
aAssetProfileIdentifiers: AssetProfileIdentifier[]
): Promise<EnhancedSymbolProfile[]> {
return this.prismaService.symbolProfile
.findMany({
@ -54,7 +54,7 @@ export class SymbolProfileService {
SymbolProfileOverrides: true
},
where: {
OR: aUniqueAssets.map(({ dataSource, symbol }) => {
OR: aAssetProfileIdentifiers.map(({ dataSource, symbol }) => {
return {
dataSource,
symbol
@ -140,7 +140,7 @@ export class SymbolProfileService {
symbolMapping,
SymbolProfileOverrides,
url
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
}: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) {
return this.prismaService.symbolProfile.update({
data: {
assetClass,

@ -7,9 +7,9 @@ import {
} from '@ghostfolio/common/config';
import { getDateFormatString } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
Filter,
InfoItem,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
@ -225,7 +225,7 @@ export class AdminMarketDataComponent
});
}
public onDeleteAssetProfile({ dataSource, symbol }: UniqueAsset) {
public onDeleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
}
@ -266,21 +266,27 @@ export class AdminMarketDataComponent
.subscribe(() => {});
}
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
public onGatherProfileDataBySymbol({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.adminService
.gatherProfileDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
this.adminService
.gatherSymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onOpenAssetProfileDialog({ dataSource, symbol }: UniqueAsset) {
public onOpenAssetProfileDialog({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.router.navigate([], {
queryParams: {
dataSource,

@ -2,8 +2,8 @@ import { AdminService } from '@ghostfolio/client/services/admin.service';
import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import {
AdminMarketDataItem,
UniqueAsset
AssetProfileIdentifier,
AdminMarketDataItem
} from '@ghostfolio/common/interfaces';
import { Injectable } from '@angular/core';
@ -13,7 +13,7 @@ import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs';
export class AdminMarketDataService {
public constructor(private adminService: AdminService) {}
public deleteAssetProfile({ dataSource, symbol }: UniqueAsset) {
public deleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
const confirmation = confirm(
$localize`Do you really want to delete this asset profile?`
);
@ -29,15 +29,19 @@ export class AdminMarketDataService {
}
}
public deleteAssetProfiles(uniqueAssets: UniqueAsset[]) {
public deleteAssetProfiles(
aAssetProfileIdentifiers: AssetProfileIdentifier[]
) {
const confirmation = confirm(
$localize`Do you really want to delete these profiles?`
);
if (confirmation) {
const deleteRequests = uniqueAssets.map(({ dataSource, symbol }) => {
return this.adminService.deleteProfileData({ dataSource, symbol });
});
const deleteRequests = aAssetProfileIdentifiers.map(
({ dataSource, symbol }) => {
return this.adminService.deleteProfileData({ dataSource, symbol });
}
);
forkJoin(deleteRequests)
.pipe(

@ -8,7 +8,7 @@ import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AdminMarketDataDetails,
UniqueAsset
AssetProfileIdentifier
} from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n';
@ -175,20 +175,23 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.dialogRef.close();
}
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
public onDeleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) {
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
this.dialogRef.close();
}
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
public onGatherProfileDataBySymbol({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.adminService
.gatherProfileDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
this.adminService
.gatherSymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
@ -242,7 +245,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
}
}
public onSetBenchmark({ dataSource, symbol }: UniqueAsset) {
public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
this.dataService
.postBenchmark({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
@ -342,7 +345,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
});
}
public onUnsetBenchmark({ dataSource, symbol }: UniqueAsset) {
public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
this.dataService
.deleteBenchmark({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))

@ -2,8 +2,8 @@ import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
AssetProfileIdentifier,
PortfolioPosition,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -108,7 +108,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
this.initialize();
}
public onSymbolClicked({ dataSource, symbol }: UniqueAsset) {
public onSymbolClicked({ dataSource, symbol }: AssetProfileIdentifier) {
if (dataSource && symbol) {
this.router.navigate([], {
queryParams: { dataSource, symbol, holdingDetailDialog: true }

@ -5,9 +5,9 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { NUMERICAL_PRECISION_THRESHOLD } from '@ghostfolio/common/config';
import {
AssetProfileIdentifier,
LineChartItem,
PortfolioPerformance,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -26,7 +26,7 @@ import { takeUntil } from 'rxjs/operators';
export class HomeOverviewComponent implements OnDestroy, OnInit {
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
public deviceType: string;
public errors: UniqueAsset[];
public errors: AssetProfileIdentifier[];
public hasError: boolean;
public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean;

@ -38,6 +38,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces';
export class ImportActivitiesDialog implements OnDestroy {
public accounts: CreateAccountDto[] = [];
public activities: Activity[] = [];
public assetProfileForm: FormGroup;
public dataSource: MatTableDataSource<Activity>;
public details: any[] = [];
public deviceType: string;
@ -53,7 +54,6 @@ export class ImportActivitiesDialog implements OnDestroy {
public sortDirection: SortDirection = 'desc';
public stepperOrientation: StepperOrientation;
public totalItems: number;
public uniqueAssetForm: FormGroup;
private unsubscribeSubject = new Subject<void>();
@ -73,8 +73,8 @@ export class ImportActivitiesDialog implements OnDestroy {
this.stepperOrientation =
this.deviceType === 'mobile' ? 'vertical' : 'horizontal';
this.uniqueAssetForm = this.formBuilder.group({
uniqueAsset: [undefined, Validators.required]
this.assetProfileForm = this.formBuilder.group({
assetProfileIdentifier: [undefined, Validators.required]
});
if (
@ -85,7 +85,7 @@ export class ImportActivitiesDialog implements OnDestroy {
this.dialogTitle = $localize`Import Dividends`;
this.mode = 'DIVIDEND';
this.uniqueAssetForm.get('uniqueAsset').disable();
this.assetProfileForm.get('assetProfileIdentifier').disable();
this.dataService
.fetchPortfolioHoldings({
@ -102,7 +102,7 @@ export class ImportActivitiesDialog implements OnDestroy {
this.holdings = sortBy(holdings, ({ name }) => {
return name.toLowerCase();
});
this.uniqueAssetForm.get('uniqueAsset').enable();
this.assetProfileForm.get('assetProfileIdentifier').enable();
this.isLoading = false;
@ -167,10 +167,11 @@ export class ImportActivitiesDialog implements OnDestroy {
}
public onLoadDividends(aStepper: MatStepper) {
this.uniqueAssetForm.get('uniqueAsset').disable();
this.assetProfileForm.get('assetProfileIdentifier').disable();
const { dataSource, symbol } =
this.uniqueAssetForm.get('uniqueAsset').value;
const { dataSource, symbol } = this.assetProfileForm.get(
'assetProfileIdentifier'
).value;
this.dataService
.fetchDividendsImport({
@ -193,7 +194,7 @@ export class ImportActivitiesDialog implements OnDestroy {
this.details = [];
this.errorMessages = [];
this.importStep = ImportStep.SELECT_ACTIVITIES;
this.uniqueAssetForm.get('uniqueAsset').enable();
this.assetProfileForm.get('assetProfileIdentifier').enable();
aStepper.reset();
}

@ -25,14 +25,14 @@
<div class="pt-3">
@if (mode === 'DIVIDEND') {
<form
[formGroup]="uniqueAssetForm"
[formGroup]="assetProfileForm"
(ngSubmit)="onLoadDividends(stepper)"
>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Holding</mat-label>
<mat-select formControlName="uniqueAsset">
<mat-select formControlName="assetProfileIdentifier">
<mat-select-trigger>{{
uniqueAssetForm.get('uniqueAsset')?.value?.name
assetProfileForm.get('assetProfileIdentifier')?.value?.name
}}</mat-select-trigger>
@for (holding of holdings; track holding) {
<mat-option
@ -63,7 +63,7 @@
color="primary"
mat-flat-button
type="submit"
[disabled]="!uniqueAssetForm.valid"
[disabled]="!assetProfileForm.valid"
>
<span i18n>Load Dividends</span>
</button>

@ -6,10 +6,10 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { prettifySymbol } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
Holding,
PortfolioDetails,
PortfolioPosition,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { Market, MarketAdvanced } from '@ghostfolio/common/types';
@ -161,7 +161,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.initialize();
}
public onAccountChartClicked({ symbol }: UniqueAsset) {
public onAccountChartClicked({ symbol }: AssetProfileIdentifier) {
if (symbol && symbol !== UNKNOWN_KEY) {
this.router.navigate([], {
queryParams: { accountId: symbol, accountDetailDialog: true }
@ -169,7 +169,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
}
public onSymbolChartClicked({ dataSource, symbol }: UniqueAsset) {
public onSymbolChartClicked({ dataSource, symbol }: AssetProfileIdentifier) {
if (dataSource && symbol) {
this.router.navigate([], {
queryParams: { dataSource, symbol, holdingDetailDialog: true }

@ -7,13 +7,13 @@ import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
AdminData,
AdminJobs,
AdminMarketData,
AdminMarketDataDetails,
EnhancedSymbolProfile,
Filter,
UniqueAsset
Filter
} from '@ghostfolio/common/interfaces';
import { HttpClient, HttpParams } from '@angular/common/http';
@ -35,7 +35,7 @@ export class AdminService {
private http: HttpClient
) {}
public addAssetProfile({ dataSource, symbol }: UniqueAsset) {
public addAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
return this.http.post<void>(
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
null
@ -62,7 +62,7 @@ export class AdminService {
return this.http.delete<void>(`/api/v1/platform/${aId}`);
}
public deleteProfileData({ dataSource, symbol }: UniqueAsset) {
public deleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) {
return this.http.delete<void>(
`/api/v1/admin/profile-data/${dataSource}/${symbol}`
);
@ -167,7 +167,10 @@ export class AdminService {
return this.http.post<void>('/api/v1/admin/gather/profile-data', {});
}
public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
public gatherProfileDataBySymbol({
dataSource,
symbol
}: AssetProfileIdentifier) {
return this.http.post<void>(
`/api/v1/admin/gather/profile-data/${dataSource}/${symbol}`,
{}
@ -178,7 +181,7 @@ export class AdminService {
dataSource,
date,
symbol
}: UniqueAsset & {
}: AssetProfileIdentifier & {
date?: Date;
}) {
let url = `/api/v1/admin/gather/${dataSource}/${symbol}`;
@ -217,7 +220,7 @@ export class AdminService {
symbol,
symbolMapping,
url
}: UniqueAsset & UpdateAssetProfileDto) {
}: AssetProfileIdentifier & UpdateAssetProfileDto) {
return this.http.patch<EnhancedSymbolProfile>(
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
{
@ -272,7 +275,7 @@ export class AdminService {
dataSource,
scraperConfiguration,
symbol
}: UniqueAsset & UpdateAssetProfileDto['scraperConfiguration']) {
}: AssetProfileIdentifier & UpdateAssetProfileDto['scraperConfiguration']) {
return this.http.post<any>(
`/api/v1/admin/market-data/${dataSource}/${symbol}/test`,
{

@ -20,6 +20,7 @@ import {
AccountBalancesResponse,
Accounts,
AdminMarketDataDetails,
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
BenchmarkResponse,
Export,
@ -34,7 +35,6 @@ import {
PortfolioPerformanceResponse,
PortfolioPublicDetails,
PortfolioReport,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
@ -230,7 +230,7 @@ export class DataService {
});
}
public fetchDividendsImport({ dataSource, symbol }: UniqueAsset) {
public fetchDividendsImport({ dataSource, symbol }: AssetProfileIdentifier) {
return this.http.get<ImportResponse>(
`/api/v1/import/dividends/${dataSource}/${symbol}`
);
@ -270,7 +270,7 @@ export class DataService {
return this.http.delete<any>(`/api/v1/order/${aId}`);
}
public deleteBenchmark({ dataSource, symbol }: UniqueAsset) {
public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
}
@ -289,7 +289,7 @@ export class DataService {
public fetchAsset({
dataSource,
symbol
}: UniqueAsset): Observable<AdminMarketDataDetails> {
}: AssetProfileIdentifier): Observable<AdminMarketDataDetails> {
return this.http.get<any>(`/api/v1/asset/${dataSource}/${symbol}`).pipe(
map((data) => {
for (const item of data.marketData) {
@ -308,7 +308,7 @@ export class DataService {
}: {
range: DateRange;
startDate: Date;
} & UniqueAsset): Observable<BenchmarkMarketDataDetails> {
} & AssetProfileIdentifier): Observable<BenchmarkMarketDataDetails> {
let params = new HttpParams();
if (range) {
@ -630,7 +630,7 @@ export class DataService {
);
}
public postBenchmark(benchmark: UniqueAsset) {
public postBenchmark(benchmark: AssetProfileIdentifier) {
return this.http.post(`/api/v1/benchmark`, benchmark);
}
@ -654,7 +654,7 @@ export class DataService {
dataSource,
symbol,
tags
}: { tags: Tag[] } & UniqueAsset) {
}: { tags: Tag[] } & AssetProfileIdentifier) {
return this.http.put<void>(
`/api/v1/portfolio/position/${dataSource}/${symbol}/tags`,
{ tags }

@ -19,7 +19,7 @@ import {
ghostfolioScraperApiSymbolPrefix,
locale
} from './config';
import { Benchmark, UniqueAsset } from './interfaces';
import { AssetProfileIdentifier, Benchmark } from './interfaces';
import { BenchmarkTrend, ColorScheme } from './types';
export const DATE_FORMAT = 'yyyy-MM-dd';
@ -147,7 +147,10 @@ export function getAllActivityTypes(): ActivityType[] {
return Object.values(ActivityType);
}
export function getAssetProfileIdentifier({ dataSource, symbol }: UniqueAsset) {
export function getAssetProfileIdentifier({
dataSource,
symbol
}: AssetProfileIdentifier) {
return `${dataSource}-${symbol}`;
}
@ -377,7 +380,7 @@ export function parseDate(date: string): Date | null {
return parseISO(date);
}
export function parseSymbol({ dataSource, symbol }: UniqueAsset) {
export function parseSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
const [ticker, exchange] = symbol.split('.');
return {

@ -1,13 +1,13 @@
import { Role } from '@prisma/client';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { UniqueAsset } from './unique-asset.interface';
import { Role } from '@prisma/client';
export interface AdminData {
exchangeRates: ({
label1: string;
label2: string;
value: number;
} & UniqueAsset)[];
} & AssetProfileIdentifier)[];
settings: { [key: string]: boolean | object | string | string[] };
transactionCount: number;
userCount: number;

@ -1,6 +1,6 @@
import { DataSource } from '@prisma/client';
export interface UniqueAsset {
export interface AssetProfileIdentifier {
dataSource: DataSource;
symbol: string;
}

@ -7,6 +7,7 @@ import type {
AdminMarketData,
AdminMarketDataItem
} from './admin-market-data.interface';
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
import type { BenchmarkProperty } from './benchmark-property.interface';
import type { Benchmark } from './benchmark.interface';
@ -48,7 +49,6 @@ import type { Subscription } from './subscription.interface';
import type { SymbolMetrics } from './symbol-metrics.interface';
import type { SystemMessage } from './system-message.interface';
import type { TabConfiguration } from './tab-configuration.interface';
import type { UniqueAsset } from './unique-asset.interface';
import type { UserSettings } from './user-settings.interface';
import type { User } from './user.interface';
@ -61,6 +61,7 @@ export {
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem,
AssetProfileIdentifier,
Benchmark,
BenchmarkMarketDataDetails,
BenchmarkProperty,
@ -101,7 +102,6 @@ export {
Subscription,
SymbolMetrics,
TabConfiguration,
UniqueAsset,
User,
UserSettings
};

@ -1,6 +1,6 @@
import { UniqueAsset } from '../unique-asset.interface';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
export interface ResponseError {
errors?: UniqueAsset[];
errors?: AssetProfileIdentifier[];
hasErrors: boolean;
}

@ -1,5 +1,5 @@
import { transformToBig } from '@ghostfolio/common/class-transformer';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { Big } from 'big.js';
@ -9,7 +9,7 @@ export class PortfolioSnapshot {
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
currentValueInBaseCurrency: Big;
errors?: UniqueAsset[];
errors?: AssetProfileIdentifier[];
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)

@ -3,7 +3,7 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { getDateFormatString, getLocale } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { GfActivityTypeComponent } from '@ghostfolio/ui/activity-type';
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
@ -99,7 +99,7 @@ export class GfActivitiesTableComponent
@Output() export = new EventEmitter<void>();
@Output() exportDrafts = new EventEmitter<string[]>();
@Output() import = new EventEmitter<void>();
@Output() importDividends = new EventEmitter<UniqueAsset>();
@Output() importDividends = new EventEmitter<AssetProfileIdentifier>();
@Output() pageChanged = new EventEmitter<PageEvent>();
@Output() selectedActivities = new EventEmitter<Activity[]>();
@Output() sortChanged = new EventEmitter<Sort>();
@ -263,7 +263,7 @@ export class GfActivitiesTableComponent
alert(aComment);
}
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset) {
public onOpenPositionDialog({ dataSource, symbol }: AssetProfileIdentifier) {
this.router.navigate([], {
queryParams: { dataSource, symbol, holdingDetailDialog: true }
});

@ -1,4 +1,4 @@
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
export interface IDateRangeOption {
@ -6,7 +6,7 @@ export interface IDateRangeOption {
value: DateRange;
}
export interface ISearchResultItem extends UniqueAsset {
export interface ISearchResultItem extends AssetProfileIdentifier {
assetSubClassString: string;
currency: string;
name: string;

@ -1,5 +1,9 @@
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
import { Benchmark, UniqueAsset, User } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
Benchmark,
User
} from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n';
import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value';
@ -84,7 +88,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
}
}
public onOpenBenchmarkDialog({ dataSource, symbol }: UniqueAsset) {
public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) {
this.router.navigate([], {
queryParams: { dataSource, symbol, benchmarkDetailDialog: true }
});
@ -95,7 +99,10 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
this.unsubscribeSubject.complete();
}
private openBenchmarkDetailDialog({ dataSource, symbol }: UniqueAsset) {
private openBenchmarkDetailDialog({
dataSource,
symbol
}: AssetProfileIdentifier) {
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, {
data: <BenchmarkDetailDialogParams>{
dataSource,

@ -2,7 +2,10 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset
import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { getLocale } from '@ghostfolio/common/helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
PortfolioPosition
} from '@ghostfolio/common/interfaces';
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
import { GfValueComponent } from '@ghostfolio/ui/value';
@ -102,7 +105,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
}
}
public onOpenHoldingDialog({ dataSource, symbol }: UniqueAsset) {
public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) {
if (this.hasPermissionToOpenDetails) {
this.router.navigate([], {
queryParams: { dataSource, symbol, holdingDetailDialog: true }

@ -1,7 +1,10 @@
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getLocale, getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
PortfolioPosition
} from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
@ -71,7 +74,7 @@ export class GfPortfolioProportionChartComponent
};
} = {};
@Output() proportionChartClicked = new EventEmitter<UniqueAsset>();
@Output() proportionChartClicked = new EventEmitter<AssetProfileIdentifier>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;

@ -1,5 +1,8 @@
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import {
AssetProfileIdentifier,
PortfolioPosition
} from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common';
import {
@ -40,7 +43,7 @@ export class GfTreemapChartComponent
@Input() cursor: string;
@Input() holdings: PortfolioPosition[];
@Output() treemapChartClicked = new EventEmitter<UniqueAsset>();
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;

Loading…
Cancel
Save