Browse Source

Add error messages if import include duplicates

pull/1984/head
Thomas 2 years ago
parent
commit
95901368eb
  1. 19
      apps/api/src/app/import/import.service.ts
  2. 7
      apps/api/src/app/order/interfaces/activities.interface.ts
  3. 1
      apps/api/src/app/order/order.service.ts
  4. 4
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  5. 30
      libs/ui/src/lib/activities-table/activities-table.component.html
  6. 28
      libs/ui/src/lib/activities-table/activities-table.component.ts
  7. 2
      libs/ui/src/lib/activities-table/activities-table.module.ts
  8. 1
      libs/ui/src/lib/i18n.ts

19
apps/api/src/app/import/import.service.ts

@ -1,7 +1,10 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import {
Activity,
ActivityError
} from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
@ -80,11 +83,11 @@ export class ImportService {
comment: undefined, comment: undefined,
createdAt: undefined, createdAt: undefined,
date: parseDate(dateString), date: parseDate(dateString),
// TODO: Add evaluated error state
fee: 0, fee: 0,
feeInBaseCurrency: 0, feeInBaseCurrency: 0,
id: assetProfile.id, id: assetProfile.id,
isDraft: false, isDraft: false,
isDuplicate: false, // TODO: Use evaluated state
SymbolProfile: <SymbolProfile>(<unknown>assetProfile), SymbolProfile: <SymbolProfile>(<unknown>assetProfile),
symbolProfileId: assetProfile.id, symbolProfileId: assetProfile.id,
type: 'DIVIDEND', type: 'DIVIDEND',
@ -228,8 +231,8 @@ export class ImportService {
accountId, accountId,
comment, comment,
date, date,
error,
fee, fee,
isDuplicate,
quantity, quantity,
SymbolProfile: assetProfile, SymbolProfile: assetProfile,
type, type,
@ -283,7 +286,7 @@ export class ImportService {
updatedAt: new Date() updatedAt: new Date()
}; };
} else { } else {
if (isDuplicate) { if (error) {
continue; continue;
} }
@ -321,7 +324,7 @@ export class ImportService {
//@ts-ignore //@ts-ignore
activities.push({ activities.push({
...order, ...order,
isDuplicate, error,
value, value,
feeInBaseCurrency: this.exchangeRateDataService.toCurrency( feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee, fee,
@ -389,12 +392,16 @@ export class ImportService {
); );
}); });
const error: ActivityError = isDuplicate
? { code: 'IS_DUPLICATE' }
: undefined;
return { return {
accountId, accountId,
comment, comment,
date, date,
error,
fee, fee,
isDuplicate,
quantity, quantity,
type, type,
unitPrice, unitPrice,

7
apps/api/src/app/order/interfaces/activities.interface.ts

@ -5,9 +5,14 @@ export interface Activities {
} }
export interface Activity extends OrderWithAccount { export interface Activity extends OrderWithAccount {
error?: ActivityError;
feeInBaseCurrency: number; feeInBaseCurrency: number;
isDuplicate: boolean;
updateAccountBalance?: boolean; updateAccountBalance?: boolean;
value: number; value: number;
valueInBaseCurrency: number; valueInBaseCurrency: number;
} }
export interface ActivityError {
code: 'IS_DUPLICATE';
message?: string;
}

1
apps/api/src/app/order/order.service.ts

@ -333,7 +333,6 @@ export class OrderService {
order.SymbolProfile.currency, order.SymbolProfile.currency,
userCurrency userCurrency
), ),
isDuplicate: false,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency( valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
value, value,
order.SymbolProfile.currency, order.SymbolProfile.currency,

4
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -236,8 +236,8 @@ export class ImportActivitiesDialog implements OnDestroy {
} }
public updateSelection(activities: Activity[]) { public updateSelection(activities: Activity[]) {
this.selectedActivities = activities.filter(({ isDuplicate }) => { this.selectedActivities = activities.filter(({ error }) => {
return !isDuplicate; return !error;
}); });
} }

30
libs/ui/src/lib/activities-table/activities-table.component.html

@ -78,11 +78,9 @@
<mat-checkbox <mat-checkbox
color="primary" color="primary"
[checked]=" [checked]="
areAllRowsSelected() && areAllRowsSelected() && !hasErrors && selectedRows.hasValue()
!hasDuplicateActivity &&
selectedRows.hasValue()
" "
[disabled]="hasDuplicateActivity" [disabled]="hasErrors"
[indeterminate]="selectedRows.hasValue() && !areAllRowsSelected()" [indeterminate]="selectedRows.hasValue() && !areAllRowsSelected()"
(change)="$event ? toggleAllRows() : null" (change)="$event ? toggleAllRows() : null"
></mat-checkbox> ></mat-checkbox>
@ -90,10 +88,8 @@
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
<mat-checkbox <mat-checkbox
color="primary" color="primary"
[checked]=" [checked]="element.error ? false : selectedRows.isSelected(element)"
element.isDuplicate ? false : selectedRows.isSelected(element) [disabled]="element.error"
"
[disabled]="element.isDuplicate"
(change)="$event ? selectedRows.toggle(element) : null" (change)="$event ? selectedRows.toggle(element) : null"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
></mat-checkbox> ></mat-checkbox>
@ -101,6 +97,23 @@
<td *matFooterCellDef class="px-1" mat-footer-cell></td> <td *matFooterCellDef class="px-1" mat-footer-cell></td>
</ng-container> </ng-container>
<ng-container matColumnDef="importStatus">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n></ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<div
*ngIf="element.error"
class="d-flex"
matTooltipPosition="above"
[matTooltip]="element.error.message"
>
<ion-icon class="text-danger" name="alert-circle-outline"></ion-icon>
</div>
</td>
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
</ng-container>
<ng-container matColumnDef="count"> <ng-container matColumnDef="count">
<th <th
*matHeaderCellDef *matHeaderCellDef
@ -125,6 +138,7 @@
mat-footer-cell mat-footer-cell
></td> ></td>
</ng-container> </ng-container>
<ng-container matColumnDef="date"> <ng-container matColumnDef="date">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Date</ng-container> <ng-container i18n>Date</ng-container>

28
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -19,6 +19,7 @@ import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types'; import { OrderWithAccount } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import Big from 'big.js'; import Big from 'big.js';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { endOfToday, format, isAfter } from 'date-fns'; import { endOfToday, format, isAfter } from 'date-fns';
@ -66,7 +67,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
public endOfToday = endOfToday(); public endOfToday = endOfToday();
public filters$ = new Subject<Filter[]>(); public filters$ = new Subject<Filter[]>();
public hasDrafts = false; public hasDrafts = false;
public hasDuplicateActivity = false; public hasErrors = false;
public isAfter = isAfter; public isAfter = isAfter;
public isLoading = true; public isLoading = true;
public isUUID = isUUID; public isUUID = isUUID;
@ -109,6 +110,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
public ngOnChanges() { public ngOnChanges() {
this.displayedColumns = [ this.displayedColumns = [
'select', 'select',
'importStatus',
'count', 'count',
'date', 'date',
'type', 'type',
@ -130,7 +132,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
}); });
} else { } else {
this.displayedColumns = this.displayedColumns.filter((column) => { this.displayedColumns = this.displayedColumns.filter((column) => {
return column !== 'select'; return column !== 'importStatus' && column !== 'select';
}); });
} }
@ -143,6 +145,20 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
this.defaultDateFormat = getDateFormatString(this.locale); this.defaultDateFormat = getDateFormatString(this.locale);
if (this.activities) { if (this.activities) {
this.activities = this.activities.map((activity) => {
return {
...activity,
error: activity.error
? {
...activity.error,
message: translate(
`IMPORT_ACTIVITY_ERROR_${activity.error.code}`
)
}
: undefined
};
});
this.allFilters = this.getSearchableFieldValues(this.activities); this.allFilters = this.getSearchableFieldValues(this.activities);
this.dataSource = new MatTableDataSource(this.activities); this.dataSource = new MatTableDataSource(this.activities);
@ -167,11 +183,11 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
this.updateFilters(); this.updateFilters();
this.hasDuplicateActivity = this.activities.some(({ isDuplicate }) => { this.hasErrors = this.activities.some(({ error }) => {
return isDuplicate; return !!error;
}); });
} else { } else {
this.hasDuplicateActivity = false; this.hasErrors = false;
} }
} }
@ -184,7 +200,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
public onClickActivity(activity: Activity) { public onClickActivity(activity: Activity) {
if (this.showCheckbox) { if (this.showCheckbox) {
if (!activity.isDuplicate) { if (!activity.error) {
this.selectedRows.toggle(activity); this.selectedRows.toggle(activity);
} }
} else if ( } else if (

2
libs/ui/src/lib/activities-table/activities-table.module.ts

@ -6,6 +6,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module'; import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
@ -32,6 +33,7 @@ import { ActivitiesTableComponent } from './activities-table.component';
MatPaginatorModule, MatPaginatorModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
MatTooltipModule,
NgxSkeletonLoaderModule, NgxSkeletonLoaderModule,
RouterModule RouterModule
], ],

1
libs/ui/src/lib/i18n.ts

@ -11,6 +11,7 @@ const locales = {
EMERGENCY_FUND: $localize`Emergency Fund`, EMERGENCY_FUND: $localize`Emergency Fund`,
GRANT: $localize`Grant`, GRANT: $localize`Grant`,
HIGHER_RISK: $localize`Higher Risk`, HIGHER_RISK: $localize`Higher Risk`,
IMPORT_ACTIVITY_ERROR_IS_DUPLICATE: $localize`This activity already exists.`,
LOWER_RISK: $localize`Lower Risk`, LOWER_RISK: $localize`Lower Risk`,
OTHER: $localize`Other`, OTHER: $localize`Other`,
RETIREMENT_PROVISION: $localize`Retirement Provision`, RETIREMENT_PROVISION: $localize`Retirement Provision`,

Loading…
Cancel
Save