Browse Source

Feature/support account platforms in activities import (#7515)

* Support account platforms in activities import

* Update changelog
pull/7526/head
Thomas Kaul 5 days ago
committed by GitHub
parent
commit
8ac551fc44
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 7
      apps/api/src/app/import/import-data.dto.ts
  3. 1
      apps/api/src/app/import/import.controller.ts
  4. 44
      apps/api/src/app/import/import.service.ts
  5. 5
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  6. 8
      apps/client/src/app/services/import-activities.service.ts
  7. 6
      libs/common/src/lib/dtos/create-platform.dto.ts
  8. 42
      test/import/not-ok/invalid-platform.json

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added support for the account platforms in the activities import
- Added the database model and endpoints to manage the stock splits of an asset profile (experimental)
### Changed

7
apps/api/src/app/import/import-data.dto.ts

@ -2,6 +2,7 @@ import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreateOrderDto,
CreatePlatformDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
@ -26,6 +27,12 @@ export class ImportDataDto {
@ValidateNested({ each: true })
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
@IsArray()
@IsOptional()
@Type(() => CreatePlatformDto)
@ValidateNested({ each: true })
platforms?: CreatePlatformDto[];
@IsArray()
@IsOptional()
@Type(() => CreateTagDto)

1
apps/api/src/app/import/import.controller.ts

@ -77,6 +77,7 @@ export class ImportController {
accountsWithBalancesDto: importData.accounts ?? [],
activitiesDto: importData.activities,
assetProfilesWithMarketDataDto: importData.assetProfiles ?? [],
platformsDto: importData.platforms ?? [],
tagsDto: importData.tags ?? [],
user: this.request.user
});

44
apps/api/src/app/import/import.service.ts

@ -177,6 +177,7 @@ export class ImportService {
assetProfilesWithMarketDataDto,
isDryRun = false,
maxActivitiesToImport,
platformsDto,
tagsDto,
user
}: {
@ -185,14 +186,54 @@ export class ImportService {
assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles'];
isDryRun?: boolean;
maxActivitiesToImport: number;
platformsDto: ImportDataDto['platforms'];
tagsDto: ImportDataDto['tags'];
user: UserWithSettings;
}): Promise<Activity[]> {
const accountIdMapping: { [oldAccountId: string]: string } = {};
const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {};
const platformIdMapping: { [oldPlatformId: string]: string } = {};
const tagIdMapping: { [oldTagId: string]: string } = {};
const userCurrency = user.settings.settings.baseCurrency;
if (platformsDto?.length) {
const canCreatePlatform = hasPermission(
user.permissions,
permissions.createPlatform
);
const existingPlatforms = await this.platformService.getPlatforms();
for (const platform of platformsDto) {
// Check if there is any existing platform with the same ID, otherwise
// fall back to a platform with the same URL
const existingPlatform =
existingPlatforms.find(({ id }) => {
return id === platform.id;
}) ??
existingPlatforms.find(({ url }) => {
return url === platform.url;
});
if (existingPlatform) {
// Store the new to old platform ID mappings for creating accounts
if (platform.id && existingPlatform.id !== platform.id) {
platformIdMapping[platform.id] = existingPlatform.id;
}
} else {
if (!canCreatePlatform) {
throw new Error(
`Insufficient permissions to create platform ("${platform.name}")`
);
}
if (!isDryRun) {
await this.platformService.createPlatform(platform);
}
}
}
}
const existingTagsOfUser =
tagsDto?.length || (!isDryRun && accountsWithBalancesDto?.length)
? await this.tagService.getTagsForUser(user.id)
@ -282,7 +323,8 @@ export class ImportService {
]);
let oldAccountId: string;
const platformId = account.platformId;
const platformId =
platformIdMapping[account.platformId] ?? account.platformId;
delete account.platformId;

5
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -4,6 +4,7 @@ import { DEFAULT_DATE_RANGE } from '@ghostfolio/common/config';
import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreatePlatformDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces';
@ -107,6 +108,7 @@ export class GfImportActivitiesDialogComponent {
private accounts: CreateAccountWithBalancesDto[] = [];
private activities: Activity[] = [];
private assetProfiles: CreateAssetProfileWithMarketDataDto[] = [];
private platforms: CreatePlatformDto[] = [];
private tags: CreateTagDto[] = [];
private readonly changeDetectorRef = inject(ChangeDetectorRef);
@ -173,6 +175,7 @@ export class GfImportActivitiesDialogComponent {
accounts: this.accounts,
activities: this.selectedActivities,
assetProfiles: this.assetProfiles,
platforms: this.platforms,
tags: this.tags
});
@ -304,6 +307,7 @@ export class GfImportActivitiesDialogComponent {
this.accounts = content.accounts;
this.assetProfiles = content.assetProfiles;
this.platforms = content.platforms;
this.tags = content.tags;
if (!isArray(content.activities)) {
@ -337,6 +341,7 @@ export class GfImportActivitiesDialogComponent {
activities: content.activities,
assetProfiles: content.assetProfiles,
isDryRun: true,
platforms: content.platforms,
tags: content.tags
});

8
apps/client/src/app/services/import-activities.service.ts

@ -2,6 +2,7 @@ import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreateOrderDto,
CreatePlatformDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
import { parseDate as parseDateHelper } from '@ghostfolio/common/helper';
@ -115,12 +116,14 @@ export class ImportActivitiesService {
activities,
assetProfiles,
isDryRun = false,
platforms,
tags
}: {
activities: CreateOrderDto[];
accounts?: CreateAccountWithBalancesDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
isDryRun?: boolean;
platforms?: CreatePlatformDto[];
tags?: CreateTagDto[];
}): Promise<{
activities: Activity[];
@ -131,6 +134,7 @@ export class ImportActivitiesService {
accounts,
activities,
assetProfiles,
platforms,
tags
},
isDryRun
@ -142,11 +146,13 @@ export class ImportActivitiesService {
accounts,
activities,
assetProfiles,
platforms,
tags
}: {
accounts?: CreateAccountWithBalancesDto[];
activities: Activity[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
platforms?: CreatePlatformDto[];
tags?: CreateTagDto[];
}): Promise<{
activities: Activity[];
@ -158,6 +164,7 @@ export class ImportActivitiesService {
return this.importJson({
accounts,
assetProfiles,
platforms,
tags,
activities: importData
});
@ -457,6 +464,7 @@ export class ImportActivitiesService {
accounts?: CreateAccountWithBalancesDto[];
activities: CreateOrderDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
platforms?: CreatePlatformDto[];
tags?: CreateTagDto[];
},
aIsDryRun = false

6
libs/common/src/lib/dtos/create-platform.dto.ts

@ -1,6 +1,10 @@
import { IsString, IsUrl } from 'class-validator';
import { IsOptional, IsString, IsUrl } from 'class-validator';
export class CreatePlatformDto {
@IsOptional()
@IsString()
id?: string;
@IsString()
name: string;

42
test/import/not-ok/invalid-platform.json

@ -0,0 +1,42 @@
{
"meta": {
"date": "2023-02-05T00:00:00.000Z",
"version": "dev"
},
"accounts": [
{
"balance": 0,
"balances": [],
"currency": "USD",
"id": "e62be662-a2c8-4cff-8b79-dc0a46576659",
"name": "Lehman Brothers Account",
"platformId": "2807e8e7-3966-4ced-8d82-0fa2d9c97277"
}
],
"platforms": [
{
"id": "2807e8e7-3966-4ced-8d82-0fa2d9c97277",
"name": "Lehman Brothers",
"url": "https://lehmanbrothers.com"
}
],
"activities": [
{
"accountId": "e62be662-a2c8-4cff-8b79-dc0a46576659",
"fee": 19,
"quantity": 5,
"type": "BUY",
"unitPrice": 298.58,
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-09-16T00:00:00.000Z",
"symbol": "MSFT"
}
],
"user": {
"settings": {
"currency": "USD",
"performanceCalculationType": "ROAI"
}
}
}
Loading…
Cancel
Save