From f8bfe72f06b4ffe6f650dbaef3e8605793cbe2ea Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Mon, 2 Jan 2023 13:47:46 +0100 Subject: [PATCH] Integrate in ImportActivitiesDialog --- apps/api/src/app/symbol/symbol.controller.ts | 2 + apps/api/src/app/symbol/symbol.module.ts | 4 +- apps/api/src/app/symbol/symbol.service.ts | 84 +++++++++++-------- .../yahoo-finance/yahoo-finance.service.ts | 1 + .../activities/activities-page.component.ts | 19 +++-- .../portfolio/activities/activities-page.html | 2 +- .../import-activities-dialog.component.ts | 65 ++++++++++++-- .../import-activities-dialog.html | 76 +++++++++++------ .../import-activities-dialog.module.ts | 9 +- .../interfaces/interfaces.ts | 2 + .../enhanced-symbol-profile.interface.ts | 2 +- .../activities-table.component.html | 30 +++---- .../activities-table.component.ts | 21 +++-- 13 files changed, 220 insertions(+), 97 deletions(-) diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index 8741a2df2..c9fcc588a 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -78,6 +78,8 @@ export class SymbolController { @Get(':dataSource/:symbol/dividends') @UseGuards(AuthGuard('jwt')) + @UseInterceptors(TransformDataSourceInRequestInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async gatherDividends( @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string diff --git a/apps/api/src/app/symbol/symbol.module.ts b/apps/api/src/app/symbol/symbol.module.ts index 2b47334a6..cea88bde2 100644 --- a/apps/api/src/app/symbol/symbol.module.ts +++ b/apps/api/src/app/symbol/symbol.module.ts @@ -2,6 +2,7 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration.modu import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; +import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module'; import { Module } from '@nestjs/common'; import { SymbolController } from './symbol.controller'; @@ -14,7 +15,8 @@ import { SymbolService } from './symbol.service'; ConfigurationModule, DataProviderModule, MarketDataModule, - PrismaModule + PrismaModule, + SymbolProfileModule ], providers: [SymbolService] }) diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 905590930..cb29b2c2b 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -5,12 +5,14 @@ import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; +import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { HistoricalDataItem, ImportResponse } from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; +import { SymbolProfile } from '@prisma/client'; import { format, subDays, subYears } from 'date-fns'; import { LookupItem } from './interfaces/lookup-item.interface'; @@ -21,6 +23,7 @@ export class SymbolService { public constructor( private readonly dataProviderService: DataProviderService, private readonly marketDataService: MarketDataService, + private readonly symbolProfileService: SymbolProfileService, private readonly yahooFinanceService: YahooFinanceService ) {} @@ -71,41 +74,54 @@ export class SymbolService { dataSource, symbol }: IDataGatheringItem): Promise { - const date = new Date(); - - // TODO: Use DataProviderService - const historicalData = await this.yahooFinanceService.getDividends( - symbol, - 'day', - subYears(date, 5), - date - ); + try { + const date = new Date(); + + const [[assetProfile], historicalData] = await Promise.all([ + this.symbolProfileService.getSymbolProfiles([ + { + dataSource, + symbol + } + ]), + // TODO: Use DataProviderService + this.yahooFinanceService.getDividends( + symbol, + 'day', + subYears(date, 5), + date + ) + ]); - return { - activities: Object.entries(historicalData[symbol]).map( - ([dateString, historicalDataItem]) => { - return { - accountId: undefined, - accountUserId: undefined, - comment: undefined, - createdAt: undefined, - date: parseDate(dateString), - fee: 0, - feeInBaseCurrency: 0, - id: undefined, - isDraft: false, - quantity: 0, - symbolProfileId: undefined, - type: 'DIVIDEND', - unitPrice: historicalDataItem.marketPrice, - updatedAt: undefined, - userId: undefined, - value: 0, - valueInBaseCurrency: 0 - }; - } - ) - }; + return { + activities: Object.entries(historicalData[symbol]).map( + ([dateString, historicalDataItem]) => { + return { + accountId: undefined, + accountUserId: undefined, + comment: undefined, + createdAt: undefined, + date: parseDate(dateString), + fee: 0, + feeInBaseCurrency: 0, + id: assetProfile.id, + isDraft: false, + quantity: 0, + SymbolProfile: (assetProfile), + symbolProfileId: undefined, + type: 'DIVIDEND', + unitPrice: historicalDataItem.marketPrice, + updatedAt: undefined, + userId: undefined, + value: 0, + valueInBaseCurrency: 0 + }; + } + ) + }; + } catch { + return { activities: [] }; + } } public async getForDate({ diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index 25026ee01..2fcb3e62c 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -217,6 +217,7 @@ export class YahooFinanceService implements DataProviderInterface { return response; } catch (error) { + // TODO: Log error and return empty response throw new Error( `Could not get historical market data for ${aSymbol} (${this.getName()}) from ${format( from, diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index cbd9f5905..41d8abae1 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -198,12 +198,21 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { }); } - public onImportDividends({ dataSource, symbol }: UniqueAsset) { - this.dataService - .fetchDividendsImport({ dataSource, symbol }) + public onImportDividends() { + const dialogRef = this.dialog.open(ImportActivitiesDialog, { + data: { + activityTypes: ['DIVIDEND'], + deviceType: this.deviceType, + user: this.user + }, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ activities }) => { - console.log(activities); + .subscribe(() => { + this.fetchActivities(); }); } 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 40e35caae..cec456c53 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -17,7 +17,7 @@ (export)="onExport($event)" (exportDrafts)="onExportDrafts($event)" (import)="onImport()" - (importDividends)="onImportDividends($event)" + (importDividends)="onImportDividends()" > 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 6a0acfb63..77ef8dfe8 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 @@ -5,12 +5,16 @@ import { Inject, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; 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'; -import { isArray } from 'lodash'; -import { Subject } from 'rxjs'; +import { Position } from '@ghostfolio/common/interfaces'; +import { AssetClass } from '@prisma/client'; +import { isArray, sortBy } from 'lodash'; +import { Subject, takeUntil } from 'rxjs'; import { ImportActivitiesDialogParams } from './interfaces/interfaces'; @@ -24,20 +28,55 @@ export class ImportActivitiesDialog implements OnDestroy { public activities: Activity[] = []; public details: any[] = []; public errorMessages: string[] = []; + public holdings: Position[] = []; public isFileSelected = false; + public mode: 'DIVIDEND'; public selectedActivities: Activity[] = []; + public uniqueAssetForm: FormGroup; private unsubscribeSubject = new Subject(); public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, + private dataService: DataService, + private formBuilder: FormBuilder, public dialogRef: MatDialogRef, private importActivitiesService: ImportActivitiesService, private snackBar: MatSnackBar ) {} - public ngOnInit() {} + public ngOnInit() { + this.uniqueAssetForm = this.formBuilder.group({ + uniqueAsset: [undefined, Validators.required] + }); + + if ( + this.data?.activityTypes?.length === 1 && + this.data?.activityTypes?.[0] === 'DIVIDEND' + ) { + this.mode = 'DIVIDEND'; + + this.dataService + .fetchPositions({ + filters: [ + { + id: AssetClass.EQUITY, + type: 'ASSET_CLASS' + } + ], + range: 'max' + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ positions }) => { + this.holdings = sortBy(positions, ({ name }) => { + return name.toLowerCase(); + }); + + this.changeDetectorRef.markForCheck(); + }); + } + } public onCancel(): void { this.dialogRef.close(); @@ -71,6 +110,24 @@ export class ImportActivitiesDialog implements OnDestroy { } } + public onLoadDividends() { + const { dataSource, symbol } = + this.uniqueAssetForm.controls['uniqueAsset'].value; + + this.dataService + .fetchDividendsImport({ + dataSource, + symbol + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ activities }) => { + this.activities = activities; + this.isFileSelected = true; + + this.changeDetectorRef.markForCheck(); + }); + } + public onReset() { this.details = []; this.errorMessages = []; @@ -95,8 +152,6 @@ export class ImportActivitiesDialog implements OnDestroy { reader.onload = async (readerEvent) => { const fileContent = readerEvent.target.result as string; - console.log(fileContent); - try { if (file.name.endsWith('.json')) { const content = JSON.parse(fileContent); diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index 7b5ad648b..9e68ec194 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -7,31 +7,59 @@
-
- -

- The following file formats are supported: - CSV - or - JSON +

+ + Holding + + {{ holding.name }} + + +
+ +
+
+ + +
+
+ + Choose File + +

+ The following file formats are supported: + CSV + or + JSON +

+
+
diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts index cba5842f8..fdae625f9 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts @@ -1,8 +1,11 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatExpansionModule } from '@angular/material/expansion'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; @@ -13,12 +16,16 @@ import { ImportActivitiesDialog } from './import-activities-dialog.component'; declarations: [ImportActivitiesDialog], imports: [ CommonModule, + FormsModule, GfActivitiesTableModule, GfDialogFooterModule, GfDialogHeaderModule, MatButtonModule, MatDialogModule, - MatExpansionModule + MatExpansionModule, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts index 5141ed11c..755a50baf 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts @@ -1,6 +1,8 @@ import { User } from '@ghostfolio/common/interfaces'; +import { Type } from '@prisma/client'; export interface ImportActivitiesDialogParams { + activityTypes: Type[]; deviceType: string; user: User; } diff --git a/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts index d2053bb7c..5b72d9ce7 100644 --- a/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts +++ b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts @@ -8,7 +8,7 @@ export interface EnhancedSymbolProfile { activitiesCount: number; assetClass: AssetClass; assetSubClass: AssetSubClass; - comment?: string; + comment: string | null; countries: Country[]; createdAt: Date; currency: string | null; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 682d19057..7c43c3ba9 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -67,7 +67,7 @@
- {{ element.date | date : defaultDateFormat }} + {{ element.date | date: defaultDateFormat }}
Total @@ -117,7 +117,7 @@
- {{ element.SymbolProfile.name }} + {{ element.SymbolProfile?.name }}
-
+
{{ - element.SymbolProfile.symbol | gfSymbol + element.SymbolProfile?.symbol | gfSymbol }}
@@ -149,7 +149,7 @@ class="d-none d-lg-table-cell px-1" mat-cell > - {{ element.SymbolProfile.currency }} + {{ element.SymbolProfile?.currency }} {{ baseCurrency }} @@ -388,6 +388,14 @@ Import Activities + -