You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

258 lines
6.4 KiB

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 {
ExportResponse,
Filter,
UserSettings
} from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { Platform, Prisma } from '@prisma/client';
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
) {}
public async export({
activityIds,
filters,
userId,
userSettings
}: {
activityIds?: string[];
filters?: Filter[];
userId: string;
userSettings: UserSettings;
}): Promise<ExportResponse> {
const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => {
return type;
});
const platformsMap: { [platformId: string]: Platform } = {};
let { activities } = await this.orderService.getOrders({
filters,
userId,
includeDrafts: true,
sortColumn: 'date',
sortDirection: 'asc',
userCurrency: userSettings?.baseCurrency,
withExcludedAccountsAndActivities: true
});
if (activityIds?.length > 0) {
activities = activities.filter(({ id }) => {
return activityIds.includes(id);
});
}
const where: Prisma.AccountWhereInput = { userId };
if (filtersByAccount?.length > 0) {
where.id = {
in: filtersByAccount.map(({ id }) => {
return id;
})
};
}
const accounts = (
await this.accountService.accounts({
where,
include: {
balances: true,
platform: true
},
orderBy: {
name: 'asc'
}
})
)
.filter(({ id }) => {
return activityIds?.length > 0
? activities.some(({ accountId }) => {
return accountId === id;
})
: true;
})
.map(
({
balance,
balances,
comment,
currency,
id,
isExcluded,
name,
platform,
platformId
}) => {
if (platformId) {
platformsMap[platformId] = platform;
}
return {
balance,
balances: balances.map(({ date, value }) => {
return { date: date.toISOString(), value };
}),
comment,
currency,
id,
isExcluded,
name,
platformId
};
}
);
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 }) => {
return (
isUsed &&
activities.some((activity) => {
return activity.tags.some(({ id: tagId }) => {
return tagId === id;
});
})
);
})
.map(({ id, name }) => {
return {
id,
name
};
});
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,
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(
({
accountId,
comment,
currency,
date,
fee,
id,
quantity,
SymbolProfile,
tags: currentTags,
type,
unitPrice
}) => {
return {
accountId,
comment,
fee,
id,
quantity,
type,
unitPrice,
currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toISOString(),
symbol: SymbolProfile.symbol,
tags: currentTags.map(({ id: tagId }) => {
return tagId;
})
};
}
),
user: {
settings: {
currency: userSettings?.baseCurrency,
performanceCalculationType: userSettings?.performanceCalculationType
}
}
};
}
}