Browse Source

Add support for wealth items

pull/666/head
Thomas 3 years ago
parent
commit
df5b8f91e9
  1. 11
      apps/api/src/app/import/import.service.ts
  2. 11
      apps/api/src/services/data-gathering.service.ts
  3. 5
      apps/api/src/services/data-provider/data-provider.module.ts
  4. 1
      apps/api/src/services/data-provider/data-provider.service.ts
  5. 43
      apps/api/src/services/data-provider/manual/manual.service.ts
  6. 1
      apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html
  7. 2
      apps/client/src/app/services/import-transactions.service.ts
  8. 24
      libs/ui/src/lib/activities-table/activities-table.component.html
  9. 4
      libs/ui/src/lib/activities-table/activities-table.component.scss
  10. 2
      libs/ui/src/lib/activities-table/activities-table.component.ts
  11. 2
      prisma/schema.prisma
  12. 1
      test/import/ok.csv

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

@ -21,8 +21,13 @@ export class ImportService {
userId: string; userId: string;
}): Promise<void> { }): Promise<void> {
for (const order of orders) { for (const order of orders) {
order.dataSource = if (!order.dataSource) {
order.dataSource ?? this.dataProviderService.getPrimaryDataSource(); if (order.type === 'ITEM') {
order.dataSource = 'MANUAL';
} else {
order.dataSource = this.dataProviderService.getPrimaryDataSource();
}
}
} }
await this.validateOrders({ orders, userId }); await this.validateOrders({ orders, userId });
@ -111,6 +116,7 @@ export class ImportService {
throw new Error(`orders.${index} is a duplicate transaction`); throw new Error(`orders.${index} is a duplicate transaction`);
} }
if (dataSource !== 'MANUAL') {
const result = await this.dataProviderService.get([ const result = await this.dataProviderService.get([
{ dataSource, symbol } { dataSource, symbol }
]); ]);
@ -128,4 +134,5 @@ export class ImportService {
} }
} }
} }
}
} }

11
apps/api/src/services/data-gathering.service.ts

@ -445,6 +445,11 @@ export class DataGatheringService {
}, },
scraperConfiguration: true, scraperConfiguration: true,
symbol: true symbol: true
},
where: {
dataSource: {
not: 'MANUAL'
}
} }
}) })
).map((symbolProfile) => { ).map((symbolProfile) => {
@ -479,6 +484,11 @@ export class DataGatheringService {
dataSource: true, dataSource: true,
scraperConfiguration: true, scraperConfiguration: true,
symbol: true symbol: true
},
where: {
dataSource: {
not: 'MANUAL'
}
} }
}); });
@ -537,6 +547,7 @@ export class DataGatheringService {
return distinctOrders.filter((distinctOrder) => { return distinctOrders.filter((distinctOrder) => {
return ( return (
distinctOrder.dataSource !== DataSource.GHOSTFOLIO && distinctOrder.dataSource !== DataSource.GHOSTFOLIO &&
distinctOrder.dataSource !== DataSource.MANUAL &&
distinctOrder.dataSource !== DataSource.RAKUTEN distinctOrder.dataSource !== DataSource.RAKUTEN
); );
}); });

5
apps/api/src/services/data-provider/data-provider.module.ts

@ -2,6 +2,7 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration.modu
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module'; import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service'; import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-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 { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
@ -23,6 +24,7 @@ import { DataProviderService } from './data-provider.service';
DataProviderService, DataProviderService,
GhostfolioScraperApiService, GhostfolioScraperApiService,
GoogleSheetsService, GoogleSheetsService,
ManualService,
RakutenRapidApiService, RakutenRapidApiService,
YahooFinanceService, YahooFinanceService,
{ {
@ -30,6 +32,7 @@ import { DataProviderService } from './data-provider.service';
AlphaVantageService, AlphaVantageService,
GhostfolioScraperApiService, GhostfolioScraperApiService,
GoogleSheetsService, GoogleSheetsService,
ManualService,
RakutenRapidApiService, RakutenRapidApiService,
YahooFinanceService YahooFinanceService
], ],
@ -38,12 +41,14 @@ import { DataProviderService } from './data-provider.service';
alphaVantageService, alphaVantageService,
ghostfolioScraperApiService, ghostfolioScraperApiService,
googleSheetsService, googleSheetsService,
manualService,
rakutenRapidApiService, rakutenRapidApiService,
yahooFinanceService yahooFinanceService
) => [ ) => [
alphaVantageService, alphaVantageService,
ghostfolioScraperApiService, ghostfolioScraperApiService,
googleSheetsService, googleSheetsService,
manualService,
rakutenRapidApiService, rakutenRapidApiService,
yahooFinanceService yahooFinanceService
] ]

1
apps/api/src/services/data-provider/data-provider.service.ts

@ -194,6 +194,7 @@ export class DataProviderService {
return dataProviderInterface; return dataProviderInterface;
} }
} }
throw new Error('No data provider has been found.'); throw new Error('No data provider has been found.');
} }
} }

43
apps/api/src/services/data-provider/manual/manual.service.ts

@ -0,0 +1,43 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import {
IDataProviderHistoricalResponse,
IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces';
import { Granularity } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
@Injectable()
export class ManualService implements DataProviderInterface {
public constructor() {}
public canHandle(symbol: string) {
return false;
}
public async get(
aSymbols: string[]
): Promise<{ [symbol: string]: IDataProviderResponse }> {
return {};
}
public async getHistorical(
aSymbols: string[],
aGranularity: Granularity = 'day',
from: Date,
to: Date
): Promise<{
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
return {};
}
public getName(): DataSource {
return DataSource.MANUAL;
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
return { items: [] };
}
}

1
apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html

@ -54,6 +54,7 @@
<mat-select name="type" required [(value)]="data.transaction.type"> <mat-select name="type" required [(value)]="data.transaction.type">
<mat-option value="BUY" i18n>BUY</mat-option> <mat-option value="BUY" i18n>BUY</mat-option>
<mat-option value="DIVIDEND" i18n>DIVIDEND</mat-option> <mat-option value="DIVIDEND" i18n>DIVIDEND</mat-option>
<mat-option value="ITEM" i18n>ITEM</mat-option>
<mat-option value="SELL" i18n>SELL</mat-option> <mat-option value="SELL" i18n>SELL</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

2
apps/client/src/app/services/import-transactions.service.ts

@ -245,6 +245,8 @@ export class ImportTransactionsService {
return Type.BUY; return Type.BUY;
case 'dividend': case 'dividend':
return Type.DIVIDEND; return Type.DIVIDEND;
case 'item':
return Type.ITEM;
case 'sell': case 'sell':
return Type.SELL; return Type.SELL;
default: default:

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

@ -87,15 +87,21 @@
[ngClass]="{ [ngClass]="{
buy: element.type === 'BUY', buy: element.type === 'BUY',
dividend: element.type === 'DIVIDEND', dividend: element.type === 'DIVIDEND',
item: element.type === 'ITEM',
sell: element.type === 'SELL' sell: element.type === 'SELL'
}" }"
> >
<ion-icon <ion-icon
[name]=" *ngIf="element.type === 'BUY' || element.type === 'DIVIDEND'"
element.type === 'BUY' || element.type === 'DIVIDEND' name="arrow-forward-circle-outline"
? 'arrow-forward-circle-outline' ></ion-icon>
: 'arrow-back-circle-outline' <ion-icon
" *ngIf="element.type === 'ITEM'"
name="cube-outline"
></ion-icon>
<ion-icon
*ngIf="element.type === 'SELL'"
name="arrow-back-circle-outline"
></ion-icon> ></ion-icon>
<span class="d-none d-lg-block mx-1">{{ element.type }}</span> <span class="d-none d-lg-block mx-1">{{ element.type }}</span>
</div> </div>
@ -109,7 +115,7 @@
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
{{ element.symbol | gfSymbol }} {{ element.SymbolProfile.symbol | gfSymbol }}
<span *ngIf="element.isDraft" class="badge badge-secondary ml-1" i18n <span *ngIf="element.isDraft" class="badge badge-secondary ml-1" i18n
>Draft</span >Draft</span
> >
@ -349,13 +355,15 @@
(click)=" (click)="
hasPermissionToOpenDetails && hasPermissionToOpenDetails &&
!row.isDraft && !row.isDraft &&
row.type !== 'ITEM' &&
onOpenPositionDialog({ onOpenPositionDialog({
dataSource: row.dataSource, dataSource: row.dataSource,
symbol: row.symbol symbol: row.SymbolProfile.symbol
}) })
" "
[ngClass]="{ [ngClass]="{
'cursor-pointer': hasPermissionToOpenDetails && !row.isDraft 'cursor-pointer':
hasPermissionToOpenDetails && !row.isDraft && row.type !== 'ITEM'
}" }"
></tr> ></tr>
<tr <tr

4
libs/ui/src/lib/activities-table/activities-table.component.scss

@ -54,6 +54,10 @@
color: var(--blue); color: var(--blue);
} }
&.item {
color: var(--purple);
}
&.sell { &.sell {
color: var(--orange); color: var(--orange);
} }

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

@ -302,7 +302,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
for (const activity of this.dataSource.filteredData) { for (const activity of this.dataSource.filteredData) {
if (isNumber(activity.valueInBaseCurrency)) { if (isNumber(activity.valueInBaseCurrency)) {
if (activity.type === 'BUY') { if (activity.type === 'BUY' || activity.type === 'ITEM') {
totalValue = totalValue.plus(activity.valueInBaseCurrency); totalValue = totalValue.plus(activity.valueInBaseCurrency);
} else if (activity.type === 'SELL') { } else if (activity.type === 'SELL') {
totalValue = totalValue.minus(activity.valueInBaseCurrency); totalValue = totalValue.minus(activity.valueInBaseCurrency);

2
prisma/schema.prisma

@ -185,6 +185,7 @@ enum DataSource {
ALPHA_VANTAGE ALPHA_VANTAGE
GHOSTFOLIO GHOSTFOLIO
GOOGLE_SHEETS GOOGLE_SHEETS
MANUAL
RAKUTEN RAKUTEN
YAHOO YAHOO
} }
@ -208,5 +209,6 @@ enum Role {
enum Type { enum Type {
BUY BUY
DIVIDEND DIVIDEND
ITEM
SELL SELL
} }

1
test/import/ok.csv

@ -1,3 +1,4 @@
Date,Code,Currency,Price,Quantity,Action,Fee Date,Code,Currency,Price,Quantity,Action,Fee
17/11/2021,MSFT,USD,0.62,5,dividend,0.00 17/11/2021,MSFT,USD,0.62,5,dividend,0.00
16/09/2021,MSFT,USD,298.580,5,buy,19.00 16/09/2021,MSFT,USD,298.580,5,buy,19.00
01/01/2022,Penthouse Apartment,USD,500000.0,1,item,0.00

1 Date Code Currency Price Quantity Action Fee
2 17/11/2021 MSFT USD 0.62 5 dividend 0.00
3 16/09/2021 MSFT USD 298.580 5 buy 19.00
4 01/01/2022 Penthouse Apartment USD 500000.0 1 item 0.00
Loading…
Cancel
Save