Browse Source

feat: add filters for chart, perf, positions, summary

pull/1290/head
Zakaria YAHI 3 years ago
parent
commit
06941046be
  1. 84
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 33
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 4
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  4. 4
      apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts
  5. 207
      apps/client/src/app/services/data.service.ts

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

@ -65,11 +65,19 @@ export class PortfolioController {
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getChart( public async getChart(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') range @Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') range?: DateRange,
@Query('tags') filterByTags?: string
): Promise<PortfolioChart> { ): Promise<PortfolioChart> {
const historicalDataContainer = await this.portfolioService.getChart( const historicalDataContainer = await this.portfolioService.getChart(
impersonationId, impersonationId,
range range,
this.buildFiltersFromQueries(
filterByAccounts,
filterByAssetClasses,
filterByTags
)
); );
let chartData = historicalDataContainer.items; let chartData = historicalDataContainer.items;
@ -278,11 +286,19 @@ export class PortfolioController {
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPerformance( public async getPerformance(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') range @Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') range?: DateRange,
@Query('tags') filterByTags?: string
): Promise<PortfolioPerformanceResponse> { ): Promise<PortfolioPerformanceResponse> {
const performanceInformation = await this.portfolioService.getPerformance( const performanceInformation = await this.portfolioService.getPerformance(
impersonationId, impersonationId,
range range,
this.buildFiltersFromQueries(
filterByAccounts,
filterByAssetClasses,
filterByTags
)
); );
if ( if (
@ -333,11 +349,19 @@ export class PortfolioController {
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPositions( public async getPositions(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') range @Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') range?: DateRange,
@Query('tags') filterByTags?: string
): Promise<PortfolioPositions> { ): Promise<PortfolioPositions> {
const result = await this.portfolioService.getPositions( const result = await this.portfolioService.getPositions(
impersonationId, impersonationId,
range range,
this.buildFiltersFromQueries(
filterByAccounts,
filterByAssetClasses,
filterByTags
)
); );
if ( if (
@ -423,7 +447,10 @@ export class PortfolioController {
@Get('summary') @Get('summary')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getSummary( public async getSummary(
@Headers('impersonation-id') impersonationId @Headers('impersonation-id') impersonationId,
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string
): Promise<PortfolioSummary> { ): Promise<PortfolioSummary> {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
@ -435,7 +462,15 @@ export class PortfolioController {
); );
} }
let summary = await this.portfolioService.getSummary(impersonationId); const filters = this.buildFiltersFromQueries(
filterByAccounts,
filterByAssetClasses,
filterByTags
);
let summary = await this.portfolioService.getSummary(
impersonationId,
filters
);
if ( if (
impersonationId || impersonationId ||
@ -516,4 +551,37 @@ export class PortfolioController {
return await this.portfolioService.getReport(impersonationId); return await this.portfolioService.getReport(impersonationId);
} }
private buildFiltersFromQueries(
filterByAccounts?: string,
filterByAssetClasses?: string,
filterByTags?: string
) {
const accountIds = filterByAccounts?.split(',') ?? [];
const assetClasses = filterByAssetClasses?.split(',') ?? [];
const tagIds = filterByTags?.split(',') ?? [];
const filters: Filter[] = [
...accountIds.map((accountId) => {
return <Filter>{
id: accountId,
type: 'ACCOUNT'
};
}),
...assetClasses.map((assetClass) => {
return <Filter>{
id: assetClass,
type: 'ASSET_CLASS'
};
}),
...tagIds.map((tagId) => {
return <Filter>{
id: tagId,
type: 'TAG'
};
})
];
return filters;
}
} }

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

@ -268,13 +268,15 @@ export class PortfolioService {
public async getChart( public async getChart(
aImpersonationId: string, aImpersonationId: string,
aDateRange: DateRange = 'max' aDateRange: DateRange = 'max',
aFilters?: Filter[]
): Promise<HistoricalDataContainer> { ): Promise<HistoricalDataContainer> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
userId userId,
filters: aFilters
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
@ -845,13 +847,15 @@ export class PortfolioService {
public async getPositions( public async getPositions(
aImpersonationId: string, aImpersonationId: string,
aDateRange: DateRange = 'max' aDateRange: DateRange = 'max',
aFilters?: Filter[]
): Promise<{ hasErrors: boolean; positions: Position[] }> { ): Promise<{ hasErrors: boolean; positions: Position[] }> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
userId userId,
filters: aFilters
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
@ -921,12 +925,14 @@ export class PortfolioService {
public async getPerformance( public async getPerformance(
aImpersonationId: string, aImpersonationId: string,
aDateRange: DateRange = 'max' aDateRange: DateRange = 'max',
aFilters?: Filter[]
): Promise<PortfolioPerformanceResponse> { ): Promise<PortfolioPerformanceResponse> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
filters: aFilters,
userId userId
}); });
@ -1181,20 +1187,29 @@ export class PortfolioService {
}; };
} }
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> { public async getSummary(
aImpersonationId: string,
aFilters?: Filter[]
): Promise<PortfolioSummary> {
const userCurrency = this.request.user.Settings.settings.baseCurrency; const userCurrency = this.request.user.Settings.settings.baseCurrency;
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const performanceInformation = await this.getPerformance(aImpersonationId); const performanceInformation = await this.getPerformance(
aImpersonationId,
undefined,
aFilters
);
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
userId, userId,
currency: userCurrency currency: userCurrency,
filters: aFilters
}); });
const orders = await this.orderService.getOrders({ const orders = await this.orderService.getOrders({
userCurrency, userCurrency,
userId userId,
filters: aFilters
}); });
const dividend = this.getDividend(orders).toNumber(); const dividend = this.getDividend(orders).toNumber();
const emergencyFund = new Big( const emergencyFund = new Big(

4
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -136,9 +136,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
? $localize`Filter by account or tag...` ? $localize`Filter by account or tag...`
: ''; : '';
return this.dataService.fetchPortfolioDetails({ return this.dataService.fetchPortfolioDetails(this.activeFilters);
filters: this.activeFilters
});
}), }),
takeUntil(this.unsubscribeSubject) takeUntil(this.unsubscribeSubject)
) )

4
apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts

@ -87,9 +87,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
? $localize`Filter by account or tag...` ? $localize`Filter by account or tag...`
: ''; : '';
return this.dataService.fetchPortfolioDetails({ return this.dataService.fetchPortfolioDetails(this.activeFilters);
filters: this.activeFilters
});
}), }),
takeUntil(this.unsubscribeSubject) takeUntil(this.unsubscribeSubject)
) )

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

@ -75,60 +75,21 @@ export class DataService {
}: { }: {
filters?: Filter[]; filters?: Filter[];
}): Observable<Activities> { }): Observable<Activities> {
let params = new HttpParams(); return this.http
.get<any>('/api/v1/order', {
if (filters?.length > 0) { params: {
const { ...this.buildParamsFilter(filters)
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<any>('/api/v1/order', { params }).pipe(
map(({ activities }) => {
for (const activity of activities) {
activity.createdAt = parseISO(activity.createdAt);
activity.date = parseISO(activity.date);
} }
return { activities };
}) })
); .pipe(
map(({ activities }) => {
for (const activity of activities) {
activity.createdAt = parseISO(activity.createdAt);
activity.date = parseISO(activity.date);
}
return { activities };
})
);
} }
public fetchAdminData() { public fetchAdminData() {
@ -202,9 +163,20 @@ export class DataService {
return this.http.get<BenchmarkResponse>('/api/v1/benchmark'); return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
} }
public fetchChart({ range, version }: { range: DateRange; version: number }) { public fetchChart({
range,
version,
filters
}: {
range: DateRange;
version: number;
filters?: Filter[];
}) {
return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, { return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, {
params: { range } params: {
range,
...this.buildParamsFilter(filters)
}
}); });
} }
@ -283,12 +255,14 @@ export class DataService {
} }
public fetchPositions({ public fetchPositions({
range range,
filters
}: { }: {
range: DateRange; range: DateRange;
filters?: Filter[];
}): Observable<PortfolioPositions> { }): Observable<PortfolioPositions> {
return this.http.get<PortfolioPositions>('/api/v1/portfolio/positions', { return this.http.get<PortfolioPositions>('/api/v1/portfolio/positions', {
params: { range } params: { range, ...this.buildParamsFilter(filters) }
}); });
} }
@ -302,67 +276,24 @@ export class DataService {
); );
} }
public fetchPortfolioDetails({ filters }: { filters?: Filter[] }) { public fetchPortfolioDetails(filters?: Filter[]) {
let params = new HttpParams();
if (filters?.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', { return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', {
params params: { ...this.buildParamsFilter(filters) }
}); });
} }
public fetchPortfolioPerformance({ public fetchPortfolioPerformance({
range, range,
version version,
filters
}: { }: {
range: DateRange; range: DateRange;
version: number; version: number;
filters?: Filter[];
}) { }) {
return this.http.get<PortfolioPerformanceResponse>( return this.http.get<PortfolioPerformanceResponse>(
`/api/v${version}/portfolio/performance`, `/api/v${version}/portfolio/performance`,
{ params: { range } } { params: { range, ...this.buildParamsFilter(filters) } }
); );
} }
@ -376,16 +307,22 @@ export class DataService {
return this.http.get<PortfolioReport>('/api/v1/portfolio/report'); return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
} }
public fetchPortfolioSummary(): Observable<PortfolioSummary> { public fetchPortfolioSummary(
return this.http.get<any>('/api/v1/portfolio/summary').pipe( filters?: Filter[]
map((summary) => { ): Observable<PortfolioSummary> {
if (summary.firstOrderDate) { return this.http
summary.firstOrderDate = parseISO(summary.firstOrderDate); .get<any>('/api/v1/portfolio/summary', {
} params: { ...this.buildParamsFilter(filters) }
return summary;
}) })
); .pipe(
map((summary) => {
if (summary.firstOrderDate) {
summary.firstOrderDate = parseISO(summary.firstOrderDate);
}
return summary;
})
);
} }
public fetchPositionDetail({ public fetchPositionDetail({
@ -456,4 +393,46 @@ export class DataService {
couponCode couponCode
}); });
} }
private buildParamsFilter(filters: Filter[]) {
const paramsFilter: {
accounts?: string;
assetClasses?: string;
tags?: string;
} = {};
if (filters?.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
paramsFilter.accounts = filtersByAccount
.map(({ id }) => {
return id;
})
.join(',');
}
if (filtersByAssetClass) {
paramsFilter.assetClasses = filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',');
}
if (filtersByTag) {
paramsFilter.tags = filtersByTag
.map(({ id }) => {
return id;
})
.join(',');
}
}
return paramsFilter;
}
} }

Loading…
Cancel
Save