Browse Source

Add support for wealth items

pull/666/head
Thomas 3 years ago
parent
commit
df5b8f91e9
  1. 35
      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

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

@ -21,8 +21,13 @@ export class ImportService {
userId: string;
}): Promise<void> {
for (const order of orders) {
order.dataSource =
order.dataSource ?? this.dataProviderService.getPrimaryDataSource();
if (!order.dataSource) {
if (order.type === 'ITEM') {
order.dataSource = 'MANUAL';
} else {
order.dataSource = this.dataProviderService.getPrimaryDataSource();
}
}
}
await this.validateOrders({ orders, userId });
@ -111,20 +116,22 @@ export class ImportService {
throw new Error(`orders.${index} is a duplicate transaction`);
}
const result = await this.dataProviderService.get([
{ dataSource, symbol }
]);
if (dataSource !== 'MANUAL') {
const result = await this.dataProviderService.get([
{ dataSource, symbol }
]);
if (result[symbol] === undefined) {
throw new Error(
`orders.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
);
}
if (result[symbol] === undefined) {
throw new Error(
`orders.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
);
}
if (result[symbol].currency !== currency) {
throw new Error(
`orders.${index}.currency ("${currency}") does not match with "${result[symbol].currency}"`
);
if (result[symbol].currency !== currency) {
throw new Error(
`orders.${index}.currency ("${currency}") does not match with "${result[symbol].currency}"`
);
}
}
}
}

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

@ -445,6 +445,11 @@ export class DataGatheringService {
},
scraperConfiguration: true,
symbol: true
},
where: {
dataSource: {
not: 'MANUAL'
}
}
})
).map((symbolProfile) => {
@ -479,6 +484,11 @@ export class DataGatheringService {
dataSource: true,
scraperConfiguration: true,
symbol: true
},
where: {
dataSource: {
not: 'MANUAL'
}
}
});
@ -537,6 +547,7 @@ export class DataGatheringService {
return distinctOrders.filter((distinctOrder) => {
return (
distinctOrder.dataSource !== DataSource.GHOSTFOLIO &&
distinctOrder.dataSource !== DataSource.MANUAL &&
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 { 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 { 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 { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
@ -23,6 +24,7 @@ import { DataProviderService } from './data-provider.service';
DataProviderService,
GhostfolioScraperApiService,
GoogleSheetsService,
ManualService,
RakutenRapidApiService,
YahooFinanceService,
{
@ -30,6 +32,7 @@ import { DataProviderService } from './data-provider.service';
AlphaVantageService,
GhostfolioScraperApiService,
GoogleSheetsService,
ManualService,
RakutenRapidApiService,
YahooFinanceService
],
@ -38,12 +41,14 @@ import { DataProviderService } from './data-provider.service';
alphaVantageService,
ghostfolioScraperApiService,
googleSheetsService,
manualService,
rakutenRapidApiService,
yahooFinanceService
) => [
alphaVantageService,
ghostfolioScraperApiService,
googleSheetsService,
manualService,
rakutenRapidApiService,
yahooFinanceService
]

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

@ -194,6 +194,7 @@ export class DataProviderService {
return dataProviderInterface;
}
}
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-option value="BUY" i18n>BUY</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-select>
</mat-form-field>

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

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

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

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

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

@ -54,6 +54,10 @@
color: var(--blue);
}
&.item {
color: var(--purple);
}
&.sell {
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) {
if (isNumber(activity.valueInBaseCurrency)) {
if (activity.type === 'BUY') {
if (activity.type === 'BUY' || activity.type === 'ITEM') {
totalValue = totalValue.plus(activity.valueInBaseCurrency);
} else if (activity.type === 'SELL') {
totalValue = totalValue.minus(activity.valueInBaseCurrency);

2
prisma/schema.prisma

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

1
test/import/ok.csv

@ -1,3 +1,4 @@
Date,Code,Currency,Price,Quantity,Action,Fee
17/11/2021,MSFT,USD,0.62,5,dividend,0.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