Browse Source

Integrate (wealth) items into transaction point concept

pull/3084/head
Thomas Kaul 2 years ago
parent
commit
e459cf5289
  1. 2
      apps/api/src/app/import/import.controller.ts
  2. 1
      apps/api/src/app/portfolio/portfolio-calculator.ts
  3. 7
      apps/api/src/app/portfolio/portfolio.controller.ts
  4. 40
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 2
      apps/api/src/app/symbol/symbol.controller.ts
  6. 28
      apps/api/src/services/data-provider/manual/manual.service.ts
  7. 3
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  8. 8
      apps/client/src/app/services/data.service.ts
  9. 2
      libs/common/src/lib/interfaces/portfolio-public-details.interface.ts

2
apps/api/src/app/import/import.controller.ts

@ -43,7 +43,7 @@ export class ImportController {
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async import( public async import(
@Body() importData: ImportDataDto, @Body() importData: ImportDataDto,
@Query('dryRun') isDryRun?: boolean @Query('dryRun') isDryRun = false
): Promise<ImportResponse> { ): Promise<ImportResponse> {
if ( if (
!hasPermission(this.request.user.permissions, permissions.createAccount) !hasPermission(this.request.user.permissions, permissions.createAccount)

1
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -825,6 +825,7 @@ export class PortfolioCalculator {
switch (type) { switch (type) {
case 'BUY': case 'BUY':
case 'ITEM':
factor = 1; factor = 1;
break; break;
case 'SELL': case 'SELL':

7
apps/api/src/app/portfolio/portfolio.controller.ts

@ -342,7 +342,8 @@ export class PortfolioController {
@Query('assetClasses') filterByAssetClasses?: string, @Query('assetClasses') filterByAssetClasses?: string,
@Query('range') dateRange: DateRange = 'max', @Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string, @Query('tags') filterByTags?: string,
@Query('withExcludedAccounts') withExcludedAccounts = false @Query('withExcludedAccounts') withExcludedAccounts = false,
@Query('withItems') withItems = false
): Promise<PortfolioPerformanceResponse> { ): Promise<PortfolioPerformanceResponse> {
const hasReadRestrictedAccessPermission = const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({ this.userService.hasReadRestrictedAccessPermission({
@ -361,6 +362,7 @@ export class PortfolioController {
filters, filters,
impersonationId, impersonationId,
withExcludedAccounts, withExcludedAccounts,
withItems,
userId: this.request.user.id userId: this.request.user.id
}); });
@ -515,7 +517,8 @@ export class PortfolioController {
dateOfFirstActivity: portfolioPosition.dateOfFirstActivity, dateOfFirstActivity: portfolioPosition.dateOfFirstActivity,
markets: hasDetails ? portfolioPosition.markets : undefined, markets: hasDetails ? portfolioPosition.markets : undefined,
name: portfolioPosition.name, name: portfolioPosition.name,
netPerformancePercent: portfolioPosition.netPerformancePercent, netPerformancePercentWithCurrencyEffect:
portfolioPosition.netPerformancePercentWithCurrencyEffect,
sectors: hasDetails ? portfolioPosition.sectors : [], sectors: hasDetails ? portfolioPosition.sectors : [],
symbol: portfolioPosition.symbol, symbol: portfolioPosition.symbol,
url: portfolioPosition.url, url: portfolioPosition.url,

40
apps/api/src/app/portfolio/portfolio.service.ts

@ -277,7 +277,8 @@ export class PortfolioService {
await this.getTransactionPoints({ await this.getTransactionPoints({
filters, filters,
userId, userId,
includeDrafts: true includeDrafts: true,
types: ['BUY', 'SELL']
}); });
if (transactionPoints.length === 0) { if (transactionPoints.length === 0) {
@ -702,7 +703,7 @@ export class PortfolioService {
.filter((order) => { .filter((order) => {
tags = tags.concat(order.tags); tags = tags.concat(order.tags);
return order.type === 'BUY' || order.type === 'SELL'; return ['BUY', 'ITEM', 'SELL'].includes(order.type);
}) })
.map((order) => ({ .map((order) => ({
currency: order.SymbolProfile.currency, currency: order.SymbolProfile.currency,
@ -957,7 +958,8 @@ export class PortfolioService {
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
filters, filters,
userId userId,
types: ['BUY', 'SELL']
}); });
if (transactionPoints?.length <= 0) { if (transactionPoints?.length <= 0) {
@ -1087,13 +1089,15 @@ export class PortfolioService {
filters, filters,
impersonationId, impersonationId,
userId, userId,
withExcludedAccounts = false withExcludedAccounts = false,
withItems = false
}: { }: {
dateRange?: DateRange; dateRange?: DateRange;
filters?: Filter[]; filters?: Filter[];
impersonationId: string; impersonationId: string;
userId: string; userId: string;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
withItems?: boolean;
}): Promise<PortfolioPerformanceResponse> { }): Promise<PortfolioPerformanceResponse> {
userId = await this.getUserId(impersonationId, userId); userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
@ -1128,7 +1132,8 @@ export class PortfolioService {
await this.getTransactionPoints({ await this.getTransactionPoints({
filters, filters,
userId, userId,
withExcludedAccounts withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
@ -1280,7 +1285,8 @@ export class PortfolioService {
const { orders, portfolioOrders, transactionPoints } = const { orders, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
userId userId,
types: ['BUY', 'SELL']
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
@ -1913,11 +1919,13 @@ export class PortfolioService {
private async getTransactionPoints({ private async getTransactionPoints({
filters, filters,
includeDrafts = false, includeDrafts = false,
types = ['BUY', 'ITEM', 'SELL'],
userId, userId,
withExcludedAccounts = false withExcludedAccounts = false
}: { }: {
filters?: Filter[]; filters?: Filter[];
includeDrafts?: boolean; includeDrafts?: boolean;
types?: ActivityType[];
userId: string; userId: string;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
}): Promise<{ }): Promise<{
@ -1931,10 +1939,10 @@ export class PortfolioService {
const { activities, count } = await this.orderService.getOrders({ const { activities, count } = await this.orderService.getOrders({
filters, filters,
includeDrafts, includeDrafts,
types,
userCurrency, userCurrency,
userId, userId,
withExcludedAccounts, withExcludedAccounts
types: ['BUY', 'SELL']
}); });
if (count <= 0) { if (count <= 0) {
@ -2006,7 +2014,7 @@ export class PortfolioService {
userCurrency, userCurrency,
userId, userId,
withExcludedAccounts, withExcludedAccounts,
types: ['ITEM', 'LIABILITY'] types: ['LIABILITY']
}); });
const accounts: PortfolioDetails['accounts'] = {}; const accounts: PortfolioDetails['accounts'] = {};
@ -2094,18 +2102,14 @@ export class PortfolioService {
Account, Account,
quantity, quantity,
SymbolProfile, SymbolProfile,
type, type
valueInBaseCurrency
} of ordersByAccount) { } of ordersByAccount) {
const unitPriceInBaseCurrency =
portfolioItemsNow[SymbolProfile.symbol]?.marketPriceInBaseCurrency ??
valueInBaseCurrency ??
0;
let currentValueOfSymbolInBaseCurrency = let currentValueOfSymbolInBaseCurrency =
quantity * unitPriceInBaseCurrency; quantity *
portfolioItemsNow[SymbolProfile.symbol]
?.marketPriceInBaseCurrency ?? 0;
if (type === 'LIABILITY' || type === 'SELL') { if (['LIABILITY', 'SELL'].includes(type)) {
currentValueOfSymbolInBaseCurrency *= -1; currentValueOfSymbolInBaseCurrency *= -1;
} }

2
apps/api/src/app/symbol/symbol.controller.ts

@ -39,7 +39,7 @@ export class SymbolController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async lookupSymbol( public async lookupSymbol(
@Query('includeIndices') includeIndices: boolean = false, @Query('includeIndices') includeIndices = false,
@Query('query') query = '' @Query('query') query = ''
): Promise<{ items: LookupItem[] }> { ): Promise<{ items: LookupItem[] }> {
try { try {

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

@ -166,13 +166,29 @@ export class ManualService implements DataProviderInterface {
} }
}); });
for (const symbolProfile of symbolProfiles) { for (const { currency, symbol } of symbolProfiles) {
response[symbolProfile.symbol] = { let marketPrice = marketData.find((marketDataItem) => {
currency: symbolProfile.currency, return marketDataItem.symbol === symbol;
})?.marketPrice;
if (!marketPrice) {
// Fallback to unit price of last activity
const lastActivity = await this.prismaService.order.findFirst({
orderBy: {
date: 'desc'
},
where: {
SymbolProfile: { symbol }
}
});
marketPrice = lastActivity?.unitPrice;
}
response[symbol] = {
currency,
marketPrice,
dataSource: this.getName(), dataSource: this.getName(),
marketPrice: marketData.find((marketDataItem) => {
return marketDataItem.symbol === symbolProfile.symbol;
})?.marketPrice,
marketState: 'delayed' marketState: 'delayed'
}; };
} }

3
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -227,7 +227,8 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
} }
], ],
range: 'max', range: 'max',
withExcludedAccounts: true withExcludedAccounts: true,
withItems: true
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ chart }) => { .subscribe(({ chart }) => {

8
apps/client/src/app/services/data.service.ts

@ -437,11 +437,13 @@ export class DataService {
public fetchPortfolioPerformance({ public fetchPortfolioPerformance({
filters, filters,
range, range,
withExcludedAccounts = false withExcludedAccounts = false,
withItems = false
}: { }: {
filters?: Filter[]; filters?: Filter[];
range: DateRange; range: DateRange;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
withItems?: boolean;
}): Observable<PortfolioPerformanceResponse> { }): Observable<PortfolioPerformanceResponse> {
let params = this.buildFiltersAsQueryParams({ filters }); let params = this.buildFiltersAsQueryParams({ filters });
params = params.append('range', range); params = params.append('range', range);
@ -450,6 +452,10 @@ export class DataService {
params = params.append('withExcludedAccounts', withExcludedAccounts); params = params.append('withExcludedAccounts', withExcludedAccounts);
} }
if (withItems) {
params = params.append('withItems', withItems);
}
return this.http return this.http
.get<any>(`/api/v2/portfolio/performance`, { .get<any>(`/api/v2/portfolio/performance`, {
params params

2
libs/common/src/lib/interfaces/portfolio-public-details.interface.ts

@ -13,7 +13,7 @@ export interface PortfolioPublicDetails {
| 'dateOfFirstActivity' | 'dateOfFirstActivity'
| 'markets' | 'markets'
| 'name' | 'name'
| 'netPerformancePercent' | 'netPerformancePercentWithCurrencyEffect'
| 'sectors' | 'sectors'
| 'symbol' | 'symbol'
| 'url' | 'url'

Loading…
Cancel
Save