diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 313c12b35..f4ff7bc6d 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -203,6 +203,10 @@ export class OrderService { }): Promise { const { data, where } = params; + if (data.Account.connect.id_userId.id === null) { + delete data.Account; + } + if (data.type === 'ITEM') { const name = data.symbol; diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index 58a046c5b..180da6dbf 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -1,7 +1,8 @@ import { DataSource, Type } from '@prisma/client'; -import { IsISO8601, IsNumber, IsString } from 'class-validator'; +import { IsISO8601, IsNumber, IsOptional, IsString } from 'class-validator'; export class UpdateOrderDto { + @IsOptional() @IsString() accountId: string; 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 cdbe3c839..05c02bcb4 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 @@ -6,7 +6,7 @@ import { OnDestroy, ViewChild } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, 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'; @@ -14,6 +14,7 @@ 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 { isUUID } from 'class-validator'; import { isString } from 'lodash'; import { EMPTY, Observable, Subject } from 'rxjs'; import { @@ -37,25 +38,15 @@ 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 activityForm: FormGroup; + 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 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); + public Validators = Validators; private unsubscribeSubject = new Subject(); @@ -63,6 +54,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, public dialogRef: MatDialogRef, + private formBuilder: FormBuilder, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTransactionDialogParams ) {} @@ -72,78 +64,98 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.currencies = currencies; this.platforms = platforms; - this.filteredLookupItemsObservable = - this.searchSymbolCtrl.valueChanges.pipe( - startWith(''), - debounceTime(400), - distinctUntilChanged(), - switchMap((query: string) => { - if (isString(query)) { - const filteredLookupItemsObservable = - this.dataService.fetchSymbols(query); - - filteredLookupItemsObservable.subscribe((filteredLookupItems) => { - this.filteredLookupItems = filteredLookupItems; - }); - - return filteredLookupItemsObservable; - } + this.activityForm = this.formBuilder.group({ + accountId: [this.data.activity?.accountId, Validators.required], + currency: [ + this.data.activity?.SymbolProfile?.currency, + Validators.required + ], + dataSource: [ + this.data.activity?.SymbolProfile?.dataSource, + Validators.required + ], + date: [this.data.activity?.date, Validators.required], + fee: [this.data.activity?.fee, Validators.required], + name: [this.data.activity?.SymbolProfile?.name, Validators.required], + quantity: [this.data.activity?.quantity, Validators.required], + searchSymbol: [ + { + dataSource: this.data.activity?.SymbolProfile?.dataSource, + symbol: this.data.activity?.SymbolProfile?.symbol + }, + Validators.required + ], + type: [undefined, Validators.required], // Set after value changes subscription + unitPrice: [this.data.activity?.unitPrice, Validators.required] + }); - return []; - }) - ); + this.filteredLookupItemsObservable = this.activityForm.controls[ + 'searchSymbol' + ].valueChanges.pipe( + startWith(''), + debounceTime(400), + distinctUntilChanged(), + switchMap((query: string) => { + if (isString(query)) { + const filteredLookupItemsObservable = + this.dataService.fetchSymbols(query); + + filteredLookupItemsObservable.subscribe((filteredLookupItems) => { + this.filteredLookupItems = filteredLookupItems; + }); + + return filteredLookupItemsObservable; + } + + return []; + }) + ); - this.typeCtrl.valueChanges.subscribe((type: Type) => { + this.activityForm.controls['type'].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; + this.activityForm.controls['accountId'].removeValidators( + Validators.required + ); + this.activityForm.controls['accountId'].updateValueAndValidity(); + this.activityForm.controls['currency'].setValue( + this.data.user.settings.baseCurrency + ); + this.activityForm.controls['dataSource'].removeValidators( + Validators.required + ); + this.activityForm.controls['dataSource'].updateValueAndValidity(); + this.activityForm.controls['name'].setValidators(Validators.required); + this.activityForm.controls['name'].updateValueAndValidity(); + this.activityForm.controls['quantity'].setValue(1); + this.activityForm.controls['searchSymbol'].removeValidators( + Validators.required + ); + this.activityForm.controls['searchSymbol'].updateValueAndValidity(); } 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.activityForm.controls['accountId'].setValidators( + Validators.required + ); + this.activityForm.controls['accountId'].updateValueAndValidity(); + this.activityForm.controls['dataSource'].setValidators( + Validators.required + ); + this.activityForm.controls['dataSource'].updateValueAndValidity(); + this.activityForm.controls['name'].removeValidators( + Validators.required + ); + this.activityForm.controls['name'].updateValueAndValidity(); + this.activityForm.controls['searchSymbol'].setValidators( + Validators.required + ); + this.activityForm.controls['searchSymbol'].updateValueAndValidity(); } - - 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); + this.activityForm.controls['type'].setValue(this.data.activity?.type); if (this.data.activity?.id) { - this.searchSymbolCtrl.disable(); - this.typeCtrl.disable(); + this.activityForm.controls['searchSymbol'].disable(); + this.activityForm.controls['type'].disable(); } if (this.data.activity?.symbol) { @@ -162,7 +174,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { } public applyCurrentMarketPrice() { - this.unitPriceCtrl.setValue(this.currentMarketPrice); + this.activityForm.patchValue({ + unitPrice: this.currentMarketPrice + }); } public displayFn(aLookupItem: LookupItem) { @@ -171,13 +185,16 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { public onBlurSymbol() { const currentLookupItem = this.filteredLookupItems.find((lookupItem) => { - return lookupItem.symbol === this.data.activity.symbol; + return ( + lookupItem.symbol === + this.activityForm.controls['searchSymbol'].value.symbol + ); }); if (currentLookupItem) { this.updateSymbol(currentLookupItem.symbol); } else { - this.searchSymbolCtrl.setErrors({ incorrect: true }); + this.activityForm.controls['searchSymbol'].setErrors({ incorrect: true }); this.data.activity.currency = null; this.data.activity.dataSource = null; @@ -193,15 +210,17 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { 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 + accountId: this.activityForm.controls['accountId'].value, + currency: this.activityForm.controls['currency'].value, + date: this.activityForm.controls['date'].value, + dataSource: this.activityForm.controls['dataSource'].value, + fee: this.activityForm.controls['fee'].value, + quantity: this.activityForm.controls['quantity'].value, + symbol: isUUID(this.activityForm.controls['searchSymbol'].value.symbol) + ? this.activityForm.controls['name'].value + : this.activityForm.controls['searchSymbol'].value.symbol, + type: this.activityForm.controls['type'].value, + unitPrice: this.activityForm.controls['unitPrice'].value }; if (this.data.activity.id) { @@ -212,7 +231,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { } public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { - this.dataSourceCtrl.setValue(event.option.value.dataSource); + this.activityForm.controls['dataSource'].setValue( + event.option.value.dataSource + ); this.updateSymbol(event.option.value.symbol); } @@ -224,16 +245,15 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { private updateSymbol(symbol: string) { this.isLoading = true; - this.searchSymbolCtrl.setErrors(null); - - this.searchSymbolCtrl.setValue({ symbol }); + this.activityForm.controls['searchSymbol'].setErrors(null); + this.activityForm.controls['searchSymbol'].setValue({ symbol }); this.changeDetectorRef.markForCheck(); this.dataService .fetchSymbolItem({ - dataSource: this.dataSourceCtrl.value, - symbol: this.searchSymbolCtrl.value.symbol + dataSource: this.activityForm.controls['dataSource'].value, + symbol: this.activityForm.controls['searchSymbol'].value.symbol }) .pipe( catchError(() => { @@ -250,8 +270,8 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { takeUntil(this.unsubscribeSubject) ) .subscribe(({ currency, dataSource, marketPrice }) => { - this.currencyCtrl.setValue(currency); - this.dataSourceCtrl.setValue(dataSource); + this.activityForm.controls['currency'].setValue(currency); + this.activityForm.controls['dataSource'].setValue(dataSource); this.currentMarketPrice = marketPrice; 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 1de007517..212b3c245 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,11 +1,15 @@ -
+

Update activity

Add activity

Type - + BUY DIVIDEND ITEM @@ -13,25 +17,29 @@
-
+
Account - + {{ account.name }}
-
+
Symbol or ISIN @@ -54,20 +62,18 @@
-
+
Name - +
Currency - + {{ currency }} @@ -77,18 +83,13 @@
Data Source - +
Date - + Quantity - +
Unit Price - - {{ currencyCtrl.value }} + + {{ activityForm.controls['currency'].value }}
- +