Browse Source

Improve validation

pull/1769/head
Thomas 3 years ago
parent
commit
31ff6c3971
  1. 20
      apps/api/src/app/exchange-rate/exchange-rate.controller.ts
  2. 12
      apps/api/src/services/exchange-rate-data.service.ts
  3. 18
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  4. 36
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

20
apps/api/src/app/exchange-rate/exchange-rate.controller.ts

@ -1,6 +1,13 @@
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import {
Controller,
Get,
HttpException,
Param,
UseGuards
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { ExchangeRateService } from './exchange-rate.service'; import { ExchangeRateService } from './exchange-rate.service';
@ -18,9 +25,18 @@ export class ExchangeRateController {
): Promise<IDataProviderHistoricalResponse> { ): Promise<IDataProviderHistoricalResponse> {
const date = new Date(dateString); const date = new Date(dateString);
return this.exchangeRateService.getExchangeRate({ const { marketPrice } = await this.exchangeRateService.getExchangeRate({
date, date,
symbol symbol
}); });
if (marketPrice) {
return { marketPrice };
}
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
} }
} }

12
apps/api/src/services/exchange-rate-data.service.ts

@ -168,7 +168,7 @@ export class ExchangeRateDataService {
return this.toCurrency(aValue, aFromCurrency, aToCurrency); return this.toCurrency(aValue, aFromCurrency, aToCurrency);
} }
let factor = 1; let factor: number;
if (aFromCurrency !== aToCurrency) { if (aFromCurrency !== aToCurrency) {
const dataSource = this.dataProviderService.getPrimaryDataSource(); const dataSource = this.dataProviderService.getPrimaryDataSource();
@ -185,7 +185,6 @@ export class ExchangeRateDataService {
} else { } else {
// TODO: Get from data provider service or calculate indirectly via base currency // TODO: Get from data provider service or calculate indirectly via base currency
// and market data // and market data
return this.toCurrency(aValue, aFromCurrency, aToCurrency);
} }
} }
@ -193,12 +192,15 @@ export class ExchangeRateDataService {
return factor * aValue; return factor * aValue;
} }
// Fallback with error, if currencies are not available
Logger.error( Logger.error(
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`, `No exchange rate has been found for ${aFromCurrency}${aToCurrency} at ${format(
aDate,
DATE_FORMAT
)}`,
'ExchangeRateDataService' 'ExchangeRateDataService'
); );
return aValue;
return undefined;
} }
private async prepareCurrencies(): Promise<string[]> { private async prepareCurrencies(): Promise<string[]> {

18
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -15,6 +15,7 @@ import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog'; } from '@angular/material/legacy-dialog';
import { getDateFormatString } from '@ghostfolio/common/helper';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
@ -56,6 +57,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
}); });
public currencies: string[] = []; public currencies: string[] = [];
public currentMarketPrice = null; public currentMarketPrice = null;
public defaultDateFormat: string;
public filteredLookupItems: LookupItem[]; public filteredLookupItems: LookupItem[];
public filteredLookupItemsObservable: Observable<LookupItem[]>; public filteredLookupItemsObservable: Observable<LookupItem[]>;
public filteredTagsObservable: Observable<Tag[]>; public filteredTagsObservable: Observable<Tag[]>;
@ -85,6 +87,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
const { currencies, platforms, tags } = this.dataService.fetchInfo(); const { currencies, platforms, tags } = this.dataService.fetchInfo();
this.currencies = currencies; this.currencies = currencies;
this.defaultDateFormat = getDateFormatString(this.locale);
this.platforms = platforms; this.platforms = platforms;
this.tags = tags.map(({ id, name }) => { this.tags = tags.map(({ id, name }) => {
return { return {
@ -148,6 +151,9 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
let exchangeRateOfFee = 1; let exchangeRateOfFee = 1;
let exchangeRateOfUnitPrice = 1; let exchangeRateOfUnitPrice = 1;
this.activityForm.controls['feeInCustomCurrency'].setErrors(null);
this.activityForm.controls['unitPriceInCustomCurrency'].setErrors(null);
const currency = this.activityForm.controls['currency'].value; const currency = this.activityForm.controls['currency'].value;
const currencyOfFee = this.activityForm.controls['currencyOfFee'].value; const currencyOfFee = this.activityForm.controls['currencyOfFee'].value;
const currencyOfUnitPrice = const currencyOfUnitPrice =
@ -166,7 +172,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
); );
exchangeRateOfFee = marketPrice; exchangeRateOfFee = marketPrice;
} catch {} } catch {
this.activityForm.controls['feeInCustomCurrency'].setErrors({
invalid: true
});
}
} }
const feeInCustomCurrency = const feeInCustomCurrency =
@ -194,7 +204,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
); );
exchangeRateOfUnitPrice = marketPrice; exchangeRateOfUnitPrice = marketPrice;
} catch {} } catch {
this.activityForm.controls['unitPriceInCustomCurrency'].setErrors({
invalid: true
});
}
} }
const unitPriceInCustomCurrency = const unitPriceInCustomCurrency =

36
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -7,7 +7,7 @@
<h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1> <h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1>
<h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1> <h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1>
<div class="flex-grow-1 pt-3" mat-dialog-content> <div class="flex-grow-1 pt-3" mat-dialog-content>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Type</mat-label> <mat-label i18n>Type</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
@ -18,7 +18,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Account</mat-label> <mat-label i18n>Account</mat-label>
<mat-select formControlName="accountId"> <mat-select formControlName="accountId">
@ -33,6 +33,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div <div
class="mb-3"
[ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }" [ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }"
> >
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
@ -69,6 +70,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div <div
class="mb-3"
[ngClass]="{ 'd-none': !activityForm.controls['name'].hasValidator(Validators.required) }" [ngClass]="{ 'd-none': !activityForm.controls['name'].hasValidator(Validators.required) }"
> >
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
@ -92,7 +94,7 @@
<input formControlName="dataSource" matInput /> <input formControlName="dataSource" matInput />
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Date</mat-label> <mat-label i18n>Date</mat-label>
<input formControlName="date" matInput [matDatepicker]="date" /> <input formControlName="date" matInput [matDatepicker]="date" />
@ -106,13 +108,13 @@
<mat-datepicker #date disabled="false"></mat-datepicker> <mat-datepicker #date disabled="false"></mat-datepicker>
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Quantity</mat-label> <mat-label i18n>Quantity</mat-label>
<input formControlName="quantity" matInput type="number" /> <input formControlName="quantity" matInput type="number" />
</mat-form-field> </mat-form-field>
</div> </div>
<div class="align-items-start d-flex"> <div class="align-items-start d-flex mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label <mat-label
><ng-container [ngSwitch]="activityForm.controls['type']?.value"> ><ng-container [ngSwitch]="activityForm.controls['type']?.value">
@ -139,6 +141,14 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
<mat-error
*ngIf="activityForm.controls['unitPriceInCustomCurrency'].hasError('invalid')"
><ng-container i18n
>Oops! Could not get the historical exchange rate from</ng-container
>
{{ activityForm.controls['date']?.value | date: defaultDateFormat
}}</mat-error
>
</mat-form-field> </mat-form-field>
<button <button
*ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')" *ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')"
@ -168,7 +178,7 @@
> >
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Fee</mat-label> <mat-label i18n>Fee</mat-label>
<input formControlName="feeInCustomCurrency" matInput type="number" /> <input formControlName="feeInCustomCurrency" matInput type="number" />
@ -183,6 +193,14 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
<mat-error
*ngIf="activityForm.controls['feeInCustomCurrency'].hasError('invalid')"
><ng-container i18n
>Oops! Could not get the historical exchange rate from</ng-container
>
{{ activityForm.controls['date']?.value | date: defaultDateFormat
}}</mat-error
>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="d-none"> <div class="d-none">
@ -194,7 +212,7 @@
> >
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Note</mat-label> <mat-label i18n>Note</mat-label>
<textarea <textarea
@ -207,6 +225,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div <div
class="mb-3"
[ngClass]="{ 'd-none': activityForm.controls['type']?.value !== 'ITEM' }" [ngClass]="{ 'd-none': activityForm.controls['type']?.value !== 'ITEM' }"
> >
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
@ -222,6 +241,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div <div
class="mb-3"
[ngClass]="{ 'd-none': activityForm.controls['type']?.value !== 'ITEM' }" [ngClass]="{ 'd-none': activityForm.controls['type']?.value !== 'ITEM' }"
> >
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
@ -236,7 +256,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div [ngClass]="{ 'd-none': tags?.length <= 0 }"> <div class="mb-3" [ngClass]="{ 'd-none': tags?.length <= 0 }">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Tags</mat-label> <mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList> <mat-chip-grid #tagsChipList>

Loading…
Cancel
Save