|
@ -1,6 +1,9 @@ |
|
|
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 { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; |
|
|
import { |
|
|
|
|
|
CreateOrderDto, |
|
|
|
|
|
OrderDto |
|
|
|
|
|
} 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'; |
|
@ -94,7 +97,8 @@ export class ImportService { |
|
|
value, |
|
|
value, |
|
|
assetProfile.currency, |
|
|
assetProfile.currency, |
|
|
userCurrency |
|
|
userCurrency |
|
|
) |
|
|
), |
|
|
|
|
|
isDuplicate: false |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
} catch { |
|
|
} catch { |
|
@ -204,6 +208,11 @@ export class ImportService { |
|
|
userId |
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const activitiesDtoWithDuplication = await this.labelDuplicateActivities({ |
|
|
|
|
|
activitiesDto, |
|
|
|
|
|
userId |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const accounts = (await this.accountService.getAccounts(userId)).map( |
|
|
const accounts = (await this.accountService.getAccounts(userId)).map( |
|
|
(account) => { |
|
|
(account) => { |
|
|
return { id: account.id, name: account.name }; |
|
|
return { id: account.id, name: account.name }; |
|
@ -228,8 +237,9 @@ export class ImportService { |
|
|
quantity, |
|
|
quantity, |
|
|
symbol, |
|
|
symbol, |
|
|
type, |
|
|
type, |
|
|
unitPrice |
|
|
unitPrice, |
|
|
} of activitiesDto) { |
|
|
isDuplicate |
|
|
|
|
|
} of activitiesDtoWithDuplication) { |
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
return id === accountId; |
|
|
return id === accountId; |
|
@ -279,6 +289,10 @@ export class ImportService { |
|
|
updatedAt: new Date() |
|
|
updatedAt: new Date() |
|
|
}; |
|
|
}; |
|
|
} else { |
|
|
} else { |
|
|
|
|
|
if (isDuplicate) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
order = await this.orderService.createOrder({ |
|
|
order = await this.orderService.createOrder({ |
|
|
comment, |
|
|
comment, |
|
|
date, |
|
|
date, |
|
@ -322,13 +336,70 @@ export class ImportService { |
|
|
value, |
|
|
value, |
|
|
currency, |
|
|
currency, |
|
|
userCurrency |
|
|
userCurrency |
|
|
) |
|
|
), |
|
|
|
|
|
isDuplicate |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
console.log(activities); |
|
|
return activities; |
|
|
return activities; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async labelDuplicateActivities({ |
|
|
|
|
|
activitiesDto, |
|
|
|
|
|
userId |
|
|
|
|
|
}: { |
|
|
|
|
|
activitiesDto: Partial<CreateOrderDto>[]; |
|
|
|
|
|
userId: string; |
|
|
|
|
|
}): Promise<Partial<OrderDto>[]> { |
|
|
|
|
|
const existingActivities = await this.orderService.orders({ |
|
|
|
|
|
include: { SymbolProfile: true }, |
|
|
|
|
|
orderBy: { date: 'desc' }, |
|
|
|
|
|
where: { userId } |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const activitiesDtoWithDuplication: Partial<OrderDto>[] = []; |
|
|
|
|
|
|
|
|
|
|
|
for (const activitiesDtoEntry of activitiesDto) { |
|
|
|
|
|
const { |
|
|
|
|
|
currency, |
|
|
|
|
|
dataSource, |
|
|
|
|
|
date, |
|
|
|
|
|
fee, |
|
|
|
|
|
quantity, |
|
|
|
|
|
symbol, |
|
|
|
|
|
type, |
|
|
|
|
|
unitPrice |
|
|
|
|
|
} = activitiesDtoEntry; |
|
|
|
|
|
|
|
|
|
|
|
const duplicateActivity = existingActivities.find((activity) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
activity.SymbolProfile.currency === currency && |
|
|
|
|
|
activity.SymbolProfile.dataSource === dataSource && |
|
|
|
|
|
isSameDay(activity.date, parseISO(<string>(<unknown>date))) && |
|
|
|
|
|
activity.fee === fee && |
|
|
|
|
|
activity.quantity === quantity && |
|
|
|
|
|
activity.SymbolProfile.symbol === symbol && |
|
|
|
|
|
activity.type === type && |
|
|
|
|
|
activity.unitPrice === unitPrice |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (duplicateActivity) { |
|
|
|
|
|
activitiesDtoWithDuplication.push({ |
|
|
|
|
|
...activitiesDtoEntry, |
|
|
|
|
|
isDuplicate: true |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
activitiesDtoWithDuplication.push({ |
|
|
|
|
|
...activitiesDtoEntry, |
|
|
|
|
|
isDuplicate: false |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return activitiesDtoWithDuplication; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private isUniqueAccount(accounts: AccountWithPlatform[]) { |
|
|
private isUniqueAccount(accounts: AccountWithPlatform[]) { |
|
|
const uniqueAccountIds = new Set<string>(); |
|
|
const uniqueAccountIds = new Set<string>(); |
|
|
|
|
|
|
|
@ -355,33 +426,11 @@ export class ImportService { |
|
|
const assetProfiles: { |
|
|
const assetProfiles: { |
|
|
[symbol: string]: Partial<SymbolProfile>; |
|
|
[symbol: string]: Partial<SymbolProfile>; |
|
|
} = {}; |
|
|
} = {}; |
|
|
const existingActivities = await this.orderService.orders({ |
|
|
|
|
|
include: { SymbolProfile: true }, |
|
|
|
|
|
orderBy: { date: 'desc' }, |
|
|
|
|
|
where: { userId } |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
for (const [ |
|
|
for (const [ |
|
|
index, |
|
|
index, |
|
|
{ currency, dataSource, date, fee, quantity, symbol, type, unitPrice } |
|
|
{ currency, dataSource, symbol } |
|
|
] of activitiesDto.entries()) { |
|
|
] of activitiesDto.entries()) { |
|
|
const duplicateActivity = existingActivities.find((activity) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
activity.SymbolProfile.currency === currency && |
|
|
|
|
|
activity.SymbolProfile.dataSource === dataSource && |
|
|
|
|
|
isSameDay(activity.date, parseISO(<string>(<unknown>date))) && |
|
|
|
|
|
activity.fee === fee && |
|
|
|
|
|
activity.quantity === quantity && |
|
|
|
|
|
activity.SymbolProfile.symbol === symbol && |
|
|
|
|
|
activity.type === type && |
|
|
|
|
|
activity.unitPrice === unitPrice |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (duplicateActivity) { |
|
|
|
|
|
throw new Error(`activities.${index} is a duplicate activity`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (dataSource !== 'MANUAL') { |
|
|
if (dataSource !== 'MANUAL') { |
|
|
const assetProfile = ( |
|
|
const assetProfile = ( |
|
|
await this.dataProviderService.getAssetProfiles([ |
|
|
await this.dataProviderService.getAssetProfiles([ |
|
|