|
|
|
@ -6,11 +6,15 @@ 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';
|
|
|
|
|
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 {
|
|
|
|
@ -34,19 +38,15 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
|
|
|
|
|
export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
@ViewChild('autocomplete') autocomplete;
|
|
|
|
|
|
|
|
|
|
public activityForm: FormGroup;
|
|
|
|
|
|
|
|
|
|
public currencies: string[] = [];
|
|
|
|
|
public currentMarketPrice = null;
|
|
|
|
|
public filteredLookupItems: LookupItem[];
|
|
|
|
|
public filteredLookupItemsObservable: Observable<LookupItem[]>;
|
|
|
|
|
public isLoading = false;
|
|
|
|
|
public platforms: { id: string; name: string }[];
|
|
|
|
|
public searchSymbolCtrl = new FormControl(
|
|
|
|
|
{
|
|
|
|
|
dataSource: this.data.transaction.dataSource,
|
|
|
|
|
symbol: this.data.transaction.symbol
|
|
|
|
|
},
|
|
|
|
|
Validators.required
|
|
|
|
|
);
|
|
|
|
|
public Validators = Validators;
|
|
|
|
|
|
|
|
|
|
private unsubscribeSubject = new Subject<void>();
|
|
|
|
|
|
|
|
|
@ -54,6 +54,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
private changeDetectorRef: ChangeDetectorRef,
|
|
|
|
|
private dataService: DataService,
|
|
|
|
|
public dialogRef: MatDialogRef<CreateOrUpdateTransactionDialog>,
|
|
|
|
|
private formBuilder: FormBuilder,
|
|
|
|
|
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTransactionDialogParams
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
@ -63,36 +64,105 @@ 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;
|
|
|
|
|
});
|
|
|
|
|
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 filteredLookupItemsObservable;
|
|
|
|
|
}
|
|
|
|
|
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.activityForm.controls['type'].valueChanges.subscribe((type: Type) => {
|
|
|
|
|
if (type === 'ITEM') {
|
|
|
|
|
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.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();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
this.activityForm.controls['type'].setValue(this.data.activity?.type);
|
|
|
|
|
|
|
|
|
|
if (this.data.transaction.id) {
|
|
|
|
|
this.searchSymbolCtrl.disable();
|
|
|
|
|
if (this.data.activity?.id) {
|
|
|
|
|
this.activityForm.controls['searchSymbol'].disable();
|
|
|
|
|
this.activityForm.controls['type'].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 +174,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public applyCurrentMarketPrice() {
|
|
|
|
|
this.data.transaction.unitPrice = this.currentMarketPrice;
|
|
|
|
|
this.activityForm.patchValue({
|
|
|
|
|
unitPrice: this.currentMarketPrice
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public displayFn(aLookupItem: LookupItem) {
|
|
|
|
@ -113,17 +185,20 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
|
|
|
|
|
public onBlurSymbol() {
|
|
|
|
|
const currentLookupItem = this.filteredLookupItems.find((lookupItem) => {
|
|
|
|
|
return lookupItem.symbol === this.data.transaction.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.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 +208,32 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
this.dialogRef.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public onSubmit() {
|
|
|
|
|
const activity: CreateOrderDto | UpdateOrderDto = {
|
|
|
|
|
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) {
|
|
|
|
|
(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.activityForm.controls['dataSource'].setValue(
|
|
|
|
|
event.option.value.dataSource
|
|
|
|
|
);
|
|
|
|
|
this.updateSymbol(event.option.value.symbol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -146,20 +245,21 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
private updateSymbol(symbol: string) {
|
|
|
|
|
this.isLoading = true;
|
|
|
|
|
|
|
|
|
|
this.searchSymbolCtrl.setErrors(null);
|
|
|
|
|
this.activityForm.controls['searchSymbol'].setErrors(null);
|
|
|
|
|
this.activityForm.controls['searchSymbol'].setValue({ symbol });
|
|
|
|
|
|
|
|
|
|
this.data.transaction.symbol = symbol;
|
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
|
|
|
|
|
|
|
|
this.dataService
|
|
|
|
|
.fetchSymbolItem({
|
|
|
|
|
dataSource: this.data.transaction.dataSource,
|
|
|
|
|
symbol: this.data.transaction.symbol
|
|
|
|
|
dataSource: this.activityForm.controls['dataSource'].value,
|
|
|
|
|
symbol: this.activityForm.controls['searchSymbol'].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 +270,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|
|
|
|
takeUntil(this.unsubscribeSubject)
|
|
|
|
|
)
|
|
|
|
|
.subscribe(({ currency, dataSource, marketPrice }) => {
|
|
|
|
|
this.data.transaction.currency = currency;
|
|
|
|
|
this.data.transaction.dataSource = dataSource;
|
|
|
|
|
this.activityForm.controls['currency'].setValue(currency);
|
|
|
|
|
this.activityForm.controls['dataSource'].setValue(dataSource);
|
|
|
|
|
|
|
|
|
|
this.currentMarketPrice = marketPrice;
|
|
|
|
|
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|