|
|
@ -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 { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.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 { |
|
|
|
getAssetProfileIdentifier, |
|
|
|
parseDate |
|
|
|
} from '@ghostfolio/common/helper'; |
|
|
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; |
|
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
|
|
|
import { |
|
|
|
AccountWithPlatform, |
|
|
|
OrderWithAccount, |
|
|
@ -46,7 +48,8 @@ export class ImportService { |
|
|
|
private readonly orderService: OrderService, |
|
|
|
private readonly platformService: PlatformService, |
|
|
|
private readonly portfolioService: PortfolioService, |
|
|
|
private readonly symbolProfileService: SymbolProfileService |
|
|
|
private readonly symbolProfileService: SymbolProfileService, |
|
|
|
private readonly tagService: TagService |
|
|
|
) {} |
|
|
|
|
|
|
|
public async getDividends({ |
|
|
@ -154,6 +157,7 @@ export class ImportService { |
|
|
|
assetProfilesWithMarketDataDto, |
|
|
|
isDryRun = false, |
|
|
|
maxActivitiesToImport, |
|
|
|
tagsDto, |
|
|
|
user |
|
|
|
}: { |
|
|
|
accountsWithBalancesDto: ImportDataDto['accounts']; |
|
|
@ -161,10 +165,12 @@ export class ImportService { |
|
|
|
assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; |
|
|
|
isDryRun?: boolean; |
|
|
|
maxActivitiesToImport: number; |
|
|
|
tagsDto: ImportDataDto['tags']; |
|
|
|
user: UserWithSettings; |
|
|
|
}): Promise<Activity[]> { |
|
|
|
const accountIdMapping: { [oldAccountId: string]: string } = {}; |
|
|
|
const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {}; |
|
|
|
const tagIdMapping: { [oldTagId: string]: string } = {}; |
|
|
|
const userCurrency = user.settings.settings.baseCurrency; |
|
|
|
|
|
|
|
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) { |
|
|
|
if (!activity.dataSource) { |
|
|
|
if (['FEE', 'INTEREST', 'LIABILITY'].includes(activity.type)) { |
|
|
@ -313,6 +363,11 @@ export class ImportService { |
|
|
|
if (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[] = []; |
|
|
|
|
|
|
|
for (const activity of activitiesExtendedWithErrors) { |
|
|
@ -351,6 +424,7 @@ export class ImportService { |
|
|
|
const fee = activity.fee; |
|
|
|
const quantity = activity.quantity; |
|
|
|
const SymbolProfile = activity.SymbolProfile; |
|
|
|
const tagIds = activity.tagIds ?? []; |
|
|
|
const type = activity.type; |
|
|
|
const unitPrice = activity.unitPrice; |
|
|
|
|
|
|
@ -388,11 +462,17 @@ export class ImportService { |
|
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
|
return id === accountId; |
|
|
|
}); |
|
|
|
const validatedTags = tags.filter(({ id: tagId }) => { |
|
|
|
return tagIds.some((activityTagId) => { |
|
|
|
return activityTagId === tagId; |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
let order: |
|
|
|
| OrderWithAccount |
|
|
|
| (Omit<OrderWithAccount, 'Account'> & { |
|
|
|
Account?: { id: string; name: string }; |
|
|
|
| (Omit<OrderWithAccount, 'account' | 'tags'> & { |
|
|
|
account?: { id: string; name: string }; |
|
|
|
tags?: { id: string; name: string }[]; |
|
|
|
}); |
|
|
|
|
|
|
|
if (isDryRun) { |
|
|
@ -404,7 +484,7 @@ export class ImportService { |
|
|
|
quantity, |
|
|
|
type, |
|
|
|
unitPrice, |
|
|
|
Account: validatedAccount, |
|
|
|
account: validatedAccount, |
|
|
|
accountId: validatedAccount?.id, |
|
|
|
accountUserId: undefined, |
|
|
|
createdAt: new Date(), |
|
|
@ -436,6 +516,7 @@ export class ImportService { |
|
|
|
userId: dataSource === 'MANUAL' ? user.id : undefined |
|
|
|
}, |
|
|
|
symbolProfileId: undefined, |
|
|
|
tags: validatedTags, |
|
|
|
updatedAt: new Date(), |
|
|
|
userId: user.id |
|
|
|
}; |
|
|
@ -469,6 +550,9 @@ export class ImportService { |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
tags: validatedTags.map(({ id }) => { |
|
|
|
return { id }; |
|
|
|
}), |
|
|
|
updateAccountBalance: false, |
|
|
|
user: { connect: { id: user.id } }, |
|
|
|
userId: user.id |
|
|
@ -546,6 +630,7 @@ export class ImportService { |
|
|
|
fee, |
|
|
|
quantity, |
|
|
|
symbol, |
|
|
|
tags, |
|
|
|
type, |
|
|
|
unitPrice |
|
|
|
}) => { |
|
|
@ -594,7 +679,8 @@ export class ImportService { |
|
|
|
isActive: true, |
|
|
|
sectors: undefined, |
|
|
|
updatedAt: undefined |
|
|
|
} |
|
|
|
}, |
|
|
|
tagIds: tags |
|
|
|
}; |
|
|
|
} |
|
|
|
); |
|
|
|