From beb4ee23fbefec049ed11557049a7c3a7d007ea3 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:18:11 +0100 Subject: [PATCH] Extend form --- apps/api/src/app/export/export.service.ts | 3 +- apps/api/src/app/order/order.service.ts | 17 ++- ...-or-update-transaction-dialog.component.ts | 131 ++++++++++++++---- .../create-or-update-transaction-dialog.html | 79 +++++------ .../interfaces/interfaces.ts | 5 +- .../transactions-page.component.ts | 32 +---- apps/client/src/app/services/admin.service.ts | 2 +- libs/common/src/lib/helper.ts | 6 + .../activities-table.component.html | 7 +- .../activities-table.component.ts | 8 +- 10 files changed, 183 insertions(+), 107 deletions(-) diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 301f13cea..c092c347e 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -1,5 +1,6 @@ import { environment } from '@ghostfolio/api/environments/environment'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { isUUID } from '@ghostfolio/common/helper'; import { Export } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; @@ -59,7 +60,7 @@ export class ExportService { type, unitPrice, dataSource: SymbolProfile.dataSource, - symbol: SymbolProfile.symbol + symbol: type === 'ITEM' ? SymbolProfile.name : SymbolProfile.symbol }; } ) diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 5b5251899..313c12b35 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -59,7 +59,7 @@ export class OrderService { return account.isDefault === true; }); - const Account = { + let Account = { connect: { id_userId: { userId: data.userId, @@ -70,14 +70,22 @@ export class OrderService { if (data.type === 'ITEM') { const currency = data.currency; + const dataSource: DataSource = 'MANUAL'; const id = uuidv4(); const name = data.SymbolProfile.connectOrCreate.create.symbol; + Account = undefined; + data.dataSource = dataSource; data.id = id; data.symbol = null; data.SymbolProfile.connectOrCreate.create.currency = currency; + data.SymbolProfile.connectOrCreate.create.dataSource = dataSource; data.SymbolProfile.connectOrCreate.create.name = name; data.SymbolProfile.connectOrCreate.create.symbol = id; + data.SymbolProfile.connectOrCreate.where.dataSource_symbol = { + dataSource, + symbol: id + }; } else { data.SymbolProfile.connectOrCreate.create.symbol = data.SymbolProfile.connectOrCreate.create.symbol.toUpperCase(); @@ -195,6 +203,13 @@ export class OrderService { }): Promise { const { data, where } = params; + if (data.type === 'ITEM') { + const name = data.symbol; + + data.symbol = null; + data.SymbolProfile = { update: { name } }; + } + const isDraft = isAfter(data.date as Date, endOfToday()); if (!isDraft) { diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts index 974bbcce1..cdbe3c839 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts @@ -9,8 +9,11 @@ import { import { FormControl, Validators } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; +import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { Type } from '@prisma/client'; import { isString } from 'lodash'; import { EMPTY, Observable, Subject } from 'rxjs'; import { @@ -34,19 +37,25 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces'; export class CreateOrUpdateTransactionDialog implements OnDestroy { @ViewChild('autocomplete') autocomplete; + public accountIdCtrl = new FormControl({}, Validators.required); + public currencyCtrl = new FormControl({}, Validators.required); public currencies: string[] = []; public currentMarketPrice = null; + public dataSourceCtrl = new FormControl({}, Validators.required); + public dateCtrl = new FormControl({}, Validators.required); + public feeCtrl = new FormControl({}, Validators.required); public filteredLookupItems: LookupItem[]; public filteredLookupItemsObservable: Observable; public isLoading = false; + public nameCtrl = new FormControl({}, Validators.required); public platforms: { id: string; name: string }[]; - public searchSymbolCtrl = new FormControl( - { - dataSource: this.data.transaction.dataSource, - symbol: this.data.transaction.symbol - }, - Validators.required - ); + public quantityCtrl = new FormControl({}, Validators.required); + public searchSymbolCtrl = new FormControl({}, Validators.required); + public showAccountIdCtrl = true; + public showNameCtrl = true; + public showSearchSymbolCtrl = true; + public typeCtrl = new FormControl({}, Validators.required); + public unitPriceCtrl = new FormControl({}, Validators.required); private unsubscribeSubject = new Subject(); @@ -84,15 +93,64 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { }) ); - if (this.data.transaction.id) { + this.typeCtrl.valueChanges.subscribe((type: Type) => { + if (type === 'ITEM') { + this.accountIdCtrl.removeValidators(Validators.required); + this.accountIdCtrl.updateValueAndValidity(); + this.currencyCtrl.setValue(this.data.user.settings.baseCurrency); + this.dataSourceCtrl.removeValidators(Validators.required); + this.dataSourceCtrl.updateValueAndValidity(); + this.nameCtrl.setValidators(Validators.required); + this.nameCtrl.updateValueAndValidity(); + this.quantityCtrl.setValue(1); + this.searchSymbolCtrl.removeValidators(Validators.required); + this.searchSymbolCtrl.updateValueAndValidity(); + this.showAccountIdCtrl = false; + this.showNameCtrl = true; + this.showSearchSymbolCtrl = false; + } else { + this.accountIdCtrl.setValidators(Validators.required); + this.accountIdCtrl.updateValueAndValidity(); + this.currencyCtrl.setValue(undefined); + this.dataSourceCtrl.setValidators(Validators.required); + this.dataSourceCtrl.updateValueAndValidity(); + this.nameCtrl.removeValidators(Validators.required); + this.nameCtrl.updateValueAndValidity(); + this.quantityCtrl.setValue(undefined); + this.searchSymbolCtrl.setValidators(Validators.required); + this.searchSymbolCtrl.updateValueAndValidity(); + this.showAccountIdCtrl = true; + this.showNameCtrl = false; + this.showSearchSymbolCtrl = true; + } + + this.changeDetectorRef.markForCheck(); + }); + + this.accountIdCtrl.setValue(this.data.activity?.accountId); + this.currencyCtrl.setValue(this.data.activity?.currency); + this.dataSourceCtrl.setValue(this.data.activity?.dataSource); + this.dateCtrl.setValue(this.data.activity?.date); + this.feeCtrl.setValue(this.data.activity?.fee); + this.nameCtrl.setValue(this.data.activity?.SymbolProfile?.name); + this.quantityCtrl.setValue(this.data.activity?.quantity); + this.searchSymbolCtrl.setValue({ + dataSource: this.data.activity?.dataSource, + symbol: this.data.activity?.symbol + }); + this.typeCtrl.setValue(this.data.activity?.type); + this.unitPriceCtrl.setValue(this.data.activity?.unitPrice); + + if (this.data.activity?.id) { this.searchSymbolCtrl.disable(); + this.typeCtrl.disable(); } - if (this.data.transaction.symbol) { + if (this.data.activity?.symbol) { this.dataService .fetchSymbolItem({ - dataSource: this.data.transaction.dataSource, - symbol: this.data.transaction.symbol + dataSource: this.data.activity?.dataSource, + symbol: this.data.activity?.symbol }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ marketPrice }) => { @@ -104,7 +162,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { } public applyCurrentMarketPrice() { - this.data.transaction.unitPrice = this.currentMarketPrice; + this.unitPriceCtrl.setValue(this.currentMarketPrice); } public displayFn(aLookupItem: LookupItem) { @@ -113,7 +171,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { public onBlurSymbol() { const currentLookupItem = this.filteredLookupItems.find((lookupItem) => { - return lookupItem.symbol === this.data.transaction.symbol; + return lookupItem.symbol === this.data.activity.symbol; }); if (currentLookupItem) { @@ -121,9 +179,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { } else { this.searchSymbolCtrl.setErrors({ incorrect: true }); - this.data.transaction.currency = null; - this.data.transaction.dataSource = null; - this.data.transaction.symbol = null; + this.data.activity.currency = null; + this.data.activity.dataSource = null; + this.data.activity.symbol = null; } this.changeDetectorRef.markForCheck(); @@ -133,8 +191,28 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.dialogRef.close(); } + public onSubmit() { + const activity: CreateOrderDto | UpdateOrderDto = { + accountId: this.accountIdCtrl.value, + currency: this.currencyCtrl.value, + date: this.dateCtrl.value, + dataSource: this.dataSourceCtrl.value, + fee: this.feeCtrl.value, + quantity: this.quantityCtrl.value, + symbol: this.searchSymbolCtrl.value.symbol ?? this.nameCtrl.value, + type: this.typeCtrl.value, + unitPrice: this.unitPriceCtrl.value + }; + + if (this.data.activity.id) { + (activity as UpdateOrderDto).id = this.data.activity.id; + } + + this.dialogRef.close({ activity }); + } + public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { - this.data.transaction.dataSource = event.option.value.dataSource; + this.dataSourceCtrl.setValue(event.option.value.dataSource); this.updateSymbol(event.option.value.symbol); } @@ -148,18 +226,20 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.searchSymbolCtrl.setErrors(null); - this.data.transaction.symbol = symbol; + this.searchSymbolCtrl.setValue({ symbol }); + + this.changeDetectorRef.markForCheck(); this.dataService .fetchSymbolItem({ - dataSource: this.data.transaction.dataSource, - symbol: this.data.transaction.symbol + dataSource: this.dataSourceCtrl.value, + symbol: this.searchSymbolCtrl.value.symbol }) .pipe( catchError(() => { - this.data.transaction.currency = null; - this.data.transaction.dataSource = null; - this.data.transaction.unitPrice = null; + this.data.activity.currency = null; + this.data.activity.dataSource = null; + this.data.activity.unitPrice = null; this.isLoading = false; @@ -170,8 +250,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { takeUntil(this.unsubscribeSubject) ) .subscribe(({ currency, dataSource, marketPrice }) => { - this.data.transaction.currency = currency; - this.data.transaction.dataSource = dataSource; + this.currencyCtrl.setValue(currency); + this.dataSourceCtrl.setValue(dataSource); + this.currentMarketPrice = marketPrice; this.isLoading = false; diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html index 1cf1401b3..1de007517 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -1,22 +1,29 @@
-

Update activity

-

Add activity

+

Update activity

+

Add activity

+ + Type + + BUY + DIVIDEND + ITEM + SELL + + +
+
Account - + {{ account.name }}
-
+
Symbol or ISIN
-
+
- Type - - BUY - DIVIDEND - ITEM - SELL - + Name +
@@ -64,10 +65,8 @@ Currency {{ currency }} Data Source - +
Date
@@ -126,13 +116,12 @@ - {{ data.transaction.currency }} + {{ currencyCtrl.value }}
@@ -169,8 +152,8 @@ color="primary" i18n mat-flat-button - [disabled]="!(addTransactionForm.form.valid && data.transaction.currency && data.transaction.symbol)" - [mat-dialog-close]="data" + [disabled]="!(addTransactionForm.form.valid)" + (click)="onSubmit()" > Save diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts index da122b585..b4b15dfdb 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts @@ -1,9 +1,10 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { User } from '@ghostfolio/common/interfaces'; -import { Account, Order } from '@prisma/client'; +import { Account } from '@prisma/client'; export interface CreateOrUpdateTransactionDialogParams { accountId: string; accounts: Account[]; - transaction: Order; + activity: Activity; user: User; } 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 5d165c40f..f77623044 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 @@ -242,35 +242,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { }); } - public openUpdateTransactionDialog({ - accountId, - currency, - dataSource, - date, - fee, - id, - quantity, - symbol, - type, - unitPrice - }: OrderModel): void { + public openUpdateTransactionDialog(activity: Activity): void { const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, { data: { + activity, accounts: this.user?.accounts?.filter((account) => { return account.accountType === 'SECURITIES'; }), - transaction: { - accountId, - currency, - dataSource, - date, - fee, - id, - quantity, - symbol, - type, - unitPrice - }, user: this.user }, height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', @@ -281,7 +259,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((data: any) => { - const transaction: UpdateOrderDto = data?.transaction; + const transaction: UpdateOrderDto = data?.activity; if (transaction) { this.dataService @@ -336,7 +314,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { accounts: this.user?.accounts?.filter((account) => { return account.accountType === 'SECURITIES'; }), - transaction: { + activity: { accountId: aTransaction?.accountId ?? this.defaultAccountId, currency: aTransaction?.currency ?? null, dataSource: aTransaction?.dataSource ?? null, @@ -357,7 +335,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((data: any) => { - const transaction: CreateOrderDto = data?.transaction; + const transaction: CreateOrderDto = data?.activity; if (transaction) { this.dataService.postOrder(transaction).subscribe({ diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 8b75473d1..ff6e624d0 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -6,7 +6,7 @@ import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails } from '@ghostfolio/common/interfaces'; import { DataSource, MarketData } from '@prisma/client'; import { format, parseISO } from 'date-fns'; -import { map, Observable } from 'rxjs'; +import { Observable, map } from 'rxjs'; @Injectable({ providedIn: 'root' diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index dbfc787f3..4ddb88fcf 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -106,6 +106,12 @@ export function isGhostfolioScraperApiSymbol(aSymbol = '') { return aSymbol.startsWith(ghostfolioScraperApiSymbolPrefix); } +export function isUUID(aString: string) { + const regexExp = + /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; + return regexExp.test(aString); +} + export function resetHours(aDate: Date) { const year = getYear(aDate); const month = getMonth(aDate); 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 ccf2e6a85..4cb943f55 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -115,7 +115,12 @@
- {{ element.SymbolProfile.symbol | gfSymbol }} + + {{ element.SymbolProfile.name }} + + + {{ element.SymbolProfile.symbol | gfSymbol }} + Draft diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index c7771772a..d9762ea30 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -21,6 +21,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; +import { isUUID } from '@ghostfolio/common/helper'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { DataSource } from '@prisma/client'; import Big from 'big.js'; @@ -69,6 +70,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy { public filters: Observable = this.filters$.asObservable(); public isAfter = isAfter; public isLoading = true; + public isUUID = isUUID; public placeholder = ''; public routeQueryParams: Subscription; public searchControl = new FormControl(); @@ -274,7 +276,11 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy { fieldValues.add(activity.Account?.name); fieldValues.add(activity.Account?.Platform?.name); fieldValues.add(activity.SymbolProfile.currency); - fieldValues.add(activity.SymbolProfile.symbol); + fieldValues.add( + isUUID(activity.SymbolProfile.symbol) + ? activity.SymbolProfile.name + : activity.SymbolProfile.symbol + ); fieldValues.add(activity.type); fieldValues.add(format(activity.date, 'yyyy'));