|
|
@ -1,17 +1,19 @@ |
|
|
|
import { AccountService } from '@ghostfolio/api/app/account/account.service'; |
|
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
|
import { environment } from '@ghostfolio/api/environments/environment'; |
|
|
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; |
|
|
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service'; |
|
|
|
import { Filter, Export } from '@ghostfolio/common/interfaces'; |
|
|
|
|
|
|
|
import { Injectable } from '@nestjs/common'; |
|
|
|
import { Platform, Prisma } from '@prisma/client'; |
|
|
|
import { groupBy } from 'lodash'; |
|
|
|
import { groupBy, uniqBy } from 'lodash'; |
|
|
|
|
|
|
|
@Injectable() |
|
|
|
export class ExportService { |
|
|
|
public constructor( |
|
|
|
private readonly accountService: AccountService, |
|
|
|
private readonly marketDataService: MarketDataService, |
|
|
|
private readonly orderService: OrderService, |
|
|
|
private readonly tagService: TagService |
|
|
|
) {} |
|
|
@ -108,6 +110,36 @@ export class ExportService { |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
const customAssetProfiles = uniqBy( |
|
|
|
activities |
|
|
|
.map(({ SymbolProfile }) => { |
|
|
|
return SymbolProfile; |
|
|
|
}) |
|
|
|
.filter(({ userId: assetProfileUserId }) => { |
|
|
|
return assetProfileUserId === userId; |
|
|
|
}), |
|
|
|
({ id }) => { |
|
|
|
return id; |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
const marketDataByAssetProfile = Object.fromEntries( |
|
|
|
await Promise.all( |
|
|
|
customAssetProfiles.map(async ({ dataSource, id, symbol }) => { |
|
|
|
const marketData = ( |
|
|
|
await this.marketDataService.marketDataItems({ |
|
|
|
where: { dataSource, symbol } |
|
|
|
}) |
|
|
|
).map(({ date, marketPrice }) => ({ |
|
|
|
date: date.toISOString(), |
|
|
|
marketPrice |
|
|
|
})); |
|
|
|
|
|
|
|
return [id, marketData] as const; |
|
|
|
}) |
|
|
|
) |
|
|
|
); |
|
|
|
|
|
|
|
const tags = (await this.tagService.getTagsForUser(userId)) |
|
|
|
.filter( |
|
|
|
({ id, isUsed }) => |
|
|
@ -128,6 +160,55 @@ export class ExportService { |
|
|
|
return { |
|
|
|
meta: { date: new Date().toISOString(), version: environment.version }, |
|
|
|
accounts, |
|
|
|
assetProfiles: customAssetProfiles.map( |
|
|
|
({ |
|
|
|
assetClass, |
|
|
|
assetSubClass, |
|
|
|
comment, |
|
|
|
countries, |
|
|
|
currency, |
|
|
|
cusip, |
|
|
|
dataSource, |
|
|
|
figi, |
|
|
|
figiComposite, |
|
|
|
figiShareClass, |
|
|
|
holdings, |
|
|
|
id, |
|
|
|
isActive, |
|
|
|
isin, |
|
|
|
name, |
|
|
|
scraperConfiguration, |
|
|
|
sectors, |
|
|
|
symbol, |
|
|
|
symbolMapping, |
|
|
|
url |
|
|
|
}) => { |
|
|
|
return { |
|
|
|
assetClass, |
|
|
|
assetSubClass, |
|
|
|
comment, |
|
|
|
countries: countries as unknown as Prisma.JsonArray, |
|
|
|
currency, |
|
|
|
cusip, |
|
|
|
dataSource, |
|
|
|
figi, |
|
|
|
figiComposite, |
|
|
|
figiShareClass, |
|
|
|
holdings: holdings as unknown as Prisma.JsonArray, |
|
|
|
id, |
|
|
|
isActive, |
|
|
|
isin, |
|
|
|
marketData: marketDataByAssetProfile[id], |
|
|
|
name, |
|
|
|
scraperConfiguration: |
|
|
|
scraperConfiguration as unknown as Prisma.JsonArray, |
|
|
|
sectors: sectors as unknown as Prisma.JsonArray, |
|
|
|
symbol, |
|
|
|
symbolMapping, |
|
|
|
url |
|
|
|
}; |
|
|
|
} |
|
|
|
), |
|
|
|
platforms: Object.values(platformsMap), |
|
|
|
tags, |
|
|
|
activities: activities.map( |
|
|
@ -155,11 +236,7 @@ export class ExportService { |
|
|
|
currency: currency ?? SymbolProfile.currency, |
|
|
|
dataSource: SymbolProfile.dataSource, |
|
|
|
date: date.toISOString(), |
|
|
|
symbol: |
|
|
|
['FEE', 'INTEREST', 'LIABILITY'].includes(type) || |
|
|
|
(SymbolProfile.dataSource === 'MANUAL' && type === 'BUY') |
|
|
|
? SymbolProfile.name |
|
|
|
: SymbolProfile.symbol, |
|
|
|
symbol: SymbolProfile.symbol, |
|
|
|
tags: currentTags.map(({ id: tagId }) => { |
|
|
|
return tagId; |
|
|
|
}) |
|
|
|