From ad3ebd42bb44488856a50e74821a1bfb4db53efb Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:49:37 +0100 Subject: [PATCH 01/12] Feature/migrate mat suffix to angular material 15 (#1655) * Migrate matSuffix to @angular/material 15 * Update changelog --- CHANGELOG.md | 8 ++++ .../market-data-detail-dialog.html | 24 +++++------ .../market-data-detail-dialog.module.ts | 6 +-- .../market-data-detail-dialog.scss | 16 ++----- .../portfolio/activities/activities-page.html | 2 +- .../activities/activities-page.module.ts | 4 +- ...ate-or-update-activity-dialog.component.ts | 2 +- .../create-or-update-activity-dialog.html | 42 +++++++++---------- ...create-or-update-activity-dialog.module.ts | 14 +++---- .../create-or-update-activity-dialog.scss | 35 ++++------------ 10 files changed, 65 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b55ba893..2cd26e71e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Migrated the style of `ActivitiesPageModule` to `@angular/material` `15` (mdc) +- Migrated the style of `GfCreateOrUpdateActivityDialogModule` to `@angular/material` `15` (mdc) +- Migrated the style of `GfMarketDataDetailDialogModule` to `@angular/material` `15` (mdc) + ## 1.231.0 - 2023-02-04 ### Added diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html index 230133de7..1797833c4 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html @@ -1,6 +1,6 @@

Details for {{ data.symbol }}

-
+
Date @@ -11,7 +11,7 @@ [matDatepicker]="date" [(ngModel)]="data.date" /> - +
-
+
Market Price - {{ data.currency }} - + {{ data.currency }} +
diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts index 14aa2199e..b46158505 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts @@ -2,10 +2,10 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; +import { MatButtonModule } from '@angular/material/button'; import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; -import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field'; -import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; import { MarketDataDetailDialog } from './market-data-detail-dialog.component'; diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss index 91922978c..c94c65c54 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss @@ -4,19 +4,9 @@ .mat-dialog-content { max-height: unset; - .mat-form-field-appearance-outline { - ::ng-deep { - .mat-form-field-suffix { - top: -0.3rem; - } - - .mat-form-field-wrapper { - padding-bottom: 0; - } - } - - ion-icon { - font-size: 130%; + .mat-mdc-button { + &.apply-current-market-price { + height: 56px; } } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.html b/apps/client/src/app/pages/portfolio/activities/activities-page.html index cec456c53..d958598c1 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -33,7 +33,7 @@ [queryParams]="{ createDialog: true }" [routerLink]="[]" > - +
diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.module.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.module.ts index 1f69f60f3..28d4437a9 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.module.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; -import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { RouterModule } from '@angular/router'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index cbe740adc..0fa6ae580 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -10,7 +10,7 @@ import { } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; -import { MatLegacyAutocompleteSelectedEvent as MatAutocompleteSelectedEvent } from '@angular/material/legacy-autocomplete'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialogRef as MatDialogRef diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index e5b215fbc..1b46ecf69 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -6,7 +6,7 @@ >

Update activity

Add activity

-
+
Type @@ -76,7 +76,7 @@
Currency - + {{ currency }} @@ -93,7 +93,7 @@ Date - +
-
+
@@ -121,20 +121,20 @@ - {{ activityForm.controls['currency'].value }} - +
@@ -142,7 +142,7 @@
@@ -157,7 +157,7 @@ Fee - {{ activityForm.controls['currency'].value }} @@ -207,8 +207,8 @@
Tags - - + {{ tag.name }} - + - + Date: Wed, 25 Jan 2023 12:12:11 +0530 Subject: [PATCH 02/12] Add accounts to activities export --- apps/api/src/app/export/export.service.ts | 14 ++++++++++++++ libs/common/src/lib/interfaces/export.interface.ts | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index c4655e7d8..ac6a861be 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -14,6 +14,19 @@ export class ExportService { activityIds?: string[]; userId: string; }): Promise { + const accounts = await this.prismaService.account.findMany({ + select: { + accountType: true, + balance: true, + currency: true, + id: true, + isDefault: true, + isExcluded: true, + name: true + }, + where: { userId } + }); + let activities = await this.prismaService.order.findMany({ orderBy: { date: 'desc' }, select: { @@ -38,6 +51,7 @@ export class ExportService { return { meta: { date: new Date().toISOString(), version: environment.version }, + accounts, activities: activities.map( ({ accountId, diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/export.interface.ts index 37dbfba79..cd47a8f0b 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/export.interface.ts @@ -1,10 +1,14 @@ -import { Order } from '@prisma/client'; +import { Order, Account } from '@prisma/client'; export interface Export { meta: { date: string; version: string; }; + accounts: Omit< + Account, + 'createdAt' | 'platformId' | 'updatedAt' | 'userId' + >[]; activities: (Omit< Order, | 'accountUserId' From 4469114bb862bc0554a2d86bd22b5adc7d958dcb Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Wed, 25 Jan 2023 12:59:49 +0530 Subject: [PATCH 03/12] Export platformId with account details --- apps/api/src/app/export/export.service.ts | 3 ++- libs/common/src/lib/interfaces/export.interface.ts | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index ac6a861be..ebf0845cc 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -22,7 +22,8 @@ export class ExportService { id: true, isDefault: true, isExcluded: true, - name: true + name: true, + platformId: true }, where: { userId } }); diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/export.interface.ts index cd47a8f0b..7b7470717 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/export.interface.ts @@ -5,10 +5,7 @@ export interface Export { date: string; version: string; }; - accounts: Omit< - Account, - 'createdAt' | 'platformId' | 'updatedAt' | 'userId' - >[]; + accounts: Omit[]; activities: (Omit< Order, | 'accountUserId' From 3e9df51bf751e75dfb00cc16fda96c91568c2937 Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Wed, 25 Jan 2023 20:32:12 +0530 Subject: [PATCH 04/12] Add logic for importing accounts --- .../api/src/app/account/create-account.dto.ts | 8 ++ apps/api/src/app/import/import-data.dto.ts | 9 ++- apps/api/src/app/import/import.controller.ts | 11 ++- apps/api/src/app/import/import.service.ts | 73 +++++++++++++++++++ .../import-activities-dialog.component.ts | 19 +++-- .../app/services/import-activities.service.ts | 43 +++++++---- 6 files changed, 142 insertions(+), 21 deletions(-) diff --git a/apps/api/src/app/account/create-account.dto.ts b/apps/api/src/app/account/create-account.dto.ts index 3ea13e20a..3c32163e0 100644 --- a/apps/api/src/app/account/create-account.dto.ts +++ b/apps/api/src/app/account/create-account.dto.ts @@ -8,6 +8,14 @@ import { } from 'class-validator'; export class CreateAccountDto { + @IsOptional() + @IsString() + id: string; + + @IsOptional() + @IsBoolean() + isDefault: boolean; + @IsString() accountType: AccountType; diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index f3a0ba8fe..aa141aab5 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -1,8 +1,15 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Type } from 'class-transformer'; -import { IsArray, ValidateNested } from 'class-validator'; +import { IsArray, ValidateNested, IsOptional } from 'class-validator'; +import { CreateAccountDto } from '../account/create-account.dto'; export class ImportDataDto { + @IsOptional() + @IsArray() + @Type(() => CreateAccountDto) + @ValidateNested({ each: true }) + accounts: CreateAccountDto[]; + @IsArray() @Type(() => CreateOrderDto) @ValidateNested({ each: true }) diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 2591ab638..a1a1d4b1d 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -2,6 +2,7 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ImportResponse } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { Body, @@ -38,7 +39,14 @@ export class ImportController { @Body() importData: ImportDataDto, @Query('dryRun') isDryRun?: boolean ): Promise { - if (!this.configurationService.get('ENABLE_FEATURE_IMPORT')) { + if ( + !this.configurationService.get('ENABLE_FEATURE_IMPORT') || + (importData.accounts?.length > 0 && + !hasPermission( + this.request.user.permissions, + permissions.createAccount + )) + ) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), StatusCodes.FORBIDDEN @@ -64,6 +72,7 @@ export class ImportController { isDryRun, userCurrency, activitiesDto: importData.activities, + accountsDto: importData.accounts || [], userId: this.request.user.id }); diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index d3be33bbc..2d09ca761 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -17,6 +17,7 @@ import { SymbolProfile } from '@prisma/client'; import Big from 'big.js'; import { endOfToday, isAfter, isSameDay, parseISO } from 'date-fns'; import { v4 as uuidv4 } from 'uuid'; +import { CreateAccountDto } from '../account/create-account.dto'; @Injectable() export class ImportService { @@ -101,17 +102,84 @@ export class ImportService { public async import({ activitiesDto, + accountsDto, isDryRun = false, maxActivitiesToImport, userCurrency, userId }: { activitiesDto: Partial[]; + accountsDto: Partial[]; isDryRun?: boolean; maxActivitiesToImport: number; userCurrency: string; userId: string; }): Promise { + const accountMappings = {}; + //Validate accounts + if (accountsDto?.length) { + for (let account of accountsDto) { + const existingAccounts = await this.accountService.accounts({ + where: { id: account.id } + }); + + const oldAccountId = account.id; + const platformId = account.platformId; + + delete account.id; + delete account.platformId; + delete account.isDefault; + + //If account id does not exist, then create a new one + if (existingAccounts.length === 0) { + const newAccountConfig = { + ...account, + User: { connect: { id: userId } } + }; + + if (platformId) { + Object.assign(newAccountConfig, { + Platform: { connect: { id: platformId } } + }); + } + + const newAccount = await this.accountService.createAccount( + newAccountConfig, + userId + ); + + accountMappings[oldAccountId] = newAccount.id; + continue; + } + + //If account id is used, then check if it belongs to the same user + //Yes -> Merge the accounts and don't create a new one + if (existingAccounts[0].userId === userId) { + continue; + } + + //No -> Replace the account id with a new account id as well as update all the activities when looping + + const newAccountConfig = { + ...account, + User: { connect: { id: userId } } + }; + + if (platformId) { + Object.assign(newAccountConfig, { + Platform: { connect: { id: platformId } } + }); + } + + const newAccount = await this.accountService.createAccount( + newAccountConfig, + userId + ); + + accountMappings[oldAccountId] = newAccount.id; + } + } + for (const activity of activitiesDto) { if (!activity.dataSource) { if (activity.type === 'ITEM') { @@ -120,6 +188,11 @@ export class ImportService { activity.dataSource = this.dataProviderService.getPrimaryDataSource(); } } + + //If we updated the account id, then update the account id in the activity as well + if (Object.keys(accountMappings).includes(activity.accountId)) { + activity.accountId = accountMappings[activity.accountId]; + } } const assetProfiles = await this.validateActivities({ diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index b7c143c37..b5d7b0a83 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -11,6 +11,7 @@ import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; +import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; @@ -28,6 +29,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; templateUrl: 'import-activities-dialog.html' }) export class ImportActivitiesDialog implements OnDestroy { + public accounts: CreateAccountDto[] = []; public activities: Activity[] = []; public details: any[] = []; public errorMessages: string[] = []; @@ -91,9 +93,10 @@ export class ImportActivitiesDialog implements OnDestroy { try { this.snackBar.open('⏳ ' + $localize`Importing data...`); - await this.importActivitiesService.importSelectedActivities( - this.selectedActivities - ); + await this.importActivitiesService.importSelectedActivities({ + accounts: this.accounts, + activities: this.selectedActivities + }); this.snackBar.open( '✅ ' + $localize`Import has been completed`, @@ -180,10 +183,13 @@ export class ImportActivitiesDialog implements OnDestroy { } try { - this.activities = await this.importActivitiesService.importJson({ - content: content.activities, + const data = await this.importActivitiesService.importJson({ + activities: content.activities, + accounts: content.accounts, isDryRun: true }); + this.activities = data.activities; + this.accounts = data.accounts; } catch (error) { console.error(error); this.handleImportError({ error, activities: content.activities }); @@ -192,11 +198,12 @@ export class ImportActivitiesDialog implements OnDestroy { return; } else if (file.name.endsWith('.csv')) { try { - this.activities = await this.importActivitiesService.importCsv({ + const data = await this.importActivitiesService.importCsv({ fileContent, isDryRun: true, userAccounts: this.data.user.accounts }); + this.activities = data.activities; } catch (error) { console.error(error); this.handleImportError({ diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 2e15f367f..c2b0bbe05 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Account, DataSource, Type } from '@prisma/client'; @@ -33,7 +34,10 @@ export class ImportActivitiesService { fileContent: string; isDryRun?: boolean; userAccounts: Account[]; - }): Promise { + }): Promise<{ + accounts?: CreateAccountDto[]; + activities: Activity[]; + }> { const content = csvToJson(fileContent, { dynamicTyping: true, header: true, @@ -55,20 +59,26 @@ export class ImportActivitiesService { }); } - return await this.importJson({ isDryRun, content: activities }); + return await this.importJson({ isDryRun, activities }); } public importJson({ - content, + activities, + accounts, isDryRun = false }: { - content: CreateOrderDto[]; + activities: CreateOrderDto[]; + accounts?: CreateAccountDto[]; isDryRun?: boolean; - }): Promise { + }): Promise<{ + accounts?: CreateAccountDto[]; + activities: Activity[]; + }> { return new Promise((resolve, reject) => { this.postImport( { - activities: content + activities, + accounts }, isDryRun ) @@ -80,22 +90,29 @@ export class ImportActivitiesService { ) .subscribe({ next: (data) => { - resolve(data.activities); + resolve(data); } }); }); } - public importSelectedActivities( - selectedActivities: Activity[] - ): Promise { + public importSelectedActivities({ + accounts, + activities + }: { + accounts?: CreateAccountDto[]; + activities: Activity[]; + }): Promise<{ + accounts?: CreateAccountDto[]; + activities: Activity[]; + }> { const importData: CreateOrderDto[] = []; - for (const activity of selectedActivities) { + for (const activity of activities) { importData.push(this.convertToCreateOrderDto(activity)); } - return this.importJson({ content: importData }); + return this.importJson({ activities: importData, accounts }); } private convertToCreateOrderDto({ @@ -347,7 +364,7 @@ export class ImportActivitiesService { } private postImport( - aImportData: { activities: CreateOrderDto[] }, + aImportData: { activities: CreateOrderDto[]; accounts: CreateAccountDto[] }, aIsDryRun = false ) { return this.http.post<{ activities: Activity[] }>( From e104ae17f8a5918913883eac0c4b4cd9b70fa369 Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Wed, 25 Jan 2023 22:59:37 +0530 Subject: [PATCH 05/12] Refactoring and bug fixes --- apps/api/src/app/account/account.service.ts | 7 ++ apps/api/src/app/import/import.service.ts | 76 +++++++------------ .../import-activities-dialog.component.ts | 3 +- 3 files changed, 37 insertions(+), 49 deletions(-) diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index 7c10fc31f..d144faa8b 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -104,6 +104,13 @@ export class AccountService { }); } + public async getAccountById(accountId: string) { + const accounts = await this.accounts({ + where: { id: accountId } + }); + return accounts.length ? accounts[0] : null; + } + public async getCashDetails({ currency, filters = [], diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2d09ca761..2e4f1044b 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -115,68 +115,48 @@ export class ImportService { userCurrency: string; userId: string; }): Promise { - const accountMappings = {}; - //Validate accounts - if (accountsDto?.length) { + const accountIdMapping = {}; + + if (!isDryRun && accountsDto?.length) { for (let account of accountsDto) { - const existingAccounts = await this.accountService.accounts({ - where: { id: account.id } - }); + //Check if there is any existing account with the same id + const accountWithSameId = await this.accountService.getAccountById( + account.id + ); - const oldAccountId = account.id; - const platformId = account.platformId; + //If there is no account or if the account belongs to a different user then create a new account + if (!accountWithSameId || accountWithSameId.userId !== userId) { + let oldAccountId: string; + const platformId = account.platformId; - delete account.id; - delete account.platformId; - delete account.isDefault; + delete account.platformId; + delete account.isDefault; + + if (accountWithSameId) { + oldAccountId = account.id; + delete account.id; + } - //If account id does not exist, then create a new one - if (existingAccounts.length === 0) { - const newAccountConfig = { + const newAccountObj = { ...account, User: { connect: { id: userId } } }; if (platformId) { - Object.assign(newAccountConfig, { + Object.assign(newAccountObj, { Platform: { connect: { id: platformId } } }); } const newAccount = await this.accountService.createAccount( - newAccountConfig, + newAccountObj, userId ); - accountMappings[oldAccountId] = newAccount.id; - continue; - } - - //If account id is used, then check if it belongs to the same user - //Yes -> Merge the accounts and don't create a new one - if (existingAccounts[0].userId === userId) { - continue; - } - - //No -> Replace the account id with a new account id as well as update all the activities when looping - - const newAccountConfig = { - ...account, - User: { connect: { id: userId } } - }; - - if (platformId) { - Object.assign(newAccountConfig, { - Platform: { connect: { id: platformId } } - }); + if (accountWithSameId && oldAccountId) { + accountIdMapping[oldAccountId] = newAccount.id; + } } - - const newAccount = await this.accountService.createAccount( - newAccountConfig, - userId - ); - - accountMappings[oldAccountId] = newAccount.id; } } @@ -189,9 +169,11 @@ export class ImportService { } } - //If we updated the account id, then update the account id in the activity as well - if (Object.keys(accountMappings).includes(activity.accountId)) { - activity.accountId = accountMappings[activity.accountId]; + if (!isDryRun) { + //If a new account is created, then update the accountId in all activities + if (Object.keys(accountIdMapping).includes(activity.accountId)) { + activity.accountId = accountIdMapping[activity.accountId]; + } } } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index b5d7b0a83..fd8685c9a 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -183,13 +183,12 @@ export class ImportActivitiesDialog implements OnDestroy { } try { + this.accounts = content.accounts; const data = await this.importActivitiesService.importJson({ activities: content.activities, - accounts: content.accounts, isDryRun: true }); this.activities = data.activities; - this.accounts = data.accounts; } catch (error) { console.error(error); this.handleImportError({ error, activities: content.activities }); From 912c10e6baffe7bbc9df6d23be0ec11a4ec7d67a Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Wed, 1 Feb 2023 23:18:49 +0530 Subject: [PATCH 06/12] Fix issue with activities for new accounts --- apps/api/src/app/import/import.service.ts | 5 +++-- .../import-activities-dialog.component.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2e4f1044b..c672f5bbc 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -117,7 +117,8 @@ export class ImportService { }): Promise { const accountIdMapping = {}; - if (!isDryRun && accountsDto?.length) { + //Create new accounts during dryRun so that new account Ids don't get invalidated + if (isDryRun && accountsDto?.length) { for (let account of accountsDto) { //Check if there is any existing account with the same id const accountWithSameId = await this.accountService.getAccountById( @@ -169,7 +170,7 @@ export class ImportService { } } - if (!isDryRun) { + if (isDryRun) { //If a new account is created, then update the accountId in all activities if (Object.keys(accountIdMapping).includes(activity.accountId)) { activity.accountId = accountIdMapping[activity.accountId]; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index fd8685c9a..80b20df2d 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -186,7 +186,8 @@ export class ImportActivitiesDialog implements OnDestroy { this.accounts = content.accounts; const data = await this.importActivitiesService.importJson({ activities: content.activities, - isDryRun: true + isDryRun: true, + accounts: this.accounts }); this.activities = data.activities; } catch (error) { From ab7b98630b0fa45b5a0a19125759804685110418 Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Wed, 1 Feb 2023 23:42:46 +0530 Subject: [PATCH 07/12] minor refactoring --- apps/api/src/app/account/account.service.ts | 14 +++++++------- apps/api/src/app/account/create-account.dto.ts | 16 ++++++++-------- apps/api/src/app/import/import-data.dto.ts | 2 +- apps/api/src/app/import/import.controller.ts | 6 +++--- apps/api/src/app/import/import.service.ts | 13 +++++++------ .../import-activities-dialog.component.ts | 4 ++-- .../app/services/import-activities.service.ts | 11 +++++------ .../src/lib/interfaces/export.interface.ts | 2 +- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index d144faa8b..32bf9f875 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -80,6 +80,13 @@ export class AccountService { }); } + public async getAccountById(accountId: string) { + const accounts = await this.accounts({ + where: { id: accountId } + }); + return accounts.length ? accounts[0] : null; + } + public async getAccounts(aUserId: string) { const accounts = await this.accounts({ include: { Order: true, Platform: true }, @@ -104,13 +111,6 @@ export class AccountService { }); } - public async getAccountById(accountId: string) { - const accounts = await this.accounts({ - where: { id: accountId } - }); - return accounts.length ? accounts[0] : null; - } - public async getCashDetails({ currency, filters = [], diff --git a/apps/api/src/app/account/create-account.dto.ts b/apps/api/src/app/account/create-account.dto.ts index 3c32163e0..69377bdd2 100644 --- a/apps/api/src/app/account/create-account.dto.ts +++ b/apps/api/src/app/account/create-account.dto.ts @@ -8,14 +8,6 @@ import { } from 'class-validator'; export class CreateAccountDto { - @IsOptional() - @IsString() - id: string; - - @IsOptional() - @IsBoolean() - isDefault: boolean; - @IsString() accountType: AccountType; @@ -25,6 +17,14 @@ export class CreateAccountDto { @IsString() currency: string; + @IsOptional() + @IsString() + id: string; + + @IsOptional() + @IsBoolean() + isDefault: boolean; + @IsBoolean() @IsOptional() isExcluded?: boolean; diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index aa141aab5..cfd866120 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -1,6 +1,6 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Type } from 'class-transformer'; -import { IsArray, ValidateNested, IsOptional } from 'class-validator'; +import { IsArray, IsOptional, ValidateNested } from 'class-validator'; import { CreateAccountDto } from '../account/create-account.dto'; export class ImportDataDto { diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index a1a1d4b1d..0de1c0b54 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -68,11 +68,11 @@ export class ImportController { try { const activities = await this.importService.import({ - maxActivitiesToImport, + accountsDto: importData.accounts || [], + activitiesDto: importData.activities, isDryRun, + maxActivitiesToImport, userCurrency, - activitiesDto: importData.activities, - accountsDto: importData.accounts || [], userId: this.request.user.id }); diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index c672f5bbc..c72d00eca 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,4 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; @@ -17,7 +18,6 @@ import { SymbolProfile } from '@prisma/client'; import Big from 'big.js'; import { endOfToday, isAfter, isSameDay, parseISO } from 'date-fns'; import { v4 as uuidv4 } from 'uuid'; -import { CreateAccountDto } from '../account/create-account.dto'; @Injectable() export class ImportService { @@ -101,15 +101,15 @@ export class ImportService { } public async import({ - activitiesDto, accountsDto, + activitiesDto, isDryRun = false, maxActivitiesToImport, userCurrency, userId }: { - activitiesDto: Partial[]; accountsDto: Partial[]; + activitiesDto: Partial[]; isDryRun?: boolean; maxActivitiesToImport: number; userCurrency: string; @@ -117,10 +117,10 @@ export class ImportService { }): Promise { const accountIdMapping = {}; - //Create new accounts during dryRun so that new account Ids don't get invalidated + //Create new accounts during dryRun so that new account IDs don't get invalidated if (isDryRun && accountsDto?.length) { for (let account of accountsDto) { - //Check if there is any existing account with the same id + //Check if there is any existing account with the same ID const accountWithSameId = await this.accountService.getAccountById( account.id ); @@ -154,6 +154,7 @@ export class ImportService { userId ); + //Store the new to old account ID mappings for updating activities if (accountWithSameId && oldAccountId) { accountIdMapping[oldAccountId] = newAccount.id; } @@ -170,8 +171,8 @@ export class ImportService { } } + //If a new account is created, then update the accountId in all activities if (isDryRun) { - //If a new account is created, then update the accountId in all activities if (Object.keys(accountIdMapping).includes(activity.accountId)) { activity.accountId = accountIdMapping[activity.accountId]; } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 80b20df2d..045cee17f 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -183,12 +183,12 @@ export class ImportActivitiesDialog implements OnDestroy { } try { - this.accounts = content.accounts; const data = await this.importActivitiesService.importJson({ activities: content.activities, isDryRun: true, - accounts: this.accounts + accounts: content.accounts }); + this.accounts = content.accounts; this.activities = data.activities; } catch (error) { console.error(error); diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index c2b0bbe05..357ad196a 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -35,7 +35,6 @@ export class ImportActivitiesService { isDryRun?: boolean; userAccounts: Account[]; }): Promise<{ - accounts?: CreateAccountDto[]; activities: Activity[]; }> { const content = csvToJson(fileContent, { @@ -71,14 +70,14 @@ export class ImportActivitiesService { accounts?: CreateAccountDto[]; isDryRun?: boolean; }): Promise<{ - accounts?: CreateAccountDto[]; activities: Activity[]; + accounts?: CreateAccountDto[]; }> { return new Promise((resolve, reject) => { this.postImport( { - activities, - accounts + accounts, + activities }, isDryRun ) @@ -100,11 +99,11 @@ export class ImportActivitiesService { accounts, activities }: { - accounts?: CreateAccountDto[]; activities: Activity[]; - }): Promise<{ accounts?: CreateAccountDto[]; + }): Promise<{ activities: Activity[]; + accounts?: CreateAccountDto[]; }> { const importData: CreateOrderDto[] = []; diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/export.interface.ts index 7b7470717..dcd7aa60d 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/export.interface.ts @@ -1,4 +1,4 @@ -import { Order, Account } from '@prisma/client'; +import { Account, Order } from '@prisma/client'; export interface Export { meta: { From 665e5ad6cdd44f2ff6844529ad58c1be1adca801 Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Sat, 4 Feb 2023 23:33:03 +0530 Subject: [PATCH 08/12] Resolve comments --- apps/api/src/app/account/create-account.dto.ts | 4 ---- apps/api/src/app/export/export.service.ts | 1 - apps/api/src/app/import/import-data.dto.ts | 2 +- apps/api/src/app/import/import.service.ts | 3 +-- .../import-activities-dialog.component.ts | 17 +++++++---------- .../app/services/import-activities.service.ts | 11 ++++------- .../src/lib/interfaces/export.interface.ts | 2 +- 7 files changed, 14 insertions(+), 26 deletions(-) diff --git a/apps/api/src/app/account/create-account.dto.ts b/apps/api/src/app/account/create-account.dto.ts index 69377bdd2..43e2584b7 100644 --- a/apps/api/src/app/account/create-account.dto.ts +++ b/apps/api/src/app/account/create-account.dto.ts @@ -21,10 +21,6 @@ export class CreateAccountDto { @IsString() id: string; - @IsOptional() - @IsBoolean() - isDefault: boolean; - @IsBoolean() @IsOptional() isExcluded?: boolean; diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index ebf0845cc..219b022cb 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -20,7 +20,6 @@ export class ExportService { balance: true, currency: true, id: true, - isDefault: true, isExcluded: true, name: true, platformId: true diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index cfd866120..fa954a2c9 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -1,7 +1,7 @@ +import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Type } from 'class-transformer'; import { IsArray, IsOptional, ValidateNested } from 'class-validator'; -import { CreateAccountDto } from '../account/create-account.dto'; export class ImportDataDto { @IsOptional() diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index c72d00eca..9210d9da8 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -115,7 +115,7 @@ export class ImportService { userCurrency: string; userId: string; }): Promise { - const accountIdMapping = {}; + const accountIdMapping: { [oldAccountId: string]: string } = {}; //Create new accounts during dryRun so that new account IDs don't get invalidated if (isDryRun && accountsDto?.length) { @@ -131,7 +131,6 @@ export class ImportService { const platformId = account.platformId; delete account.platformId; - delete account.isDefault; if (accountWithSameId) { oldAccountId = account.id; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 045cee17f..b4e118a30 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -11,7 +11,6 @@ import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; @@ -29,7 +28,6 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; templateUrl: 'import-activities-dialog.html' }) export class ImportActivitiesDialog implements OnDestroy { - public accounts: CreateAccountDto[] = []; public activities: Activity[] = []; public details: any[] = []; public errorMessages: string[] = []; @@ -94,7 +92,6 @@ export class ImportActivitiesDialog implements OnDestroy { this.snackBar.open('⏳ ' + $localize`Importing data...`); await this.importActivitiesService.importSelectedActivities({ - accounts: this.accounts, activities: this.selectedActivities }); @@ -183,13 +180,13 @@ export class ImportActivitiesDialog implements OnDestroy { } try { - const data = await this.importActivitiesService.importJson({ - activities: content.activities, - isDryRun: true, - accounts: content.accounts - }); - this.accounts = content.accounts; - this.activities = data.activities; + const { activities } = + await this.importActivitiesService.importJson({ + accounts: content.accounts, + activities: content.activities, + isDryRun: true + }); + this.activities = activities; } catch (error) { console.error(error); this.handleImportError({ error, activities: content.activities }); diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 357ad196a..d3ac9b2fb 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -58,12 +58,12 @@ export class ImportActivitiesService { }); } - return await this.importJson({ isDryRun, activities }); + return await this.importJson({ activities, isDryRun }); } public importJson({ - activities, accounts, + activities, isDryRun = false }: { activities: CreateOrderDto[]; @@ -96,14 +96,11 @@ export class ImportActivitiesService { } public importSelectedActivities({ - accounts, activities }: { activities: Activity[]; - accounts?: CreateAccountDto[]; }): Promise<{ activities: Activity[]; - accounts?: CreateAccountDto[]; }> { const importData: CreateOrderDto[] = []; @@ -111,7 +108,7 @@ export class ImportActivitiesService { importData.push(this.convertToCreateOrderDto(activity)); } - return this.importJson({ activities: importData, accounts }); + return this.importJson({ activities: importData }); } private convertToCreateOrderDto({ @@ -363,7 +360,7 @@ export class ImportActivitiesService { } private postImport( - aImportData: { activities: CreateOrderDto[]; accounts: CreateAccountDto[] }, + aImportData: { accounts: CreateAccountDto[]; activities: CreateOrderDto[] }, aIsDryRun = false ) { return this.http.post<{ activities: Activity[] }>( diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/export.interface.ts index dcd7aa60d..b142cb2f8 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/export.interface.ts @@ -5,7 +5,7 @@ export interface Export { date: string; version: string; }; - accounts: Omit[]; + accounts: Omit[]; activities: (Omit< Order, | 'accountUserId' From dcc857a2ba6f8827552eaa2741d3a2b375bb8abe Mon Sep 17 00:00:00 2001 From: yksolanki9 Date: Sun, 5 Feb 2023 13:40:42 +0530 Subject: [PATCH 09/12] Remove getAccountById method and its usage --- apps/api/src/app/account/account.service.ts | 7 ------- apps/api/src/app/import/import.service.ts | 12 ++++++++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index 32bf9f875..7c10fc31f 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -80,13 +80,6 @@ export class AccountService { }); } - public async getAccountById(accountId: string) { - const accounts = await this.accounts({ - where: { id: accountId } - }); - return accounts.length ? accounts[0] : null; - } - public async getAccounts(aUserId: string) { const accounts = await this.accounts({ include: { Order: true, Platform: true }, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 9210d9da8..5c3aa9e59 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -117,12 +117,20 @@ export class ImportService { }): Promise { const accountIdMapping: { [oldAccountId: string]: string } = {}; + const existingAccounts = await this.accountService.accounts({ + where: { + id: { + in: accountsDto.map((account) => account.id) + } + } + }); + //Create new accounts during dryRun so that new account IDs don't get invalidated if (isDryRun && accountsDto?.length) { for (let account of accountsDto) { //Check if there is any existing account with the same ID - const accountWithSameId = await this.accountService.getAccountById( - account.id + const accountWithSameId = existingAccounts.find( + (existingAccount) => existingAccount.id === account.id ); //If there is no account or if the account belongs to a different user then create a new account From f3157414a9151543bc7eb9b7f55923a89e4c71f6 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:07:43 +0100 Subject: [PATCH 10/12] Refactoring --- apps/api/src/app/export/export.service.ts | 3 +++ apps/api/src/app/import/import.controller.ts | 4 ++-- apps/api/src/app/import/import.service.ts | 22 +++++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 219b022cb..c57f1df4e 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -15,6 +15,9 @@ export class ExportService { userId: string; }): Promise { const accounts = await this.prismaService.account.findMany({ + orderBy: { + name: 'asc' + }, select: { accountType: true, balance: true, diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 0de1c0b54..06d9ad93d 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -68,11 +68,11 @@ export class ImportController { try { const activities = await this.importService.import({ - accountsDto: importData.accounts || [], - activitiesDto: importData.activities, isDryRun, maxActivitiesToImport, userCurrency, + accountsDto: importData.accounts ?? [], + activitiesDto: importData.activities, userId: this.request.user.id }); diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 5c3aa9e59..b9ca5eccd 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -120,20 +120,22 @@ export class ImportService { const existingAccounts = await this.accountService.accounts({ where: { id: { - in: accountsDto.map((account) => account.id) + in: accountsDto.map(({ id }) => { + return id; + }) } } }); - //Create new accounts during dryRun so that new account IDs don't get invalidated + // Create new accounts during dryRun so that new account IDs don't get invalidated if (isDryRun && accountsDto?.length) { - for (let account of accountsDto) { - //Check if there is any existing account with the same ID + for (const account of accountsDto) { + // Check if there is any existing account with the same ID const accountWithSameId = existingAccounts.find( (existingAccount) => existingAccount.id === account.id ); - //If there is no account or if the account belongs to a different user then create a new account + // If there is no account or if the account belongs to a different user then create a new account if (!accountWithSameId || accountWithSameId.userId !== userId) { let oldAccountId: string; const platformId = account.platformId; @@ -145,23 +147,23 @@ export class ImportService { delete account.id; } - const newAccountObj = { + const newAccountObject = { ...account, User: { connect: { id: userId } } }; if (platformId) { - Object.assign(newAccountObj, { + Object.assign(newAccountObject, { Platform: { connect: { id: platformId } } }); } const newAccount = await this.accountService.createAccount( - newAccountObj, + newAccountObject, userId ); - //Store the new to old account ID mappings for updating activities + // Store the new to old account ID mappings for updating activities if (accountWithSameId && oldAccountId) { accountIdMapping[oldAccountId] = newAccount.id; } @@ -178,7 +180,7 @@ export class ImportService { } } - //If a new account is created, then update the accountId in all activities + // If a new account is created, then update the accountId in all activities if (isDryRun) { if (Object.keys(accountIdMapping).includes(activity.accountId)) { activity.accountId = accountIdMapping[activity.accountId]; From 56307036fac716997a92bc58999c0e502d26df9a Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:07:53 +0100 Subject: [PATCH 11/12] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd26e71e..75eae6230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support to export accounts +- Added suport to import accounts + ### Changed - Migrated the style of `ActivitiesPageModule` to `@angular/material` `15` (mdc) From a56859d64bf4eebe6e283ffa7d39a0801f611594 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:56:00 +0100 Subject: [PATCH 12/12] Extend import test files --- test/import/ok-without-accounts.json | 48 ++++++++++++++++++++++++++++ test/import/ok.json | 21 +++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/import/ok-without-accounts.json diff --git a/test/import/ok-without-accounts.json b/test/import/ok-without-accounts.json new file mode 100644 index 000000000..63961be74 --- /dev/null +++ b/test/import/ok-without-accounts.json @@ -0,0 +1,48 @@ +{ + "meta": { + "date": "2022-04-01T00:00:00.000Z", + "version": "dev" + }, + "activities": [ + { + "fee": 0, + "quantity": 0, + "type": "BUY", + "unitPrice": 0, + "currency": "USD", + "dataSource": "YAHOO", + "date": "2050-06-05T22:00:00.000Z", + "symbol": "MSFT" + }, + { + "fee": 0, + "quantity": 1, + "type": "ITEM", + "unitPrice": 500000, + "currency": "USD", + "dataSource": "MANUAL", + "date": "2021-12-31T22:00:00.000Z", + "symbol": "Penthouse Apartment" + }, + { + "fee": 0, + "quantity": 5, + "type": "DIVIDEND", + "unitPrice": 0.62, + "currency": "USD", + "dataSource": "YAHOO", + "date": "2021-11-16T22:00:00.000Z", + "symbol": "MSFT" + }, + { + "fee": 19, + "quantity": 5, + "type": "BUY", + "unitPrice": 298.58, + "currency": "USD", + "dataSource": "YAHOO", + "date": "2021-09-15T22:00:00.000Z", + "symbol": "MSFT" + } + ] +} diff --git a/test/import/ok.json b/test/import/ok.json index 63961be74..335d2cd8a 100644 --- a/test/import/ok.json +++ b/test/import/ok.json @@ -1,10 +1,23 @@ { "meta": { - "date": "2022-04-01T00:00:00.000Z", + "date": "2023-02-05T00:00:00.000Z", "version": "dev" }, + "accounts": [ + { + "accountType": "SECURITIES", + "balance": 2000, + "currency": "USD", + "id": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "isExcluded": false, + "name": "My Online Trading Account", + "platformId": null + } + ], "activities": [ { + "accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "comment": null, "fee": 0, "quantity": 0, "type": "BUY", @@ -15,6 +28,8 @@ "symbol": "MSFT" }, { + "accountId": null, + "comment": null, "fee": 0, "quantity": 1, "type": "ITEM", @@ -25,6 +40,8 @@ "symbol": "Penthouse Apartment" }, { + "accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "comment": null, "fee": 0, "quantity": 5, "type": "DIVIDEND", @@ -35,6 +52,8 @@ "symbol": "MSFT" }, { + "accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "comment": "My first order", "fee": 19, "quantity": 5, "type": "BUY",