|
@ -84,6 +84,7 @@ export class ImportService { |
|
|
feeInBaseCurrency: 0, |
|
|
feeInBaseCurrency: 0, |
|
|
id: assetProfile.id, |
|
|
id: assetProfile.id, |
|
|
isDraft: false, |
|
|
isDraft: false, |
|
|
|
|
|
isDuplicate: false, // TODO: Use evaluated state
|
|
|
SymbolProfile: <SymbolProfile>(<unknown>assetProfile), |
|
|
SymbolProfile: <SymbolProfile>(<unknown>assetProfile), |
|
|
symbolProfileId: assetProfile.id, |
|
|
symbolProfileId: assetProfile.id, |
|
|
type: 'DIVIDEND', |
|
|
type: 'DIVIDEND', |
|
@ -204,9 +205,14 @@ export class ImportService { |
|
|
userId |
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const activitiesMarkedAsDuplicates = await this.markActivitiesAsDuplicates({ |
|
|
|
|
|
activitiesDto, |
|
|
|
|
|
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 }; |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
@ -221,16 +227,14 @@ export class ImportService { |
|
|
for (const { |
|
|
for (const { |
|
|
accountId, |
|
|
accountId, |
|
|
comment, |
|
|
comment, |
|
|
currency, |
|
|
date, |
|
|
dataSource, |
|
|
|
|
|
date: dateString, |
|
|
|
|
|
fee, |
|
|
fee, |
|
|
|
|
|
isDuplicate, |
|
|
quantity, |
|
|
quantity, |
|
|
symbol, |
|
|
SymbolProfile: assetProfile, |
|
|
type, |
|
|
type, |
|
|
unitPrice |
|
|
unitPrice |
|
|
} of activitiesDto) { |
|
|
} of activitiesMarkedAsDuplicates) { |
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
|
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
const validatedAccount = accounts.find(({ id }) => { |
|
|
return id === accountId; |
|
|
return id === accountId; |
|
|
}); |
|
|
}); |
|
@ -256,29 +260,33 @@ 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, |
|
|
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, |
|
@ -291,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 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -313,15 +321,16 @@ export class ImportService { |
|
|
//@ts-ignore
|
|
|
//@ts-ignore
|
|
|
activities.push({ |
|
|
activities.push({ |
|
|
...order, |
|
|
...order, |
|
|
|
|
|
isDuplicate, |
|
|
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 |
|
|
) |
|
|
) |
|
|
}); |
|
|
}); |
|
@ -340,6 +349,78 @@ export class ImportService { |
|
|
return uniqueAccountIds.size === 1; |
|
|
return uniqueAccountIds.size === 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async markActivitiesAsDuplicates({ |
|
|
|
|
|
activitiesDto, |
|
|
|
|
|
userId |
|
|
|
|
|
}: { |
|
|
|
|
|
activitiesDto: Partial<CreateOrderDto>[]; |
|
|
|
|
|
userId: string; |
|
|
|
|
|
}): Promise<Partial<Activity>[]> { |
|
|
|
|
|
const existingActivities = await this.orderService.orders({ |
|
|
|
|
|
include: { SymbolProfile: true }, |
|
|
|
|
|
orderBy: { date: 'desc' }, |
|
|
|
|
|
where: { userId } |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return activitiesDto.map( |
|
|
|
|
|
({ |
|
|
|
|
|
accountId, |
|
|
|
|
|
comment, |
|
|
|
|
|
currency, |
|
|
|
|
|
dataSource, |
|
|
|
|
|
date: dateString, |
|
|
|
|
|
fee, |
|
|
|
|
|
quantity, |
|
|
|
|
|
symbol, |
|
|
|
|
|
type, |
|
|
|
|
|
unitPrice |
|
|
|
|
|
}) => { |
|
|
|
|
|
const date = parseISO(<string>(<unknown>dateString)); |
|
|
|
|
|
const isDuplicate = existingActivities.some((activity) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
activity.SymbolProfile.currency === currency && |
|
|
|
|
|
activity.SymbolProfile.dataSource === dataSource && |
|
|
|
|
|
isSameDay(activity.date, date) && |
|
|
|
|
|
activity.fee === fee && |
|
|
|
|
|
activity.quantity === quantity && |
|
|
|
|
|
activity.SymbolProfile.symbol === symbol && |
|
|
|
|
|
activity.type === type && |
|
|
|
|
|
activity.unitPrice === unitPrice |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
accountId, |
|
|
|
|
|
comment, |
|
|
|
|
|
date, |
|
|
|
|
|
fee, |
|
|
|
|
|
isDuplicate, |
|
|
|
|
|
quantity, |
|
|
|
|
|
type, |
|
|
|
|
|
unitPrice, |
|
|
|
|
|
SymbolProfile: { |
|
|
|
|
|
currency, |
|
|
|
|
|
dataSource, |
|
|
|
|
|
symbol, |
|
|
|
|
|
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 async validateActivities({ |
|
|
private async validateActivities({ |
|
|
activitiesDto, |
|
|
activitiesDto, |
|
|
maxActivitiesToImport, |
|
|
maxActivitiesToImport, |
|
@ -356,33 +437,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([ |
|
|