diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 5fd88d4c7..784b34d5e 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -11,6 +11,7 @@ export class ExportService { const orders = await this.prismaService.order.findMany({ orderBy: { date: 'desc' }, select: { + accountId: true, currency: true, dataSource: true, date: true, 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 8606cd395..3ef517809 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 @@ -191,7 +191,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { try { await this.importTransactionsService.importJson({ content: content.orders, - defaultAccountId: this.defaultAccountId }); this.handleImportSuccess(); @@ -204,8 +203,8 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { } else if (file.name.endsWith('.csv')) { try { await this.importTransactionsService.importCsv({ + user: this.user, fileContent, - defaultAccountId: this.defaultAccountId, primaryDataSource: this.primaryDataSource }); diff --git a/apps/client/src/app/services/import-transactions.service.ts b/apps/client/src/app/services/import-transactions.service.ts index d3baa13a4..242f272e0 100644 --- a/apps/client/src/app/services/import-transactions.service.ts +++ b/apps/client/src/app/services/import-transactions.service.ts @@ -4,14 +4,16 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { DataSource, Type } from '@prisma/client'; import { parse } from 'date-fns'; import { isNumber } from 'lodash'; -import { parse as csvToJson } from 'papaparse'; +import { Papa } from 'ngx-papaparse'; import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; +import { User } from '@ghostfolio/common/interfaces'; @Injectable({ providedIn: 'root' }) export class ImportTransactionsService { + private static ACCOUNT_ID = ['account', 'accountid']; private static CURRENCY_KEYS = ['ccy', 'currency']; private static DATE_KEYS = ['date']; private static FEE_KEYS = ['commission', 'fee']; @@ -20,28 +22,32 @@ export class ImportTransactionsService { private static TYPE_KEYS = ['action', 'type']; private static UNIT_PRICE_KEYS = ['price', 'unitprice', 'value']; - public constructor(private http: HttpClient) {} + public constructor(private http: HttpClient, private papa: Papa) {} public async importCsv({ - defaultAccountId, + user, fileContent, primaryDataSource }: { - defaultAccountId: string; + user: User; fileContent: string; primaryDataSource: DataSource; }) { - const content = csvToJson(fileContent, { + let content; + + this.papa.parse(fileContent, { dynamicTyping: true, header: true, - skipEmptyLines: true - }).data; + skipEmptyLines: true, + complete: (parsedData) => { + content = parsedData.data.filter((item) => item['date'] != null); + } + }); const orders: CreateOrderDto[] = []; - for (const [index, item] of content.entries()) { orders.push({ - accountId: defaultAccountId, + accountId: this.parseAccount({ content, index, item, user }), currency: this.parseCurrency({ content, index, item }), dataSource: primaryDataSource, date: this.parseDate({ content, index, item }), @@ -53,21 +59,14 @@ export class ImportTransactionsService { }); } - await this.importJson({ defaultAccountId, content: orders }); + + await this.importJson({ content: orders }); } - public importJson({ - content, - defaultAccountId - }: { - content: CreateOrderDto[]; - defaultAccountId: string; - }): Promise { + public importJson({ content }: { content: CreateOrderDto[] }): Promise { return new Promise((resolve, reject) => { this.postImport({ - orders: content.map((order) => { - return { ...order, accountId: defaultAccountId }; - }) + orders: content }) .pipe( catchError((error) => { @@ -90,6 +89,38 @@ export class ImportTransactionsService { }, {}); } + private parseAccount({ + content, + index, + item, + user + }: { + content: any[]; + index: number; + item: any; + user: User; + }) { + item = this.lowercaseKeys(item); + for (const key of ImportTransactionsService.ACCOUNT_ID) { + if (item[key]) { + let accountid = user.accounts.find((account) => { + return ( + account.name.toLowerCase() === item[key].toLowerCase() || + account.id == item[key] + ); + })?.id; + if (!accountid) { + accountid = user?.accounts.find((account) => { + return account.isDefault; + })?.id; + } + return accountid; + } + } + + throw { message: `orders.${index}.account is not valid`, orders: content }; + } + private parseCurrency({ content, index, diff --git a/package.json b/package.json index 01f8f938e..0bb45583d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "affected:test": "nx affected:test", "angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng", "build:all": "ng build --configuration production api && ng build --configuration production client && yarn replace-placeholders-in-build", + "build:dev": "nx build api && nx build client && yarn replace-placeholders-in-build", "build:storybook": "nx run ui:build-storybook", "clean": "rimraf dist", "database:format-schema": "prisma format", @@ -102,7 +103,7 @@ "ngx-markdown": "13.0.0", "ngx-skeleton-loader": "2.9.1", "ngx-stripe": "13.0.0", - "papaparse": "5.3.1", + "ngx-papaparse": "5.0.0", "passport": "0.4.1", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.0", diff --git a/yarn.lock b/yarn.lock index 814879352..95fc0b52a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13425,6 +13425,14 @@ ngx-markdown@13.0.0: prismjs "^1.25.0" tslib "^2.3.0" +ngx-papaparse@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ngx-papaparse/-/ngx-papaparse-5.0.0.tgz#30c5a73658cdb443e78e0b3f8c89d646d1ae5b2e" + integrity sha512-BA5vEtHPrh3UXLLbcvbHAIVEdMhQrkdMS5SpciuRDQqN1PSK71qQbVFws9AuqHoBXPwXPMUokiAzYs69VFhaPA== + dependencies: + papaparse "^5.3.0" + tslib "^1.10.0" + ngx-skeleton-loader@2.9.1: version "2.9.1" resolved "https://registry.yarnpkg.com/ngx-skeleton-loader/-/ngx-skeleton-loader-2.9.1.tgz#1e419ef66696a2017afc9c8cd0bc129d2c680ffb" @@ -14144,7 +14152,7 @@ pako@^1.0.3, pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -papaparse@5.3.1: +papaparse@^5.3.0: version "5.3.1" resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.1.tgz#770b7a9124d821d4b2132132b7bd7dce7194b5b1" integrity sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==