diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe9d8de5..73de1483b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the check for duplicates in the preview step of the activities import (regardless of the account) - Improved the check for duplicates in the preview step of the import dividends dialog (regardless of the account) - Extended the activities import to reuse an existing account of the user by name and currency +- Extended the activities import to resolve an ISIN to the symbol of the data provider ### Fixed diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index a9cde1544..1bc164686 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -40,6 +40,7 @@ import { import { Injectable } from '@nestjs/common'; import { Account, DataSource, Prisma } from '@prisma/client'; import { Big } from 'big.js'; +import { isISIN } from 'class-validator'; import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns'; import { omit, uniqBy } from 'lodash'; import { randomUUID } from 'node:crypto'; @@ -240,6 +241,59 @@ export class ImportService { } } + // Resolve the symbols with an ISIN to the symbol of the data provider + // before the asset profiles are created and the duplicates are detected + const assetProfileIdentifiersWithIsin: AssetProfileIdentifier[] = uniqBy( + [...(assetProfilesWithMarketDataDto ?? []), ...activitiesDto] + .filter(({ dataSource, symbol }) => { + return dataSource !== DataSource.MANUAL && isISIN(symbol); + }) + .map(({ dataSource, symbol }) => { + return { dataSource, symbol }; + }), + getAssetProfileIdentifier + ); + + const resolvedAssetProfileIdentifiers = await Promise.all( + assetProfileIdentifiersWithIsin.map(async ({ dataSource, symbol }) => { + try { + const assetProfile = await this.dataProviderService + .getDataProvider(dataSource) + .getAssetProfile({ symbol }); + + return { dataSource, symbol, resolvedSymbol: assetProfile?.symbol }; + } catch { + return { dataSource, symbol, resolvedSymbol: undefined }; + } + }) + ); + + for (const { + dataSource, + resolvedSymbol, + symbol + } of resolvedAssetProfileIdentifiers) { + if (!resolvedSymbol || resolvedSymbol === symbol) { + continue; + } + + for (const activity of activitiesDto) { + if (activity.dataSource === dataSource && activity.symbol === symbol) { + activity.symbol = resolvedSymbol; + } + } + + for (const assetProfileWithMarketData of assetProfilesWithMarketDataDto ?? + []) { + if ( + assetProfileWithMarketData.dataSource === dataSource && + assetProfileWithMarketData.symbol === symbol + ) { + assetProfileWithMarketData.symbol = resolvedSymbol; + } + } + } + if (platformsDto?.length) { const canCreatePlatform = hasPermission( user.permissions,