Feature/rename orders to activities in import and export (#786)

* Rename orders to activities

* Update changelog
pull/787/head
Thomas Kaul 3 years ago committed by GitHub
parent e986310302
commit 1214127ec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Renamed `orders` to `activities` in import and export functionality
- Improved the pricing page
## 1.130.0 - 30.03.2022

@ -1,13 +1,6 @@
import { Export } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Get,
Headers,
Inject,
Query,
UseGuards
} from '@nestjs/common';
import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';

@ -14,7 +14,7 @@ export class ExportService {
activityIds?: string[];
userId: string;
}): Promise<Export> {
let orders = await this.prismaService.order.findMany({
let activities = await this.prismaService.order.findMany({
orderBy: { date: 'desc' },
select: {
accountId: true,
@ -30,14 +30,14 @@ export class ExportService {
});
if (activityIds) {
orders = orders.filter((order) => {
return activityIds.includes(order.id);
activities = activities.filter((activity) => {
return activityIds.includes(activity.id);
});
}
return {
meta: { date: new Date().toISOString(), version: environment.version },
orders: orders.map(
activities: activities.map(
({
accountId,
date,

@ -6,5 +6,5 @@ export class ImportDataDto {
@IsArray()
@Type(() => CreateOrderDto)
@ValidateNested({ each: true })
orders: CreateOrderDto[];
activities: CreateOrderDto[];
}

@ -36,7 +36,7 @@ export class ImportController {
try {
return await this.importService.import({
orders: importData.orders,
activities: importData.activities,
userId: this.request.user.id
});
} catch (error) {

@ -16,23 +16,23 @@ export class ImportService {
) {}
public async import({
orders,
activities,
userId
}: {
orders: Partial<CreateOrderDto>[];
activities: Partial<CreateOrderDto>[];
userId: string;
}): Promise<void> {
for (const order of orders) {
if (!order.dataSource) {
if (order.type === 'ITEM') {
order.dataSource = 'MANUAL';
for (const activity of activities) {
if (!activity.dataSource) {
if (activity.type === 'ITEM') {
activity.dataSource = 'MANUAL';
} else {
order.dataSource = this.dataProviderService.getPrimaryDataSource();
activity.dataSource = this.dataProviderService.getPrimaryDataSource();
}
}
}
await this.validateOrders({ orders, userId });
await this.validateActivities({ activities, userId });
const accountIds = (await this.accountService.getAccounts(userId)).map(
(account) => {
@ -50,7 +50,7 @@ export class ImportService {
symbol,
type,
unitPrice
} of orders) {
} of activities) {
await this.orderService.createOrder({
fee,
quantity,
@ -79,24 +79,24 @@ export class ImportService {
}
}
private async validateOrders({
orders,
private async validateActivities({
activities,
userId
}: {
orders: Partial<CreateOrderDto>[];
activities: Partial<CreateOrderDto>[];
userId: string;
}) {
if (
orders?.length > this.configurationService.get('MAX_ORDERS_TO_IMPORT')
activities?.length > this.configurationService.get('MAX_ORDERS_TO_IMPORT')
) {
throw new Error(
`Too many transactions (${this.configurationService.get(
`Too many activities (${this.configurationService.get(
'MAX_ORDERS_TO_IMPORT'
)} at most)`
);
}
const existingOrders = await this.orderService.orders({
const existingActivities = await this.orderService.orders({
include: { SymbolProfile: true },
orderBy: { date: 'desc' },
where: { userId }
@ -105,22 +105,22 @@ export class ImportService {
for (const [
index,
{ currency, dataSource, date, fee, quantity, symbol, type, unitPrice }
] of orders.entries()) {
const duplicateOrder = existingOrders.find((order) => {
] of activities.entries()) {
const duplicateActivity = existingActivities.find((activity) => {
return (
order.SymbolProfile.currency === currency &&
order.SymbolProfile.dataSource === dataSource &&
isSameDay(order.date, parseISO(<string>(<unknown>date))) &&
order.fee === fee &&
order.quantity === quantity &&
order.SymbolProfile.symbol === symbol &&
order.type === type &&
order.unitPrice === unitPrice
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 (duplicateOrder) {
throw new Error(`orders.${index} is a duplicate transaction`);
if (duplicateActivity) {
throw new Error(`activities.${index} is a duplicate activity`);
}
if (dataSource !== 'MANUAL') {
@ -130,13 +130,13 @@ export class ImportService {
if (quotes[symbol] === undefined) {
throw new Error(
`orders.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
`activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
);
}
if (quotes[symbol].currency !== currency) {
throw new Error(
`orders.${index}.currency ("${currency}") does not match with "${quotes[symbol].currency}"`
`activities.${index}.currency ("${currency}") does not match with "${quotes[symbol].currency}"`
);
}
}

@ -27,12 +27,12 @@ export class ImportTransactionDialog implements OnDestroy {
public ngOnInit() {
for (const message of this.data.messages) {
if (message.includes('orders.')) {
if (message.includes('activities.')) {
let [index] = message.split(' ');
index = index.replace('orders.', '');
index = index.replace('activities.', '');
[index] = index.split('.');
this.details.push(this.data.orders[index]);
this.details.push(this.data.activities[index]);
} else {
this.details.push('');
}

@ -1,5 +1,5 @@
export interface ImportTransactionDialogParams {
activities: any[];
deviceType: string;
messages: string[];
orders: any[];
}

@ -185,19 +185,31 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
if (file.name.endsWith('.json')) {
const content = JSON.parse(fileContent);
if (!isArray(content.orders)) {
throw new Error();
if (!isArray(content.activities)) {
if (isArray(content.orders)) {
this.handleImportError({
activities: [],
error: {
error: {
message: [`orders needs to be renamed to activities`]
}
}
});
return;
} else {
throw new Error();
}
}
try {
await this.importTransactionsService.importJson({
content: content.orders
content: content.activities
});
this.handleImportSuccess();
} catch (error) {
console.error(error);
this.handleImportError({ error, orders: content.orders });
this.handleImportError({ error, activities: content.activities });
}
return;
@ -212,10 +224,10 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
} catch (error) {
console.error(error);
this.handleImportError({
activities: error?.activities ?? [],
error: {
error: { message: error?.error?.message ?? [error?.message] }
},
orders: error?.orders ?? []
}
});
}
@ -226,8 +238,8 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
} catch (error) {
console.error(error);
this.handleImportError({
error: { error: { message: ['Unexpected format'] } },
orders: []
activities: [],
error: { error: { message: ['Unexpected format'] } }
});
}
};
@ -281,12 +293,18 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete();
}
private handleImportError({ error, orders }: { error: any; orders: any[] }) {
private handleImportError({
activities,
error
}: {
activities: any[];
error: any;
}) {
this.snackBar.dismiss();
this.dialog.open(ImportTransactionDialog, {
data: {
orders,
activities,
deviceType: this.deviceType,
messages: error?.error?.message
},

@ -37,9 +37,9 @@ export class ImportTransactionsService {
skipEmptyLines: true
}).data;
const orders: CreateOrderDto[] = [];
const activities: CreateOrderDto[] = [];
for (const [index, item] of content.entries()) {
orders.push({
activities.push({
accountId: this.parseAccount({ item, userAccounts }),
currency: this.parseCurrency({ content, index, item }),
dataSource: this.parseDataSource({ item }),
@ -52,13 +52,13 @@ export class ImportTransactionsService {
});
}
await this.importJson({ content: orders });
await this.importJson({ content: activities });
}
public importJson({ content }: { content: CreateOrderDto[] }): Promise<void> {
return new Promise((resolve, reject) => {
this.postImport({
orders: content
activities: content
})
.pipe(
catchError((error) => {
@ -121,7 +121,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.currency is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.currency is not valid`
};
}
private parseDataSource({ item }: { item: any }) {
@ -164,7 +167,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.date is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.date is not valid`
};
}
private parseFee({
@ -184,7 +190,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.fee is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.fee is not valid`
};
}
private parseQuantity({
@ -204,7 +213,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.quantity is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.quantity is not valid`
};
}
private parseSymbol({
@ -224,7 +236,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.symbol is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.symbol is not valid`
};
}
private parseType({
@ -255,7 +270,10 @@ export class ImportTransactionsService {
}
}
throw { message: `orders.${index}.type is not valid`, orders: content };
throw {
activities: content,
message: `activities.${index}.type is not valid`
};
}
private parseUnitPrice({
@ -276,12 +294,12 @@ export class ImportTransactionsService {
}
throw {
message: `orders.${index}.unitPrice is not valid`,
orders: content
activities: content,
message: `activities.${index}.unitPrice is not valid`
};
}
private postImport(aImportData: { orders: CreateOrderDto[] }) {
private postImport(aImportData: { activities: CreateOrderDto[] }) {
return this.http.post<void>('/api/v1/import', aImportData);
}
}

@ -5,5 +5,5 @@ export interface Export {
date: string;
version: string;
};
orders: Partial<Order>[];
activities: Partial<Order>[];
}

@ -3,7 +3,7 @@
"date": "2021-01-01T00:00:00.000Z",
"version": "dev"
},
"orders": [
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",

@ -3,7 +3,7 @@
"date": "2021-01-01T00:00:00.000Z",
"version": "dev"
},
"orders": [
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",

@ -0,0 +1,39 @@
{
"meta": {
"date": "2022-04-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"accountId": null,
"date": "2021-12-31T23:00:00.000Z",
"fee": 0,
"quantity": 1,
"type": "ITEM",
"unitPrice": 500000,
"currency": "USD",
"dataSource": "MANUAL",
"symbol": "Penthouse Apartment"
},
{
"date": "2021-11-16T23:00:00.000Z",
"fee": 0,
"quantity": 5,
"type": "DIVIDEND",
"unitPrice": 0.62,
"currency": "USD",
"dataSource": "YAHOO",
"symbol": "MSFT"
},
{
"date": "2021-09-15T22:00:00.000Z",
"fee": 19,
"quantity": 5,
"type": "BUY",
"unitPrice": 298.58,
"currency": "USD",
"dataSource": "YAHOO",
"symbol": "MSFT"
}
]
}
Loading…
Cancel
Save