Browse Source

Improve check for duplicates

pull/7541/head
Thomas Kaul 3 weeks ago
parent
commit
1eb63d3a65
  1. 132
      apps/api/src/app/import/import.service.ts

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

@ -99,7 +99,9 @@ export class ImportService {
filters, filters,
userCurrency, userCurrency,
userId, userId,
startDate: parseDate(dateOfFirstActivity) includeDrafts: true,
startDate: parseDate(dateOfFirstActivity),
withExcludedAccountsAndActivities: true
}), }),
this.symbolProfileService.getSymbolProfiles([ this.symbolProfileService.getSymbolProfiles([
{ {
@ -329,23 +331,31 @@ export class ImportService {
} }
} }
if (!isDryRun && accountsWithBalancesDto?.length) { if (accountsWithBalancesDto?.length) {
const [existingAccounts, existingAccountsOfUser, existingPlatforms] = const [
await Promise.all([ existingAccountsOfOtherUsers,
this.accountService.accounts({ existingAccountsOfUser,
where: { existingPlatforms
id: { ] = await Promise.all([
in: accountsWithBalancesDto.map(({ id }) => { this.accountService.accounts({
where: {
id: {
in: accountsWithBalancesDto
.filter(({ id }) => {
return Boolean(id);
})
.map(({ id }) => {
return id; return id;
}) })
} },
} userId: { not: user.id }
}), }
this.accountService.accounts({ }),
where: { userId: user.id } this.accountService.accounts({
}), where: { userId: user.id }
this.platformService.getPlatforms() }),
]); this.platformService.getPlatforms()
]);
const existingTagIds = new Set( const existingTagIds = new Set(
existingTagsOfUser.map(({ id }) => { existingTagsOfUser.map(({ id }) => {
@ -354,19 +364,18 @@ export class ImportService {
); );
for (const accountWithBalances of accountsWithBalancesDto) { for (const accountWithBalances of accountsWithBalancesDto) {
// Check if there is any existing account with the same ID
const accountWithSameId = existingAccounts.find((existingAccount) => {
return existingAccount.id === accountWithBalances.id;
});
// Skip the account if it already belongs to the user // Skip the account if it already belongs to the user
if (accountWithSameId && accountWithSameId.userId === user.id) { if (
existingAccountsOfUser.some(({ id }) => {
return id === accountWithBalances.id;
})
) {
continue; continue;
} }
// If there is no account or if the account belongs to a different // If there is no account or if the account belongs to a different
// user, then reuse an existing account of the user with the same name // user, then reuse an existing account of the user with the same name
// or create a new account // and currency or create a new account
const accountToReuse = this.getAccountToReuse({ const accountToReuse = this.getAccountToReuse({
accountWithBalances, accountWithBalances,
accountsWithBalancesDto, accountsWithBalancesDto,
@ -388,6 +397,18 @@ export class ImportService {
continue; continue;
} }
if (isDryRun) {
continue;
}
// Check if there is any existing account of a different user with the
// same ID, since the ID cannot be reused in this case
const accountWithSameIdOfOtherUser = existingAccountsOfOtherUsers.find(
({ id }) => {
return id === accountWithBalances.id;
}
);
const account = omit(accountWithBalances, [ const account = omit(accountWithBalances, [
'balances', 'balances',
'isExcluded', 'isExcluded',
@ -400,7 +421,7 @@ export class ImportService {
delete account.platformId; delete account.platformId;
if (accountWithSameId) { if (accountWithSameIdOfOtherUser) {
oldAccountId = account.id; oldAccountId = account.id;
delete account.id; delete account.id;
} }
@ -449,7 +470,7 @@ export class ImportService {
); );
// Store the new to old account ID mappings for updating activities // Store the new to old account ID mappings for updating activities
if (accountWithSameId && oldAccountId) { if (accountWithSameIdOfOtherUser && oldAccountId) {
accountIdMapping[oldAccountId] = newAccount.id; accountIdMapping[oldAccountId] = newAccount.id;
} }
} }
@ -568,12 +589,12 @@ export class ImportService {
activity.symbol = assetProfileSymbolMapping[activity.symbol]; activity.symbol = assetProfileSymbolMapping[activity.symbol];
} }
if (!isDryRun) { // If an account is created or reused, then update the accountId in all activities
// If a new account is created, then update the accountId in all activities if (accountIdMapping[activity.accountId]) {
if (accountIdMapping[activity.accountId]) { activity.accountId = accountIdMapping[activity.accountId];
activity.accountId = accountIdMapping[activity.accountId]; }
}
if (!isDryRun) {
// If a new tag is created, then update the tag ID in all activities // If a new tag is created, then update the tag ID in all activities
activity.tags = (activity.tags ?? []).map((tagId) => { activity.tags = (activity.tags ?? []).map((tagId) => {
return tagIdMapping[tagId] ?? tagId; return tagIdMapping[tagId] ?? tagId;
@ -601,9 +622,20 @@ export class ImportService {
); );
if (isDryRun) { if (isDryRun) {
accountsWithBalancesDto.forEach(({ id, name }) => { accountsWithBalancesDto
accounts.push({ id, name }); .filter(({ id }) => {
}); // Skip the accounts which are reused or which already belong to the
// user, since they are part of the accounts of the user above
return (
!accountIdMapping[id] &&
!accounts.some(({ id: accountId }) => {
return accountId === id;
})
);
})
.forEach(({ id, name }) => {
accounts.push({ id, name });
});
} }
const tags = (await this.tagService.getTagsForUser(user.id)).map( const tags = (await this.tagService.getTagsForUser(user.id)).map(
@ -910,10 +942,12 @@ export class ImportService {
/** /**
* Returns the account of the user to reuse for the given account of the * Returns the account of the user to reuse for the given account of the
* import, based on the name. The name is only considered if it is * import, based on the name and the currency. The currency is considered
* unambiguous, both in the accounts of the user and in the accounts of the * because the activities of the import would otherwise end up in an account
* import, since it is not unique. Otherwise, distinct accounts would be * of a different currency. The name is only considered if it is unambiguous,
* merged into a single one. * both in the accounts of the user and in the accounts of the import, since
* it is not unique. Otherwise, distinct accounts would be merged into a
* single one.
*/ */
private getAccountToReuse({ private getAccountToReuse({
accountWithBalances, accountWithBalances,
@ -924,26 +958,32 @@ export class ImportService {
accountsWithBalancesDto: ImportDataDto['accounts']; accountsWithBalancesDto: ImportDataDto['accounts'];
existingAccountsOfUser: Account[]; existingAccountsOfUser: Account[];
}): Account { }): Account {
const accountsWithSameNameOfUser = existingAccountsOfUser.filter( const matchingAccountsOfUser = existingAccountsOfUser.filter(
({ name }) => { ({ currency, name }) => {
return name === accountWithBalances.name; return (
currency === accountWithBalances.currency &&
name === accountWithBalances.name
);
} }
); );
const accountsWithSameNameToImport = accountsWithBalancesDto.filter( const matchingAccountsToImport = accountsWithBalancesDto.filter(
({ name }) => { ({ currency, name }) => {
return name === accountWithBalances.name; return (
currency === accountWithBalances.currency &&
name === accountWithBalances.name
);
} }
); );
if ( if (
accountsWithSameNameOfUser.length !== 1 || matchingAccountsOfUser.length !== 1 ||
accountsWithSameNameToImport.length !== 1 matchingAccountsToImport.length !== 1
) { ) {
return undefined; return undefined;
} }
return accountsWithSameNameOfUser[0]; return matchingAccountsOfUser[0];
} }
private isUniqueAccount(accounts: AccountWithValue[]) { private isUniqueAccount(accounts: AccountWithValue[]) {

Loading…
Cancel
Save