diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 09f7da05f..5d3574fbf 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -536,7 +536,6 @@ export class ImportService { const assetProfilesToCreate: { assetProfile: Prisma.SymbolProfileCreateInput; - assetProfileIdentifier: string; marketDataObjects: Prisma.MarketDataUpdateInput[]; }[] = []; @@ -644,11 +643,7 @@ export class ImportService { // is known which activities are imported assetProfilesToCreate.push({ marketDataObjects, - assetProfile: assetProfileToCreate, - assetProfileIdentifier: getAssetProfileIdentifier({ - symbol, - dataSource: assetProfileWithMarketData.dataSource - }) + assetProfile: assetProfileToCreate }); } else { // Insert or update market data @@ -738,35 +733,21 @@ export class ImportService { return id === TAG_ID_DRAFT; }) ?? { id: TAG_ID_DRAFT, name: 'DRAFT' }; - if (assetProfilesToCreate.length) { - // Create the new asset profiles of the activities to import only, so - // that no unused asset profile remains, for example if an activity is a - // duplicate. An asset profile which is created before the validation of - // the activities would stay behind, because the import is not rolled - // back on an error. - const assetProfileIdentifiersToImport = new Set( - activitiesExtendedWithErrors - .filter(({ error }) => { - return !error; - }) - .map(({ assetProfile }) => { - return getAssetProfileIdentifier(assetProfile); - }) - ); - - for (const { - assetProfile, - assetProfileIdentifier, - marketDataObjects - } of assetProfilesToCreate) { - if (!assetProfileIdentifiersToImport.has(assetProfileIdentifier)) { - continue; - } - - await this.symbolProfileService.add(assetProfile); - - await this.marketDataService.updateMany({ data: marketDataObjects }); - } + // Create the new asset profiles of the activities to import only, so that + // no unused asset profile remains, for example if an activity is a + // duplicate. An asset profile which is created before the validation of + // the activities would stay behind, because the import is not rolled back + // on an error. + for (const { + assetProfile, + marketDataObjects + } of this.getAssetProfilesToCreate({ + activities: activitiesExtendedWithErrors, + assetProfiles: assetProfilesToCreate + })) { + await this.symbolProfileService.add(assetProfile); + + await this.marketDataService.updateMany({ data: marketDataObjects }); } const activities: Activity[] = []; @@ -1105,6 +1086,39 @@ export class ImportService { return matchingAccountsOfUser[0]; } + /** + * Returns the asset profiles which at least one activity of the import uses. + * The asset profiles are created after the validation of the activities, + * thus an asset profile of an activity which is not imported must not be + * created. + */ + private getAssetProfilesToCreate({ + activities, + assetProfiles + }: { + activities: Partial[]; + assetProfiles: { + assetProfile: Prisma.SymbolProfileCreateInput; + marketDataObjects: Prisma.MarketDataUpdateInput[]; + }[]; + }) { + const assetProfileIdentifiersToImport = new Set( + activities + .filter(({ error }) => { + return !error; + }) + .map(({ assetProfile }) => { + return getAssetProfileIdentifier(assetProfile); + }) + ); + + return assetProfiles.filter(({ assetProfile }) => { + return assetProfileIdentifiersToImport.has( + getAssetProfileIdentifier(assetProfile) + ); + }); + } + private isUniqueAccount(accounts: AccountWithValue[]) { const uniqueAccountIds = new Set(); diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 7e05dea49..102b80d56 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -39,7 +39,7 @@ import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client'; import { Big } from 'big.js'; import { eachDayOfInterval, format, isValid } from 'date-fns'; -import { groupBy, isEmpty, isNumber, uniqWith } from 'lodash'; +import { groupBy, isEmpty, isNumber, omit, uniqWith } from 'lodash'; import ms from 'ms'; import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error'; @@ -272,19 +272,22 @@ export class DataProviderService implements OnModuleInit { }); if (!assetProfiles[assetProfileIdentifier]) { + const assetProfileInImport = assetProfilesWithMarketDataDto?.find( + (assetProfile) => { + return ( + assetProfile.dataSource === dataSource && + assetProfile.symbol === symbol + ); + } + ); + + // A custom asset profile of the import is created after the + // validation, thus the data provider cannot resolve it yet if ( - (dataSource === DataSource.MANUAL && type === 'BUY') || + (dataSource === DataSource.MANUAL && + (type === 'BUY' || Boolean(assetProfileInImport))) || NON_INVESTMENT_ACTIVITY_TYPES.includes(type) ) { - const assetProfileInImport = assetProfilesWithMarketDataDto?.find( - (assetProfile) => { - return ( - assetProfile.dataSource === dataSource && - assetProfile.symbol === symbol - ); - } - ); - assetProfiles[assetProfileIdentifier] = { currency, dataSource, @@ -309,18 +312,10 @@ export class DataProviderService implements OnModuleInit { )?.[assetProfileIdentifier] ?? assetProfile; } catch {} - if (!assetProfile?.name) { - const assetProfileInImport = assetProfilesWithMarketDataDto?.find( - (profile) => { - return ( - profile.dataSource === dataSource && profile.symbol === symbol - ); - } - ); - - if (assetProfileInImport) { - Object.assign(assetProfile, assetProfileInImport); - } + if (!assetProfile?.name && assetProfileInImport) { + // Omit the market data, since it must not become part of the + // asset profile of the response + Object.assign(assetProfile, omit(assetProfileInImport, 'marketData')); } if (!assetProfile?.name) {