Browse Source

Feature/extend export by custom asset profiles (#5165)

* Extend export by custom asset profiles

* Update changelog
pull/5198/head
Attila Cseh 3 days ago
committed by GitHub
parent
commit
806df6c0c5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 2
      apps/api/src/app/export/export.module.ts
  3. 89
      apps/api/src/app/export/export.service.ts
  4. 1
      libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts
  5. 14
      libs/common/src/lib/interfaces/export.interface.ts

1
CHANGELOG.md

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Extended the export functionality by custom asset profiles
- Improved the platform icon in the create or update platform dialog of the admin control - Improved the platform icon in the create or update platform dialog of the admin control
- Localized the durations of the coupon system - Localized the durations of the coupon system
- Refactored the resources pages to standalone - Refactored the resources pages to standalone

2
apps/api/src/app/export/export.module.ts

@ -2,6 +2,7 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module';
import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
@ -14,6 +15,7 @@ import { ExportService } from './export.service';
imports: [ imports: [
AccountModule, AccountModule,
ApiModule, ApiModule,
MarketDataModule,
OrderModule, OrderModule,
TagModule, TagModule,
TransformDataSourceInRequestModule TransformDataSourceInRequestModule

89
apps/api/src/app/export/export.service.ts

@ -1,17 +1,19 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { environment } from '@ghostfolio/api/environments/environment'; 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 { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { Filter, Export } from '@ghostfolio/common/interfaces'; import { Filter, Export } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Platform, Prisma } from '@prisma/client'; import { Platform, Prisma } from '@prisma/client';
import { groupBy } from 'lodash'; import { groupBy, uniqBy } from 'lodash';
@Injectable() @Injectable()
export class ExportService { export class ExportService {
public constructor( public constructor(
private readonly accountService: AccountService, private readonly accountService: AccountService,
private readonly marketDataService: MarketDataService,
private readonly orderService: OrderService, private readonly orderService: OrderService,
private readonly tagService: TagService 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)) const tags = (await this.tagService.getTagsForUser(userId))
.filter( .filter(
({ id, isUsed }) => ({ id, isUsed }) =>
@ -128,6 +160,55 @@ export class ExportService {
return { return {
meta: { date: new Date().toISOString(), version: environment.version }, meta: { date: new Date().toISOString(), version: environment.version },
accounts, 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), platforms: Object.values(platformsMap),
tags, tags,
activities: activities.map( activities: activities.map(
@ -155,11 +236,7 @@ export class ExportService {
currency: currency ?? SymbolProfile.currency, currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource, dataSource: SymbolProfile.dataSource,
date: date.toISOString(), date: date.toISOString(),
symbol: symbol: SymbolProfile.symbol,
['FEE', 'INTEREST', 'LIABILITY'].includes(type) ||
(SymbolProfile.dataSource === 'MANUAL' && type === 'BUY')
? SymbolProfile.name
: SymbolProfile.symbol,
tags: currentTags.map(({ id: tagId }) => { tags: currentTags.map(({ id: tagId }) => {
return tagId; return tagId;
}) })

1
libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts

@ -14,6 +14,7 @@ export interface EnhancedSymbolProfile {
countries: Country[]; countries: Country[];
createdAt: Date; createdAt: Date;
currency?: string; currency?: string;
cusip?: string;
dataProviderInfo?: DataProviderInfo; dataProviderInfo?: DataProviderInfo;
dataSource: DataSource; dataSource: DataSource;
dateOfFirstActivity?: Date; dateOfFirstActivity?: Date;

14
libs/common/src/lib/interfaces/export.interface.ts

@ -1,4 +1,11 @@
import { Account, Order, Platform, Tag } from '@prisma/client'; import {
Account,
DataSource,
Order,
Platform,
SymbolProfile,
Tag
} from '@prisma/client';
import { AccountBalance } from './account-balance.interface'; import { AccountBalance } from './account-balance.interface';
@ -15,7 +22,10 @@ export interface Export {
| 'symbolProfileId' | 'symbolProfileId'
| 'updatedAt' | 'updatedAt'
| 'userId' | 'userId'
> & { date: string; symbol: string })[]; > & { dataSource: DataSource; date: string; symbol: string })[];
assetProfiles: (Omit<SymbolProfile, 'createdAt' | 'updatedAt' | 'userId'> & {
marketData: { date: string; marketPrice: number }[];
})[];
meta: { meta: {
date: string; date: string;
version: string; version: string;

Loading…
Cancel
Save