Browse Source

Fix unused custom asset profiles created by activities import

pull/7673/head
Thomas Kaul 1 week ago
parent
commit
724eef1987
  1. 84
      apps/api/src/app/import/import.service.ts
  2. 41
      apps/api/src/services/data-provider/data-provider.service.ts

84
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<Activity>[];
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<string>();

41
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) {

Loading…
Cancel
Save