|
|
|
|
@ -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;
|
|
|
|
|
})
|
|
|
|
|
|