Feature/add feature toggle for new calculation engine (#649)
* Add feature toggle for new calculation engine * Update changelog
This commit is contained in:
parent
f15b33e950
commit
bcb7f5f522
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a new calculation engine (experimental)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed the styling in the footer row of the activities table
|
- Fixed the styling in the footer row of the activities table
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PortfolioServiceFactory } from '@ghostfolio/api/app/portfolio/portfolio-service.factory';
|
import { PortfolioServiceStrategy } from '@ghostfolio/api/app/portfolio/portfolio-service.strategy';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import {
|
import {
|
||||||
nullifyValuesInObject,
|
nullifyValuesInObject,
|
||||||
@ -35,7 +35,7 @@ export class AccountController {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
private readonly portfolioServiceFactory: PortfolioServiceFactory,
|
private readonly portfolioServiceStrategy: PortfolioServiceStrategy,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
private readonly userService: UserService
|
private readonly userService: UserService
|
||||||
) {}
|
) {}
|
||||||
@ -91,7 +91,7 @@ export class AccountController {
|
|||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
let accountsWithAggregations = await this.portfolioServiceFactory
|
let accountsWithAggregations = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getAccountsWithAggregations(impersonationUserId || this.request.user.id);
|
.getAccountsWithAggregations(impersonationUserId || this.request.user.id);
|
||||||
|
|
||||||
|
@ -233,8 +233,6 @@ export class PortfolioCalculatorNew {
|
|||||||
const marketValue = marketSymbolMap[todayString]?.[item.symbol];
|
const marketValue = marketSymbolMap[todayString]?.[item.symbol];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
// annualizedGrossPerformance,
|
|
||||||
// annualizedNetPerformance,
|
|
||||||
grossPerformance,
|
grossPerformance,
|
||||||
grossPerformancePercentage,
|
grossPerformancePercentage,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
@ -340,7 +338,6 @@ export class PortfolioCalculatorNew {
|
|||||||
let lastAveragePrice = new Big(0);
|
let lastAveragePrice = new Big(0);
|
||||||
let lastValueOfInvestment = new Big(0);
|
let lastValueOfInvestment = new Big(0);
|
||||||
let lastNetValueOfInvestment = new Big(0);
|
let lastNetValueOfInvestment = new Big(0);
|
||||||
let previousOrder: PortfolioOrder = null;
|
|
||||||
let timeWeightedGrossPerformancePercentage = new Big(1);
|
let timeWeightedGrossPerformancePercentage = new Big(1);
|
||||||
let timeWeightedNetPerformancePercentage = new Big(1);
|
let timeWeightedNetPerformancePercentage = new Big(1);
|
||||||
let totalInvestment = new Big(0);
|
let totalInvestment = new Big(0);
|
||||||
@ -436,12 +433,6 @@ export class PortfolioCalculatorNew {
|
|||||||
.minus(totalInvestment)
|
.minus(totalInvestment)
|
||||||
.plus(grossPerformanceFromSells);
|
.plus(grossPerformanceFromSells);
|
||||||
|
|
||||||
const grossPerformanceSinceLastTransaction =
|
|
||||||
newGrossPerformance.minus(grossPerformance);
|
|
||||||
|
|
||||||
const netPerformanceSinceLastTransaction =
|
|
||||||
grossPerformanceSinceLastTransaction.minus(previousOrder?.fee ?? 0);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
i > indexOfStartOrder &&
|
i > indexOfStartOrder &&
|
||||||
!lastValueOfInvestment
|
!lastValueOfInvestment
|
||||||
@ -491,31 +482,8 @@ export class PortfolioCalculatorNew {
|
|||||||
feesAtStartDate = fees;
|
feesAtStartDate = fees;
|
||||||
grossPerformanceAtStartDate = grossPerformance;
|
grossPerformanceAtStartDate = grossPerformance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*console.log(`
|
|
||||||
Symbol: ${symbol}
|
|
||||||
Date: ${order.date}
|
|
||||||
Price: ${order.unitPrice}
|
|
||||||
transactionInvestment: ${transactionInvestment}
|
|
||||||
totalUnits: ${totalUnits}
|
|
||||||
totalInvestment: ${totalInvestment}
|
|
||||||
valueOfInvestment: ${valueOfInvestment}
|
|
||||||
lastAveragePrice: ${lastAveragePrice}
|
|
||||||
grossPerformanceFromSell: ${grossPerformanceFromSell}
|
|
||||||
grossPerformanceFromSells: ${grossPerformanceFromSells}
|
|
||||||
grossPerformance: ${grossPerformance.minus(grossPerformanceAtStartDate)}
|
|
||||||
netPerformance: ${grossPerformance.minus(fees)}
|
|
||||||
netPerformanceSinceLastTransaction: ${netPerformanceSinceLastTransaction}
|
|
||||||
grossPerformanceSinceLastTransaction: ${grossPerformanceSinceLastTransaction}
|
|
||||||
timeWeightedGrossPerformancePercentage: ${timeWeightedGrossPerformancePercentage}
|
|
||||||
timeWeightedNetPerformancePercentage: ${timeWeightedNetPerformancePercentage}
|
|
||||||
`);*/
|
|
||||||
|
|
||||||
previousOrder = order;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('\n---\n');
|
|
||||||
|
|
||||||
timeWeightedGrossPerformancePercentage =
|
timeWeightedGrossPerformancePercentage =
|
||||||
timeWeightedGrossPerformancePercentage.sub(1);
|
timeWeightedGrossPerformancePercentage.sub(1);
|
||||||
|
|
||||||
@ -531,8 +499,8 @@ export class PortfolioCalculatorNew {
|
|||||||
.minus(fees.minus(feesAtStartDate));
|
.minus(fees.minus(feesAtStartDate));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasErrors: !initialValue || !unitPriceAtEndDate,
|
|
||||||
initialValue,
|
initialValue,
|
||||||
|
hasErrors: !initialValue || !unitPriceAtEndDate,
|
||||||
netPerformance: totalNetPerformance,
|
netPerformance: totalNetPerformance,
|
||||||
netPerformancePercentage: timeWeightedNetPerformancePercentage,
|
netPerformancePercentage: timeWeightedNetPerformancePercentage,
|
||||||
grossPerformance: totalGrossPerformance,
|
grossPerformance: totalGrossPerformance,
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PortfolioService } from './portfolio.service';
|
|
||||||
import { PortfolioServiceNew } from './portfolio.service-new';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PortfolioServiceFactory {
|
|
||||||
public constructor(
|
|
||||||
private readonly portfolioService: PortfolioService,
|
|
||||||
private readonly portfolioServiceNew: PortfolioServiceNew
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public get() {
|
|
||||||
if (false) {
|
|
||||||
return this.portfolioServiceNew;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.portfolioService;
|
|
||||||
}
|
|
||||||
}
|
|
25
apps/api/src/app/portfolio/portfolio-service.strategy.ts
Normal file
25
apps/api/src/app/portfolio/portfolio-service.strategy.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { REQUEST } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { PortfolioService } from './portfolio.service';
|
||||||
|
import { PortfolioServiceNew } from './portfolio.service-new';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PortfolioServiceStrategy {
|
||||||
|
public constructor(
|
||||||
|
private readonly portfolioService: PortfolioService,
|
||||||
|
private readonly portfolioServiceNew: PortfolioServiceNew,
|
||||||
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public get() {
|
||||||
|
if (
|
||||||
|
this.request.user?.Settings?.settings?.['isNewCalculationEngine'] === true
|
||||||
|
) {
|
||||||
|
return this.portfolioServiceNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.portfolioService;
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
|||||||
|
|
||||||
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface';
|
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface';
|
||||||
import { PortfolioPositions } from './interfaces/portfolio-positions.interface';
|
import { PortfolioPositions } from './interfaces/portfolio-positions.interface';
|
||||||
import { PortfolioServiceFactory } from './portfolio-service.factory';
|
import { PortfolioServiceStrategy } from './portfolio-service.strategy';
|
||||||
|
|
||||||
@Controller('portfolio')
|
@Controller('portfolio')
|
||||||
export class PortfolioController {
|
export class PortfolioController {
|
||||||
@ -43,7 +43,7 @@ export class PortfolioController {
|
|||||||
private readonly accessService: AccessService,
|
private readonly accessService: AccessService,
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly portfolioServiceFactory: PortfolioServiceFactory,
|
private readonly portfolioServiceStrategy: PortfolioServiceStrategy,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
private readonly userService: UserService
|
private readonly userService: UserService
|
||||||
) {}
|
) {}
|
||||||
@ -55,7 +55,7 @@ export class PortfolioController {
|
|||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioChart> {
|
): Promise<PortfolioChart> {
|
||||||
const historicalDataContainer = await this.portfolioServiceFactory
|
const historicalDataContainer = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getChart(impersonationId, range);
|
.getChart(impersonationId, range);
|
||||||
|
|
||||||
@ -114,9 +114,10 @@ export class PortfolioController {
|
|||||||
|
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
const { accounts, holdings, hasErrors } = await this.portfolioServiceFactory
|
const { accounts, holdings, hasErrors } =
|
||||||
.get()
|
await this.portfolioServiceStrategy
|
||||||
.getDetails(impersonationId, this.request.user.id, range);
|
.get()
|
||||||
|
.getDetails(impersonationId, this.request.user.id, range);
|
||||||
|
|
||||||
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
@ -174,7 +175,7 @@ export class PortfolioController {
|
|||||||
return <any>res.json({});
|
return <any>res.json({});
|
||||||
}
|
}
|
||||||
|
|
||||||
let investments = await this.portfolioServiceFactory
|
let investments = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getInvestments(impersonationId);
|
.getInvestments(impersonationId);
|
||||||
|
|
||||||
@ -203,7 +204,7 @@ export class PortfolioController {
|
|||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||||
const performanceInformation = await this.portfolioServiceFactory
|
const performanceInformation = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getPerformance(impersonationId, range);
|
.getPerformance(impersonationId, range);
|
||||||
|
|
||||||
@ -227,7 +228,7 @@ export class PortfolioController {
|
|||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioPositions> {
|
): Promise<PortfolioPositions> {
|
||||||
const result = await this.portfolioServiceFactory
|
const result = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getPositions(impersonationId, range);
|
.getPositions(impersonationId, range);
|
||||||
|
|
||||||
@ -268,7 +269,7 @@ export class PortfolioController {
|
|||||||
hasDetails = user.subscription.type === 'Premium';
|
hasDetails = user.subscription.type === 'Premium';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { holdings } = await this.portfolioServiceFactory
|
const { holdings } = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getDetails(access.userId, access.userId);
|
.getDetails(access.userId, access.userId);
|
||||||
|
|
||||||
@ -311,7 +312,7 @@ export class PortfolioController {
|
|||||||
public async getSummary(
|
public async getSummary(
|
||||||
@Headers('impersonation-id') impersonationId
|
@Headers('impersonation-id') impersonationId
|
||||||
): Promise<PortfolioSummary> {
|
): Promise<PortfolioSummary> {
|
||||||
let summary = await this.portfolioServiceFactory
|
let summary = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getSummary(impersonationId);
|
.getSummary(impersonationId);
|
||||||
|
|
||||||
@ -342,7 +343,7 @@ export class PortfolioController {
|
|||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Param('symbol') symbol
|
@Param('symbol') symbol
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
let position = await this.portfolioServiceFactory
|
let position = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getPosition(impersonationId, symbol);
|
.getPosition(impersonationId, symbol);
|
||||||
|
|
||||||
@ -386,7 +387,7 @@ export class PortfolioController {
|
|||||||
|
|
||||||
return <any>(
|
return <any>(
|
||||||
res.json(
|
res.json(
|
||||||
await this.portfolioServiceFactory.get().getReport(impersonationId)
|
await this.portfolioServiceStrategy.get().getReport(impersonationId)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,14 @@ import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.mod
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { CurrentRateService } from './current-rate.service';
|
import { CurrentRateService } from './current-rate.service';
|
||||||
import { PortfolioServiceFactory } from './portfolio-service.factory';
|
import { PortfolioServiceStrategy } from './portfolio-service.strategy';
|
||||||
import { PortfolioController } from './portfolio.controller';
|
import { PortfolioController } from './portfolio.controller';
|
||||||
import { PortfolioService } from './portfolio.service';
|
import { PortfolioService } from './portfolio.service';
|
||||||
import { PortfolioServiceNew } from './portfolio.service-new';
|
import { PortfolioServiceNew } from './portfolio.service-new';
|
||||||
import { RulesService } from './rules.service';
|
import { RulesService } from './rules.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
exports: [PortfolioServiceFactory],
|
exports: [PortfolioServiceStrategy],
|
||||||
imports: [
|
imports: [
|
||||||
AccessModule,
|
AccessModule,
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
@ -40,7 +40,7 @@ import { RulesService } from './rules.service';
|
|||||||
CurrentRateService,
|
CurrentRateService,
|
||||||
PortfolioService,
|
PortfolioService,
|
||||||
PortfolioServiceNew,
|
PortfolioServiceNew,
|
||||||
PortfolioServiceFactory,
|
PortfolioServiceStrategy,
|
||||||
RulesService
|
RulesService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -399,11 +399,12 @@ export class PortfolioServiceNew {
|
|||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aSymbol: string
|
aSymbol: string
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
|
const userCurrency = this.request.user.Settings.currency;
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
|
||||||
const orders = (await this.orderService.getOrders({ userId })).filter(
|
const orders = (
|
||||||
(order) => order.symbol === aSymbol
|
await this.orderService.getOrders({ userCurrency, userId })
|
||||||
);
|
).filter((order) => order.symbol === aSymbol);
|
||||||
|
|
||||||
if (orders.length <= 0) {
|
if (orders.length <= 0) {
|
||||||
return {
|
return {
|
||||||
@ -871,24 +872,25 @@ export class PortfolioServiceNew {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
||||||
const currency = this.request.user.Settings.currency;
|
const userCurrency = this.request.user.Settings.currency;
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
|
||||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
const performanceInformation = await this.getPerformance(aImpersonationId);
|
||||||
|
|
||||||
const { balance } = await this.accountService.getCashDetails(
|
const { balance } = await this.accountService.getCashDetails(
|
||||||
userId,
|
userId,
|
||||||
currency
|
userCurrency
|
||||||
);
|
);
|
||||||
const orders = await this.orderService.getOrders({
|
const orders = await this.orderService.getOrders({
|
||||||
|
userCurrency,
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
const dividend = this.getDividend(orders).toNumber();
|
const dividend = this.getDividend(orders).toNumber();
|
||||||
const fees = this.getFees(orders).toNumber();
|
const fees = this.getFees(orders).toNumber();
|
||||||
const firstOrderDate = orders[0]?.date;
|
const firstOrderDate = orders[0]?.date;
|
||||||
|
|
||||||
const totalBuy = this.getTotalByType(orders, currency, 'BUY');
|
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
||||||
const totalSell = this.getTotalByType(orders, currency, 'SELL');
|
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
||||||
|
|
||||||
const committedFunds = new Big(totalBuy).sub(totalSell);
|
const committedFunds = new Big(totalBuy).sub(totalSell);
|
||||||
|
|
||||||
@ -1051,8 +1053,11 @@ export class PortfolioServiceNew {
|
|||||||
orders: OrderWithAccount[];
|
orders: OrderWithAccount[];
|
||||||
portfolioOrders: PortfolioOrder[];
|
portfolioOrders: PortfolioOrder[];
|
||||||
}> {
|
}> {
|
||||||
|
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||||
|
|
||||||
const orders = await this.orderService.getOrders({
|
const orders = await this.orderService.getOrders({
|
||||||
includeDrafts,
|
includeDrafts,
|
||||||
|
userCurrency,
|
||||||
userId,
|
userId,
|
||||||
types: ['BUY', 'SELL']
|
types: ['BUY', 'SELL']
|
||||||
});
|
});
|
||||||
@ -1061,7 +1066,6 @@ export class PortfolioServiceNew {
|
|||||||
return { transactionPoints: [], orders: [], portfolioOrders: [] };
|
return { transactionPoints: [], orders: [], portfolioOrders: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
|
||||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { IsBoolean } from 'class-validator';
|
import { IsBoolean, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateUserSettingDto {
|
export class UpdateUserSettingDto {
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
isNewCalculationEngine?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
isRestrictedView?: boolean;
|
isRestrictedView?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { Provider, Role } from '@prisma/client';
|
import { Provider } from '@prisma/client';
|
||||||
import { User as UserModel } from '@prisma/client';
|
import { User as UserModel } from '@prisma/client';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
@ -115,6 +115,12 @@ export class UserController {
|
|||||||
...data
|
...data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const key in userSettings) {
|
||||||
|
if (userSettings[key] === false) {
|
||||||
|
delete userSettings[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this.userService.updateUserSetting({
|
return await this.userService.updateUserSetting({
|
||||||
userSettings,
|
userSettings,
|
||||||
userId: this.request.user.id
|
userId: this.request.user.id
|
||||||
|
@ -192,6 +192,24 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onNewCalculationChange(aEvent: MatSlideToggleChange) {
|
||||||
|
this.dataService
|
||||||
|
.putUserSetting({ isNewCalculationEngine: aEvent.checked })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
this.userService
|
||||||
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public onRedeemCoupon() {
|
public onRedeemCoupon() {
|
||||||
let couponCode = prompt('Please enter your coupon code:');
|
let couponCode = prompt('Please enter your coupon code:');
|
||||||
couponCode = couponCode?.trim();
|
couponCode = couponCode?.trim();
|
||||||
|
@ -135,6 +135,23 @@
|
|||||||
></mat-slide-toggle>
|
></mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="user?.subscription"
|
||||||
|
class="align-items-center d-flex mt-4 py-1"
|
||||||
|
>
|
||||||
|
<div class="pr-1 w-50">
|
||||||
|
<div i18n>New Calculation Engine</div>
|
||||||
|
<div class="hint-text text-muted" i18n>Experimental</div>
|
||||||
|
</div>
|
||||||
|
<div class="pl-1 w-50">
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[checked]="user.settings.isNewCalculationEngine"
|
||||||
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
|
(change)="onNewCalculationChange($event)"
|
||||||
|
></mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user