Feature/import transactions (#212)
* Implement import transactions functionality * Update changelog
This commit is contained in:
parent
be5b58f49a
commit
c7b7efae3b
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the import functionality for transactions
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Upgraded `angular-material-css-vars` from version `2.0.0` to `2.1.0`
|
- Upgraded `angular-material-css-vars` from version `2.0.0` to `2.1.0`
|
||||||
|
@ -24,6 +24,7 @@ import { AuthModule } from './auth/auth.module';
|
|||||||
import { CacheModule } from './cache/cache.module';
|
import { CacheModule } from './cache/cache.module';
|
||||||
import { ExperimentalModule } from './experimental/experimental.module';
|
import { ExperimentalModule } from './experimental/experimental.module';
|
||||||
import { ExportModule } from './export/export.module';
|
import { ExportModule } from './export/export.module';
|
||||||
|
import { ImportModule } from './import/import.module';
|
||||||
import { InfoModule } from './info/info.module';
|
import { InfoModule } from './info/info.module';
|
||||||
import { OrderModule } from './order/order.module';
|
import { OrderModule } from './order/order.module';
|
||||||
import { PortfolioModule } from './portfolio/portfolio.module';
|
import { PortfolioModule } from './portfolio/portfolio.module';
|
||||||
@ -43,6 +44,7 @@ import { UserModule } from './user/user.module';
|
|||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
ExperimentalModule,
|
ExperimentalModule,
|
||||||
ExportModule,
|
ExportModule,
|
||||||
|
ImportModule,
|
||||||
InfoModule,
|
InfoModule,
|
||||||
OrderModule,
|
OrderModule,
|
||||||
PortfolioModule,
|
PortfolioModule,
|
||||||
|
7
apps/api/src/app/import/import-data.dto.ts
Normal file
7
apps/api/src/app/import/import-data.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Order } from '@prisma/client';
|
||||||
|
import { IsArray } from 'class-validator';
|
||||||
|
|
||||||
|
export class ImportDataDto {
|
||||||
|
@IsArray()
|
||||||
|
orders: Partial<Order>[];
|
||||||
|
}
|
50
apps/api/src/app/import/import.controller.ts
Normal file
50
apps/api/src/app/import/import.controller.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
|
import { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
HttpException,
|
||||||
|
Inject,
|
||||||
|
Post,
|
||||||
|
UseGuards
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { REQUEST } from '@nestjs/core';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
|
import { ImportDataDto } from './import-data.dto';
|
||||||
|
import { ImportService } from './import.service';
|
||||||
|
|
||||||
|
@Controller('import')
|
||||||
|
export class ImportController {
|
||||||
|
public constructor(
|
||||||
|
private readonly configurationService: ConfigurationService,
|
||||||
|
private readonly importService: ImportService,
|
||||||
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
public async import(@Body() importData: ImportDataDto): Promise<void> {
|
||||||
|
if (!this.configurationService.get('ENABLE_FEATURE_IMPORT')) {
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
|
StatusCodes.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.importService.import({
|
||||||
|
orders: importData.orders,
|
||||||
|
userId: this.request.user.id
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.BAD_REQUEST),
|
||||||
|
StatusCodes.BAD_REQUEST
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
apps/api/src/app/import/import.module.ts
Normal file
34
apps/api/src/app/import/import.module.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||||
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||||
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||||
|
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||||
|
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||||
|
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||||
|
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ImportController } from './import.controller';
|
||||||
|
import { ImportService } from './import.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [RedisCacheModule],
|
||||||
|
controllers: [ImportController],
|
||||||
|
providers: [
|
||||||
|
AlphaVantageService,
|
||||||
|
CacheService,
|
||||||
|
ConfigurationService,
|
||||||
|
DataGatheringService,
|
||||||
|
DataProviderService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
|
ImportService,
|
||||||
|
OrderService,
|
||||||
|
PrismaService,
|
||||||
|
RakutenRapidApiService,
|
||||||
|
YahooFinanceService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ImportModule {}
|
43
apps/api/src/app/import/import.service.ts
Normal file
43
apps/api/src/app/import/import.service.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Order } from '@prisma/client';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ImportService {
|
||||||
|
public constructor(private readonly orderService: OrderService) {}
|
||||||
|
|
||||||
|
public async import({
|
||||||
|
orders,
|
||||||
|
userId
|
||||||
|
}: {
|
||||||
|
orders: Partial<Order>[];
|
||||||
|
userId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
for (const {
|
||||||
|
currency,
|
||||||
|
dataSource,
|
||||||
|
date,
|
||||||
|
fee,
|
||||||
|
quantity,
|
||||||
|
symbol,
|
||||||
|
type,
|
||||||
|
unitPrice
|
||||||
|
} of orders) {
|
||||||
|
await this.orderService.createOrder(
|
||||||
|
{
|
||||||
|
currency,
|
||||||
|
dataSource,
|
||||||
|
fee,
|
||||||
|
quantity,
|
||||||
|
symbol,
|
||||||
|
type,
|
||||||
|
unitPrice,
|
||||||
|
date: parseISO(<string>(<unknown>date)),
|
||||||
|
User: { connect: { id: userId } }
|
||||||
|
},
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,10 @@ export class InfoService {
|
|||||||
|
|
||||||
const globalPermissions: string[] = [];
|
const globalPermissions: string[] = [];
|
||||||
|
|
||||||
|
if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) {
|
||||||
|
globalPermissions.push(permissions.enableImport);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
|
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
|
||||||
globalPermissions.push(permissions.enableSocialLogin);
|
globalPermissions.push(permissions.enableSocialLogin);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
import { bool, cleanEnv, host, json, num, port, str } from 'envalid';
|
import { bool, cleanEnv, host, json, num, port, str } from 'envalid';
|
||||||
|
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
import { Environment } from './interfaces/environment.interface';
|
import { Environment } from './interfaces/environment.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -16,6 +17,7 @@ export class ConfigurationService {
|
|||||||
DATA_SOURCES: json({ default: JSON.stringify([DataSource.YAHOO]) }),
|
DATA_SOURCES: json({ default: JSON.stringify([DataSource.YAHOO]) }),
|
||||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
||||||
|
ENABLE_FEATURE_IMPORT: bool({ default: !environment.production }),
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
||||||
ENABLE_FEATURE_STATISTICS: bool({ default: false }),
|
ENABLE_FEATURE_STATISTICS: bool({ default: false }),
|
||||||
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
|
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
|
||||||
|
@ -7,6 +7,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
DATA_SOURCES: string | string[]; // string is not correct, error in envalid?
|
DATA_SOURCES: string | string[]; // string is not correct, error in envalid?
|
||||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
||||||
|
ENABLE_FEATURE_IMPORT: boolean;
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
||||||
ENABLE_FEATURE_STATISTICS: boolean;
|
ENABLE_FEATURE_STATISTICS: boolean;
|
||||||
ENABLE_FEATURE_SUBSCRIPTION: boolean;
|
ENABLE_FEATURE_SUBSCRIPTION: boolean;
|
||||||
|
@ -212,7 +212,23 @@
|
|||||||
<ion-icon name="ellipsis-vertical"></ion-icon>
|
<ion-icon name="ellipsis-vertical"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #transactionsMenu="matMenu" xPosition="before">
|
<mat-menu #transactionsMenu="matMenu" xPosition="before">
|
||||||
<button i18n mat-menu-item (click)="onExport()">Export</button>
|
<button
|
||||||
|
*ngIf="hasPermissionToImportOrders"
|
||||||
|
class="align-items-center d-flex"
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onImport()"
|
||||||
|
>
|
||||||
|
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon>
|
||||||
|
<span i18n>Import</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="align-items-center d-flex"
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onExport()"
|
||||||
|
>
|
||||||
|
<ion-icon class="mr-2" name="cloud-download-outline"></ion-icon>
|
||||||
|
<span i18n>Export</span>
|
||||||
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
||||||
|
@ -43,11 +43,13 @@ export class TransactionsTableComponent
|
|||||||
{
|
{
|
||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
|
@Input() hasPermissionToImportOrders: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@Input() showActions: boolean;
|
@Input() showActions: boolean;
|
||||||
@Input() transactions: OrderWithAccount[];
|
@Input() transactions: OrderWithAccount[];
|
||||||
|
|
||||||
@Output() export = new EventEmitter<void>();
|
@Output() export = new EventEmitter<void>();
|
||||||
|
@Output() import = new EventEmitter<void>();
|
||||||
@Output() transactionDeleted = new EventEmitter<string>();
|
@Output() transactionDeleted = new EventEmitter<string>();
|
||||||
@Output() transactionToClone = new EventEmitter<OrderWithAccount>();
|
@Output() transactionToClone = new EventEmitter<OrderWithAccount>();
|
||||||
@Output() transactionToUpdate = new EventEmitter<OrderWithAccount>();
|
@Output() transactionToUpdate = new EventEmitter<OrderWithAccount>();
|
||||||
@ -190,6 +192,10 @@ export class TransactionsTableComponent
|
|||||||
this.export.emit();
|
this.export.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onImport() {
|
||||||
|
this.import.emit();
|
||||||
|
}
|
||||||
|
|
||||||
public onOpenPositionDialog({
|
public onOpenPositionDialog({
|
||||||
symbol,
|
symbol,
|
||||||
title
|
title
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
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';
|
||||||
@ -9,10 +10,11 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Order as OrderModel } from '@prisma/client';
|
import { Order as OrderModel } from '@prisma/client';
|
||||||
|
import { environment } from 'apps/client/src/environments/environment';
|
||||||
import { format, parseISO } from 'date-fns';
|
import { format, parseISO } from 'date-fns';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { EMPTY, Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { catchError, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog/create-or-update-transaction-dialog.component';
|
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog/create-or-update-transaction-dialog.component';
|
||||||
|
|
||||||
@ -26,6 +28,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
public hasPermissionToCreateOrder: boolean;
|
||||||
public hasPermissionToDeleteOrder: boolean;
|
public hasPermissionToDeleteOrder: boolean;
|
||||||
|
public hasPermissionToImportOrders: boolean;
|
||||||
public routeQueryParams: Subscription;
|
public routeQueryParams: Subscription;
|
||||||
public transactions: OrderModel[];
|
public transactions: OrderModel[];
|
||||||
public user: User;
|
public user: User;
|
||||||
@ -43,6 +46,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.routeQueryParams = route.queryParams
|
this.routeQueryParams = route.queryParams
|
||||||
@ -68,6 +72,18 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
|
this.dataService
|
||||||
|
.fetchInfo()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ globalPermissions }) => {
|
||||||
|
this.hasPermissionToImportOrders = hasPermission(
|
||||||
|
globalPermissions,
|
||||||
|
permissions.enableImport
|
||||||
|
);
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
this.impersonationStorageService
|
this.impersonationStorageService
|
||||||
@ -145,6 +161,54 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onImport() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
|
||||||
|
input.onchange = (event) => {
|
||||||
|
// Getting the file reference
|
||||||
|
const file = (event.target as HTMLInputElement).files[0];
|
||||||
|
|
||||||
|
// Setting up the reader
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(file, 'UTF-8');
|
||||||
|
|
||||||
|
reader.onload = (readerEvent) => {
|
||||||
|
try {
|
||||||
|
const content = JSON.parse(readerEvent.target.result as string);
|
||||||
|
|
||||||
|
this.snackBar.open('⏳ Importing data...');
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.postImport({
|
||||||
|
orders: content.orders
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.handleImportError(error);
|
||||||
|
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
|
takeUntil(this.unsubscribeSubject)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.fetchOrders();
|
||||||
|
|
||||||
|
this.snackBar.open('✅ Import has been completed', undefined, {
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.handleImportError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
public onUpdateTransaction(aTransaction: OrderModel) {
|
public onUpdateTransaction(aTransaction: OrderModel) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { editDialog: true, transactionId: aTransaction.id }
|
queryParams: { editDialog: true, transactionId: aTransaction.id }
|
||||||
@ -223,6 +287,11 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleImportError(aError: unknown) {
|
||||||
|
console.error(aError);
|
||||||
|
this.snackBar.open('❌ Oops, something went wrong...');
|
||||||
|
}
|
||||||
|
|
||||||
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
||||||
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
||||||
data: {
|
data: {
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
<gf-transactions-table
|
<gf-transactions-table
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
|
[hasPermissionToImportOrders]="hasPermissionToImportOrders"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteOrder"
|
[showActions]="!hasImpersonationId && hasPermissionToDeleteOrder"
|
||||||
[transactions]="transactions"
|
[transactions]="transactions"
|
||||||
(export)="onExport()"
|
(export)="onExport()"
|
||||||
|
(import)="onImport()"
|
||||||
(transactionDeleted)="onDeleteTransaction($event)"
|
(transactionDeleted)="onDeleteTransaction($event)"
|
||||||
(transactionToClone)="onCloneTransaction($event)"
|
(transactionToClone)="onCloneTransaction($event)"
|
||||||
(transactionToUpdate)="onUpdateTransaction($event)"
|
(transactionToUpdate)="onUpdateTransaction($event)"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfTransactionsTableModule } from '@ghostfolio/client/components/transactions-table/transactions-table.module';
|
import { GfTransactionsTableModule } from '@ghostfolio/client/components/transactions-table/transactions-table.module';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import { TransactionsPageComponent } from './transactions-page.component';
|
|||||||
CreateOrUpdateTransactionDialogModule,
|
CreateOrUpdateTransactionDialogModule,
|
||||||
GfTransactionsTableModule,
|
GfTransactionsTableModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatSnackBarModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
TransactionsPageRoutingModule
|
TransactionsPageRoutingModule
|
||||||
],
|
],
|
||||||
|
@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
|
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
|
||||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||||
|
import { ImportDataDto } from '@ghostfolio/api/app/import/import-data.dto';
|
||||||
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 {
|
import {
|
||||||
@ -173,6 +174,10 @@ export class DataService {
|
|||||||
return this.http.post<OrderModel>(`/api/account`, aAccount);
|
return this.http.post<OrderModel>(`/api/account`, aAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public postImport(aImportData: ImportDataDto) {
|
||||||
|
return this.http.post<void>('/api/import', aImportData);
|
||||||
|
}
|
||||||
|
|
||||||
public postOrder(aOrder: CreateOrderDto) {
|
public postOrder(aOrder: CreateOrderDto) {
|
||||||
return this.http.post<OrderModel>(`/api/order`, aOrder);
|
return this.http.post<OrderModel>(`/api/order`, aOrder);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ export const permissions = {
|
|||||||
deleteAuthDevice: 'deleteAuthDevice',
|
deleteAuthDevice: 'deleteAuthDevice',
|
||||||
deleteOrder: 'deleteOrder',
|
deleteOrder: 'deleteOrder',
|
||||||
deleteUser: 'deleteUser',
|
deleteUser: 'deleteUser',
|
||||||
|
enableImport: 'enableImport',
|
||||||
enableSocialLogin: 'enableSocialLogin',
|
enableSocialLogin: 'enableSocialLogin',
|
||||||
enableStatistics: 'enableStatistics',
|
enableStatistics: 'enableStatistics',
|
||||||
enableSubscription: 'enableSubscription',
|
enableSubscription: 'enableSubscription',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user