|
@ -1,9 +1,6 @@ |
|
|
import { AccountService } from '@ghostfolio/api/app/account/account.service'; |
|
|
import { AccountService } from '@ghostfolio/api/app/account/account.service'; |
|
|
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; |
|
|
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; |
|
|
import { |
|
|
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; |
|
|
CreateOrderDto, |
|
|
|
|
|
OrderResponseDto |
|
|
|
|
|
} from '@ghostfolio/api/app/order/create-order.dto'; |
|
|
|
|
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; |
|
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; |
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; |
|
|
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; |
|
@ -208,14 +205,14 @@ export class ImportService { |
|
|
userId |
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const activitiesDtoWithDuplication = await this.markActivitiesAsDuplicates({ |
|
|
const activitiesMarkedAsDuplicates = await this.markActivitiesAsDuplicates({ |
|
|
activitiesDto, |
|
|
activitiesDto, |
|
|
userId |
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const accounts = (await this.accountService.getAccounts(userId)).map( |
|
|
const accounts = (await this.accountService.getAccounts(userId)).map( |
|
|
(account) => { |
|
|
({ id, name }) => { |
|
|
return { id: account.id, name: account.name }; |
|
|
return { id, name }; |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
@ -230,17 +227,14 @@ export class ImportService { |
|
|
for (const { |
|
|
for (const { |
|
|
accountId, |
|
|
accountId, |
|
|
comment, |
|
|
comment, |
|
|
currency, |
|
|
date, |
|
|
dataSource, |
|
|
|
|
|
date: dateString, |
|
|
|
|
|
fee, |
|
|
fee, |
|
|
isDuplicate, |
|
|
isDuplicate, |
|
|
quantity, |
|
|
quantity, |
|
|
symbol, |
|
|
SymbolProfile: assetProfile, |
|
|
type, |
|
|
type, |
|
|
unitPrice |
|
|
unitPrice |
|
|
} of activitiesDtoWithDuplication) { |
|
|
} of activitiesMarkedAsDuplicates) { |
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
|
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
return id === accountId; |
|
|
return id === accountId; |
|
|
}); |
|
|
}); |
|
@ -266,23 +260,23 @@ export class ImportService { |
|
|
id: uuidv4(), |
|
|
id: uuidv4(), |
|
|
isDraft: isAfter(date, endOfToday()), |
|
|
isDraft: isAfter(date, endOfToday()), |
|
|
SymbolProfile: { |
|
|
SymbolProfile: { |
|
|
currency, |
|
|
assetClass: assetProfile.assetClass, |
|
|
dataSource, |
|
|
assetSubClass: assetProfile.assetSubClass, |
|
|
symbol, |
|
|
comment: assetProfile.comment, |
|
|
assetClass: null, |
|
|
countries: assetProfile.countries, |
|
|
assetSubClass: null, |
|
|
createdAt: assetProfile.createdAt, |
|
|
comment: null, |
|
|
currency: assetProfile.currency, |
|
|
countries: null, |
|
|
dataSource: assetProfile.dataSource, |
|
|
createdAt: undefined, |
|
|
id: assetProfile.id, |
|
|
id: undefined, |
|
|
isin: assetProfile.isin, |
|
|
isin: null, |
|
|
name: assetProfile.name, |
|
|
name: null, |
|
|
scraperConfiguration: assetProfile.scraperConfiguration, |
|
|
scraperConfiguration: null, |
|
|
sectors: assetProfile.sectors, |
|
|
sectors: null, |
|
|
symbol: assetProfile.currency, |
|
|
symbolMapping: null, |
|
|
symbolMapping: assetProfile.symbolMapping, |
|
|
updatedAt: undefined, |
|
|
updatedAt: assetProfile.updatedAt, |
|
|
url: null, |
|
|
url: assetProfile.url, |
|
|
...assetProfiles[symbol] |
|
|
...assetProfiles[assetProfile.symbol] |
|
|
}, |
|
|
}, |
|
|
Account: validatedAccount, |
|
|
Account: validatedAccount, |
|
|
symbolProfileId: undefined, |
|
|
symbolProfileId: undefined, |
|
@ -305,14 +299,14 @@ export class ImportService { |
|
|
SymbolProfile: { |
|
|
SymbolProfile: { |
|
|
connectOrCreate: { |
|
|
connectOrCreate: { |
|
|
create: { |
|
|
create: { |
|
|
currency, |
|
|
currency: assetProfile.currency, |
|
|
dataSource, |
|
|
dataSource: assetProfile.dataSource, |
|
|
symbol |
|
|
symbol: assetProfile.symbol |
|
|
}, |
|
|
}, |
|
|
where: { |
|
|
where: { |
|
|
dataSource_symbol: { |
|
|
dataSource_symbol: { |
|
|
dataSource, |
|
|
dataSource: assetProfile.dataSource, |
|
|
symbol |
|
|
symbol: assetProfile.symbol |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -330,12 +324,12 @@ export class ImportService { |
|
|
value, |
|
|
value, |
|
|
feeInBaseCurrency: this.exchangeRateDataService.toCurrency( |
|
|
feeInBaseCurrency: this.exchangeRateDataService.toCurrency( |
|
|
fee, |
|
|
fee, |
|
|
currency, |
|
|
assetProfile.currency, |
|
|
userCurrency |
|
|
userCurrency |
|
|
), |
|
|
), |
|
|
valueInBaseCurrency: this.exchangeRateDataService.toCurrency( |
|
|
valueInBaseCurrency: this.exchangeRateDataService.toCurrency( |
|
|
value, |
|
|
value, |
|
|
currency, |
|
|
assetProfile.currency, |
|
|
userCurrency |
|
|
userCurrency |
|
|
) |
|
|
) |
|
|
}); |
|
|
}); |
|
@ -343,38 +337,48 @@ export class ImportService { |
|
|
return activities; |
|
|
return activities; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private isUniqueAccount(accounts: AccountWithPlatform[]) { |
|
|
|
|
|
const uniqueAccountIds = new Set<string>(); |
|
|
|
|
|
|
|
|
|
|
|
for (const account of accounts) { |
|
|
|
|
|
uniqueAccountIds.add(account.id); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return uniqueAccountIds.size === 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private async markActivitiesAsDuplicates({ |
|
|
private async markActivitiesAsDuplicates({ |
|
|
activitiesDto, |
|
|
activitiesDto, |
|
|
userId |
|
|
userId |
|
|
}: { |
|
|
}: { |
|
|
activitiesDto: Partial<CreateOrderDto>[]; |
|
|
activitiesDto: Partial<CreateOrderDto>[]; |
|
|
userId: string; |
|
|
userId: string; |
|
|
}): Promise<Partial<OrderResponseDto>[]> { |
|
|
}): Promise<Partial<Activity>[]> { |
|
|
const existingActivities = await this.orderService.orders({ |
|
|
const existingActivities = await this.orderService.orders({ |
|
|
include: { SymbolProfile: true }, |
|
|
include: { SymbolProfile: true }, |
|
|
orderBy: { date: 'desc' }, |
|
|
orderBy: { date: 'desc' }, |
|
|
where: { userId } |
|
|
where: { userId } |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const activitiesDtoWithDuplication: Partial<OrderResponseDto>[] = []; |
|
|
return activitiesDto.map( |
|
|
|
|
|
({ |
|
|
for (const activitiesDtoEntry of activitiesDto) { |
|
|
accountId, |
|
|
const { |
|
|
comment, |
|
|
currency, |
|
|
currency, |
|
|
dataSource, |
|
|
dataSource, |
|
|
date, |
|
|
date: dateString, |
|
|
fee, |
|
|
fee, |
|
|
quantity, |
|
|
quantity, |
|
|
symbol, |
|
|
symbol, |
|
|
type, |
|
|
type, |
|
|
unitPrice |
|
|
unitPrice |
|
|
} = activitiesDtoEntry; |
|
|
}) => { |
|
|
|
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
const isDuplicate = existingActivities.find((activity) => { |
|
|
const isDuplicate = existingActivities.some((activity) => { |
|
|
return ( |
|
|
return ( |
|
|
activity.SymbolProfile.currency === currency && |
|
|
activity.SymbolProfile.currency === currency && |
|
|
activity.SymbolProfile.dataSource === dataSource && |
|
|
activity.SymbolProfile.dataSource === dataSource && |
|
|
isSameDay(activity.date, parseISO(<string>(<unknown>date))) && |
|
|
isSameDay(activity.date, date) && |
|
|
activity.fee === fee && |
|
|
activity.fee === fee && |
|
|
activity.quantity === quantity && |
|
|
activity.quantity === quantity && |
|
|
activity.SymbolProfile.symbol === symbol && |
|
|
activity.SymbolProfile.symbol === symbol && |
|
@ -383,30 +387,36 @@ export class ImportService { |
|
|
); |
|
|
); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (isDuplicate) { |
|
|
return { |
|
|
activitiesDtoWithDuplication.push({ |
|
|
accountId, |
|
|
...activitiesDtoEntry, |
|
|
comment, |
|
|
isDuplicate: true |
|
|
date, |
|
|
}); |
|
|
fee, |
|
|
} else { |
|
|
isDuplicate, |
|
|
activitiesDtoWithDuplication.push({ |
|
|
quantity, |
|
|
...activitiesDtoEntry, |
|
|
type, |
|
|
isDuplicate: false |
|
|
unitPrice, |
|
|
}); |
|
|
SymbolProfile: { |
|
|
} |
|
|
currency, |
|
|
} |
|
|
dataSource, |
|
|
|
|
|
symbol, |
|
|
return activitiesDtoWithDuplication; |
|
|
assetClass: null, |
|
|
|
|
|
assetSubClass: null, |
|
|
|
|
|
comment: null, |
|
|
|
|
|
countries: null, |
|
|
|
|
|
createdAt: undefined, |
|
|
|
|
|
id: undefined, |
|
|
|
|
|
isin: null, |
|
|
|
|
|
name: null, |
|
|
|
|
|
scraperConfiguration: null, |
|
|
|
|
|
sectors: null, |
|
|
|
|
|
symbolMapping: null, |
|
|
|
|
|
updatedAt: undefined, |
|
|
|
|
|
url: null |
|
|
} |
|
|
} |
|
|
|
|
|
}; |
|
|
private isUniqueAccount(accounts: AccountWithPlatform[]) { |
|
|
|
|
|
const uniqueAccountIds = new Set<string>(); |
|
|
|
|
|
|
|
|
|
|
|
for (const account of accounts) { |
|
|
|
|
|
uniqueAccountIds.add(account.id); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
); |
|
|
return uniqueAccountIds.size === 1; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private async validateActivities({ |
|
|
private async validateActivities({ |
|
|