diff --git a/CHANGELOG.md b/CHANGELOG.md index be4fb1f68..f29845007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/api/src/app/export/export.controller.ts b/apps/api/src/app/export/export.controller.ts index 3617ebe24..ce02d9835 100644 --- a/apps/api/src/app/export/export.controller.ts +++ b/apps/api/src/app/export/export.controller.ts @@ -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'; diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 124fe6325..007429a38 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -14,7 +14,7 @@ export class ExportService { activityIds?: string[]; userId: string; }): Promise { - 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, diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index 488ac786f..f3a0ba8fe 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -6,5 +6,5 @@ export class ImportDataDto { @IsArray() @Type(() => CreateOrderDto) @ValidateNested({ each: true }) - orders: CreateOrderDto[]; + activities: CreateOrderDto[]; } diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index d14bd69af..00350f819 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -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) { diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 3ddd29040..40d677d9b 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -16,23 +16,23 @@ export class ImportService { ) {} public async import({ - orders, + activities, userId }: { - orders: Partial[]; + activities: Partial[]; userId: string; }): Promise { - 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[]; + activities: Partial[]; 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((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((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}"` ); } } diff --git a/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/import-transaction-dialog.component.ts b/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/import-transaction-dialog.component.ts index ac7832ff5..8a28b4ea4 100644 --- a/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/import-transaction-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/import-transaction-dialog.component.ts @@ -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(''); } diff --git a/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/interfaces/interfaces.ts index 3800514a6..123238544 100644 --- a/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/transactions/import-transaction-dialog/interfaces/interfaces.ts @@ -1,5 +1,5 @@ export interface ImportTransactionDialogParams { + activities: any[]; deviceType: string; messages: string[]; - orders: any[]; } diff --git a/apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts b/apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts index a40d44a4f..ba73739b7 100644 --- a/apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts +++ b/apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts @@ -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 }, diff --git a/apps/client/src/app/services/import-transactions.service.ts b/apps/client/src/app/services/import-transactions.service.ts index 6b01dad47..8557fe0af 100644 --- a/apps/client/src/app/services/import-transactions.service.ts +++ b/apps/client/src/app/services/import-transactions.service.ts @@ -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 { 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('/api/v1/import', aImportData); } } diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/export.interface.ts index ff83b619a..48ddd2c98 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/export.interface.ts @@ -5,5 +5,5 @@ export interface Export { date: string; version: string; }; - orders: Partial[]; + activities: Partial[]; } diff --git a/test/import/invalid-date.json b/test/import/invalid-date.json index 2ddaf372e..99cd6d156 100644 --- a/test/import/invalid-date.json +++ b/test/import/invalid-date.json @@ -3,7 +3,7 @@ "date": "2021-01-01T00:00:00.000Z", "version": "dev" }, - "orders": [ + "activities": [ { "currency": "USD", "dataSource": "YAHOO", diff --git a/test/import/invalid-symbol.json b/test/import/invalid-symbol.json index c03f86e5d..14f0051ec 100644 --- a/test/import/invalid-symbol.json +++ b/test/import/invalid-symbol.json @@ -3,7 +3,7 @@ "date": "2021-01-01T00:00:00.000Z", "version": "dev" }, - "orders": [ + "activities": [ { "currency": "USD", "dataSource": "YAHOO", diff --git a/test/import/ok.json b/test/import/ok.json new file mode 100644 index 000000000..ae08aee73 --- /dev/null +++ b/test/import/ok.json @@ -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" + } + ] +}