Browse Source

Feature/extend activities import by tags (#5287)

* Extend activities import by tags

* Update changelog
pull/5330/head^2
Attila Cseh 4 days ago
committed by GitHub
parent
commit
7613b54514
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 4
      apps/api/src/app/endpoints/tags/create-tag.dto.ts
  3. 7
      apps/api/src/app/import/import-data.dto.ts
  4. 1
      apps/api/src/app/import/import.controller.ts
  5. 2
      apps/api/src/app/import/import.module.ts
  6. 96
      apps/api/src/app/import/import.service.ts
  7. 10
      apps/api/src/app/order/create-order.dto.ts
  8. 1
      apps/api/src/app/order/interfaces/activities.interface.ts
  9. 6
      apps/api/src/app/order/order.controller.ts
  10. 12
      apps/api/src/app/order/order.service.ts
  11. 10
      apps/api/src/app/order/update-order.dto.ts
  12. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  13. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  14. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  15. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  16. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  17. 9
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  18. 26
      apps/client/src/app/services/import-activities.service.ts

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Extended the import functionality by tags
## 2.189.0 - 2025-08-05 ## 2.189.0 - 2025-08-05
### Changed ### Changed

4
apps/api/src/app/endpoints/tags/create-tag.dto.ts

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

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

@ -3,6 +3,7 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsArray, IsOptional, ValidateNested } from 'class-validator'; import { IsArray, IsOptional, ValidateNested } from 'class-validator';
import { CreateTagDto } from '../endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto'; import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto';
@ -23,4 +24,10 @@ export class ImportDataDto {
@Type(() => CreateAssetProfileWithMarketDataDto) @Type(() => CreateAssetProfileWithMarketDataDto)
@ValidateNested({ each: true }) @ValidateNested({ each: true })
assetProfiles?: CreateAssetProfileWithMarketDataDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[];
@IsArray()
@IsOptional()
@Type(() => CreateTagDto)
@ValidateNested({ each: true })
tags?: CreateTagDto[];
} }

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

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

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

@ -13,6 +13,7 @@ import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-da
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
@ -35,6 +36,7 @@ import { ImportService } from './import.service';
PrismaModule, PrismaModule,
RedisCacheModule, RedisCacheModule,
SymbolProfileModule, SymbolProfileModule,
TagModule,
TransformDataSourceInRequestModule, TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule TransformDataSourceInResponseModule
], ],

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

@ -13,12 +13,14 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config'; import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import { import {
getAssetProfileIdentifier, getAssetProfileIdentifier,
parseDate parseDate
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { import {
AccountWithPlatform, AccountWithPlatform,
OrderWithAccount, OrderWithAccount,
@ -46,7 +48,8 @@ export class ImportService {
private readonly orderService: OrderService, private readonly orderService: OrderService,
private readonly platformService: PlatformService, private readonly platformService: PlatformService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
private readonly symbolProfileService: SymbolProfileService private readonly symbolProfileService: SymbolProfileService,
private readonly tagService: TagService
) {} ) {}
public async getDividends({ public async getDividends({
@ -154,6 +157,7 @@ export class ImportService {
assetProfilesWithMarketDataDto, assetProfilesWithMarketDataDto,
isDryRun = false, isDryRun = false,
maxActivitiesToImport, maxActivitiesToImport,
tagsDto,
user user
}: { }: {
accountsWithBalancesDto: ImportDataDto['accounts']; accountsWithBalancesDto: ImportDataDto['accounts'];
@ -161,10 +165,12 @@ export class ImportService {
assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles'];
isDryRun?: boolean; isDryRun?: boolean;
maxActivitiesToImport: number; maxActivitiesToImport: number;
tagsDto: ImportDataDto['tags'];
user: UserWithSettings; user: UserWithSettings;
}): Promise<Activity[]> { }): Promise<Activity[]> {
const accountIdMapping: { [oldAccountId: string]: string } = {}; const accountIdMapping: { [oldAccountId: string]: string } = {};
const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {}; const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {};
const tagIdMapping: { [oldTagId: string]: string } = {};
const userCurrency = user.settings.settings.baseCurrency; const userCurrency = user.settings.settings.baseCurrency;
if (!isDryRun && accountsWithBalancesDto?.length) { if (!isDryRun && accountsWithBalancesDto?.length) {
@ -293,6 +299,50 @@ export class ImportService {
} }
} }
if (tagsDto?.length) {
const existingTagsOfUser = await this.tagService.getTagsForUser(user.id);
const canCreateOwnTag = hasPermission(
user.permissions,
permissions.createOwnTag
);
for (const tag of tagsDto) {
const existingTagOfUser = existingTagsOfUser.find(({ id }) => {
return id === tag.id;
});
if (!existingTagOfUser || existingTagOfUser.userId !== null) {
if (!canCreateOwnTag) {
throw new Error(
`Insufficient permissions to create custom tag ("${tag.name}")`
);
}
if (!isDryRun) {
const existingTag = await this.tagService.getTag({ id: tag.id });
let oldTagId: string;
if (existingTag) {
oldTagId = tag.id;
delete tag.id;
}
const tagObject: Prisma.TagCreateInput = {
...tag,
user: { connect: { id: user.id } }
};
const newTag = await this.tagService.createTag(tagObject);
if (existingTag && oldTagId) {
tagIdMapping[oldTagId] = newTag.id;
}
}
}
}
}
for (const activity of activitiesDto) { for (const activity of activitiesDto) {
if (!activity.dataSource) { if (!activity.dataSource) {
if (['FEE', 'INTEREST', 'LIABILITY'].includes(activity.type)) { if (['FEE', 'INTEREST', 'LIABILITY'].includes(activity.type)) {
@ -313,6 +363,11 @@ export class ImportService {
if (assetProfileSymbolMapping[activity.symbol]) { if (assetProfileSymbolMapping[activity.symbol]) {
activity.symbol = assetProfileSymbolMapping[activity.symbol]; activity.symbol = assetProfileSymbolMapping[activity.symbol];
} }
// If a new tag is created, then update the tag ID in all activities
activity.tags = (activity.tags ?? []).map((tagId) => {
return tagIdMapping[tagId] ?? tagId;
});
} }
} }
@ -340,6 +395,24 @@ export class ImportService {
}); });
} }
const tags = (await this.tagService.getTagsForUser(user.id)).map(
({ id, name }) => {
return { id, name };
}
);
if (isDryRun) {
tagsDto
.filter(({ id }) => {
return !tags.some(({ id: tagId }) => {
return tagId === id;
});
})
.forEach(({ id, name }) => {
tags.push({ id, name });
});
}
const activities: Activity[] = []; const activities: Activity[] = [];
for (const activity of activitiesExtendedWithErrors) { for (const activity of activitiesExtendedWithErrors) {
@ -351,6 +424,7 @@ export class ImportService {
const fee = activity.fee; const fee = activity.fee;
const quantity = activity.quantity; const quantity = activity.quantity;
const SymbolProfile = activity.SymbolProfile; const SymbolProfile = activity.SymbolProfile;
const tagIds = activity.tagIds ?? [];
const type = activity.type; const type = activity.type;
const unitPrice = activity.unitPrice; const unitPrice = activity.unitPrice;
@ -388,11 +462,17 @@ export class ImportService {
const validatedAccount = accounts.find(({ id }) => { const validatedAccount = accounts.find(({ id }) => {
return id === accountId; return id === accountId;
}); });
const validatedTags = tags.filter(({ id: tagId }) => {
return tagIds.some((activityTagId) => {
return activityTagId === tagId;
});
});
let order: let order:
| OrderWithAccount | OrderWithAccount
| (Omit<OrderWithAccount, 'Account'> & { | (Omit<OrderWithAccount, 'account' | 'tags'> & {
Account?: { id: string; name: string }; account?: { id: string; name: string };
tags?: { id: string; name: string }[];
}); });
if (isDryRun) { if (isDryRun) {
@ -404,7 +484,7 @@ export class ImportService {
quantity, quantity,
type, type,
unitPrice, unitPrice,
Account: validatedAccount, account: validatedAccount,
accountId: validatedAccount?.id, accountId: validatedAccount?.id,
accountUserId: undefined, accountUserId: undefined,
createdAt: new Date(), createdAt: new Date(),
@ -436,6 +516,7 @@ export class ImportService {
userId: dataSource === 'MANUAL' ? user.id : undefined userId: dataSource === 'MANUAL' ? user.id : undefined
}, },
symbolProfileId: undefined, symbolProfileId: undefined,
tags: validatedTags,
updatedAt: new Date(), updatedAt: new Date(),
userId: user.id userId: user.id
}; };
@ -469,6 +550,9 @@ export class ImportService {
} }
} }
}, },
tags: validatedTags.map(({ id }) => {
return { id };
}),
updateAccountBalance: false, updateAccountBalance: false,
user: { connect: { id: user.id } }, user: { connect: { id: user.id } },
userId: user.id userId: user.id
@ -546,6 +630,7 @@ export class ImportService {
fee, fee,
quantity, quantity,
symbol, symbol,
tags,
type, type,
unitPrice unitPrice
}) => { }) => {
@ -594,7 +679,8 @@ export class ImportService {
isActive: true, isActive: true,
sectors: undefined, sectors: undefined,
updatedAt: undefined updatedAt: undefined
} },
tagIds: tags
}; };
} }
); );

10
apps/api/src/app/order/create-order.dto.ts

@ -1,13 +1,7 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
AssetClass,
AssetSubClass,
DataSource,
Tag,
Type
} from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer'; import { Transform, TransformFnParams } from 'class-transformer';
import { import {
IsArray, IsArray,
@ -70,7 +64,7 @@ export class CreateOrderDto {
@IsArray() @IsArray()
@IsOptional() @IsOptional()
tags?: Tag[]; tags?: string[];
@IsEnum(Type, { each: true }) @IsEnum(Type, { each: true })
type: Type; type: Type;

1
apps/api/src/app/order/interfaces/activities.interface.ts

@ -14,6 +14,7 @@ export interface Activity extends Order {
feeInAssetProfileCurrency: number; feeInAssetProfileCurrency: number;
feeInBaseCurrency: number; feeInBaseCurrency: number;
SymbolProfile?: EnhancedSymbolProfile; SymbolProfile?: EnhancedSymbolProfile;
tagIds?: string[];
tags?: Tag[]; tags?: Tag[];
unitPriceInAssetProfileCurrency: number; unitPriceInAssetProfileCurrency: number;
updateAccountBalance?: boolean; updateAccountBalance?: boolean;

6
apps/api/src/app/order/order.controller.ts

@ -217,6 +217,9 @@ export class OrderController {
} }
} }
}, },
tags: data.tags?.map((id) => {
return { id };
}),
user: { connect: { id: this.request.user.id } }, user: { connect: { id: this.request.user.id } },
userId: this.request.user.id userId: this.request.user.id
}); });
@ -293,6 +296,9 @@ export class OrderController {
name: data.symbol name: data.symbol
} }
}, },
tags: data.tags?.map((id) => {
return { id };
}),
user: { connect: { id: this.request.user.id } } user: { connect: { id: this.request.user.id } }
}, },
where: { where: {

12
apps/api/src/app/order/order.service.ts

@ -97,7 +97,7 @@ export class OrderService {
assetSubClass?: AssetSubClass; assetSubClass?: AssetSubClass;
currency?: string; currency?: string;
symbol?: string; symbol?: string;
tags?: Tag[]; tags?: { id: string }[];
updateAccountBalance?: boolean; updateAccountBalance?: boolean;
userId: string; userId: string;
} }
@ -201,9 +201,7 @@ export class OrderService {
account, account,
isDraft, isDraft,
tags: { tags: {
connect: tags.map(({ id }) => { connect: tags
return { id };
})
} }
}, },
include: { SymbolProfile: true } include: { SymbolProfile: true }
@ -658,7 +656,7 @@ export class OrderService {
assetSubClass?: AssetSubClass; assetSubClass?: AssetSubClass;
currency?: string; currency?: string;
symbol?: string; symbol?: string;
tags?: Tag[]; tags?: { id: string }[];
type?: ActivityType; type?: ActivityType;
}; };
where: Prisma.OrderWhereUniqueInput; where: Prisma.OrderWhereUniqueInput;
@ -720,9 +718,7 @@ export class OrderService {
...data, ...data,
isDraft, isDraft,
tags: { tags: {
connect: tags.map(({ id }) => { connect: tags
return { id };
})
} }
} }
}); });

10
apps/api/src/app/order/update-order.dto.ts

@ -1,13 +1,7 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
AssetClass,
AssetSubClass,
DataSource,
Tag,
Type
} from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer'; import { Transform, TransformFnParams } from 'class-transformer';
import { import {
IsArray, IsArray,
@ -71,7 +65,7 @@ export class UpdateOrderDto {
@IsArray() @IsArray()
@IsOptional() @IsOptional()
tags?: Tag[]; tags?: string[];
@IsString() @IsString()
type: Type; type: Type;

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { join } from 'path'; import { join } from 'path';
@ -108,6 +109,9 @@ describe('PortfolioCalculator', () => {
name: 'Bitcoin', name: 'Bitcoin',
symbol: activity.symbol symbol: activity.symbol
}, },
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: 44558.42 unitPriceInAssetProfileCurrency: 44558.42
})); }));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { join } from 'path'; import { join } from 'path';
@ -108,6 +109,9 @@ describe('PortfolioCalculator', () => {
name: 'Bitcoin', name: 'Bitcoin',
symbol: activity.symbol symbol: activity.symbol
}, },
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: 44558.42 unitPriceInAssetProfileCurrency: 44558.42
})); }));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { join } from 'path'; import { join } from 'path';
@ -111,6 +112,9 @@ describe('PortfolioCalculator', () => {
name: 'Novartis AG', name: 'Novartis AG',
symbol: activity.symbol symbol: activity.symbol
}, },
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: activity.unitPrice unitPriceInAssetProfileCurrency: activity.unitPrice
})); }));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { join } from 'path'; import { join } from 'path';
@ -111,6 +112,9 @@ describe('PortfolioCalculator', () => {
name: 'Novartis AG', name: 'Novartis AG',
symbol: activity.symbol symbol: activity.symbol
}, },
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: activity.unitPrice unitPriceInAssetProfileCurrency: activity.unitPrice
})); }));

4
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -495,7 +495,9 @@ export class GfCreateOrUpdateActivityDialog implements OnDestroy {
? undefined ? undefined
: this.activityForm.get('searchSymbol')?.value?.symbol) ?? : this.activityForm.get('searchSymbol')?.value?.symbol) ??
this.activityForm.get('name')?.value, this.activityForm.get('name')?.value,
tags: this.activityForm.get('tags').value, tags: this.activityForm.get('tags').value?.map(({ id }) => {
return id;
}),
type: this.activityForm.get('type').value, type: this.activityForm.get('type').value,
unitPrice: this.activityForm.get('unitPrice').value unitPrice: this.activityForm.get('unitPrice').value
}; };

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

@ -1,3 +1,4 @@
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
@ -94,6 +95,7 @@ export class GfImportActivitiesDialog implements OnDestroy {
public sortColumn = 'date'; public sortColumn = 'date';
public sortDirection: SortDirection = 'desc'; public sortDirection: SortDirection = 'desc';
public stepperOrientation: StepperOrientation; public stepperOrientation: StepperOrientation;
public tags: CreateTagDto[] = [];
public totalItems: number; public totalItems: number;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -169,7 +171,8 @@ export class GfImportActivitiesDialog implements OnDestroy {
await this.importActivitiesService.importSelectedActivities({ await this.importActivitiesService.importSelectedActivities({
accounts: this.accounts, accounts: this.accounts,
activities: this.selectedActivities, activities: this.selectedActivities,
assetProfiles: this.assetProfiles assetProfiles: this.assetProfiles,
tags: this.tags
}); });
this.snackBar.open( this.snackBar.open(
@ -297,6 +300,7 @@ export class GfImportActivitiesDialog implements OnDestroy {
this.accounts = content.accounts; this.accounts = content.accounts;
this.assetProfiles = content.assetProfiles; this.assetProfiles = content.assetProfiles;
this.tags = content.tags;
if (!isArray(content.activities)) { if (!isArray(content.activities)) {
if (isArray(content.orders)) { if (isArray(content.orders)) {
@ -328,7 +332,8 @@ export class GfImportActivitiesDialog implements OnDestroy {
accounts: content.accounts, accounts: content.accounts,
activities: content.activities, activities: content.activities,
assetProfiles: content.assetProfiles, assetProfiles: content.assetProfiles,
isDryRun: true isDryRun: true,
tags: content.tags
}); });
this.activities = activities; this.activities = activities;
this.dataSource = new MatTableDataSource(activities.reverse()); this.dataSource = new MatTableDataSource(activities.reverse());

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

@ -1,3 +1,4 @@
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
@ -75,12 +76,14 @@ export class ImportActivitiesService {
accounts, accounts,
activities, activities,
assetProfiles, assetProfiles,
isDryRun = false isDryRun = false,
tags
}: { }: {
activities: CreateOrderDto[]; activities: CreateOrderDto[];
accounts?: CreateAccountWithBalancesDto[]; accounts?: CreateAccountWithBalancesDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[];
isDryRun?: boolean; isDryRun?: boolean;
tags?: CreateTagDto[];
}): Promise<{ }): Promise<{
activities: Activity[]; activities: Activity[];
}> { }> {
@ -89,7 +92,8 @@ export class ImportActivitiesService {
{ {
accounts, accounts,
activities, activities,
assetProfiles assetProfiles,
tags
}, },
isDryRun isDryRun
) )
@ -110,11 +114,13 @@ export class ImportActivitiesService {
public importSelectedActivities({ public importSelectedActivities({
accounts, accounts,
activities, activities,
assetProfiles assetProfiles,
tags
}: { }: {
accounts?: CreateAccountWithBalancesDto[]; accounts?: CreateAccountWithBalancesDto[];
activities: Activity[]; activities: Activity[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[];
tags?: CreateTagDto[];
}): Promise<{ }): Promise<{
activities: Activity[]; activities: Activity[];
}> { }> {
@ -124,7 +130,12 @@ export class ImportActivitiesService {
importData.push(this.convertToCreateOrderDto(activity)); importData.push(this.convertToCreateOrderDto(activity));
} }
return this.importJson({ accounts, assetProfiles, activities: importData }); return this.importJson({
accounts,
assetProfiles,
tags,
activities: importData
});
} }
private convertToCreateOrderDto({ private convertToCreateOrderDto({
@ -135,6 +146,7 @@ export class ImportActivitiesService {
fee, fee,
quantity, quantity,
SymbolProfile, SymbolProfile,
tags,
type, type,
unitPrice, unitPrice,
updateAccountBalance updateAccountBalance
@ -150,7 +162,10 @@ export class ImportActivitiesService {
currency: currency ?? SymbolProfile.currency, currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource, dataSource: SymbolProfile.dataSource,
date: date.toString(), date: date.toString(),
symbol: SymbolProfile.symbol symbol: SymbolProfile.symbol,
tags: tags?.map(({ id }) => {
return id;
})
}; };
} }
@ -391,6 +406,7 @@ export class ImportActivitiesService {
accounts?: CreateAccountWithBalancesDto[]; accounts?: CreateAccountWithBalancesDto[];
activities: CreateOrderDto[]; activities: CreateOrderDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[];
tags?: CreateTagDto[];
}, },
aIsDryRun = false aIsDryRun = false
) { ) {

Loading…
Cancel
Save