Browse Source
Feature/add business logic of rule settings (#3826)
* Add business logic of rule settings
* Update changelog
---------
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/3829/head^2
Shaunak Das
4 months ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with
63 additions and
45 deletions
-
CHANGELOG.md
-
apps/api/src/app/user/update-user-setting.dto.ts
-
apps/api/src/models/rules/account-cluster-risk/current-investment.ts
-
apps/api/src/models/rules/account-cluster-risk/single-account.ts
-
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
-
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
-
apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts
-
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
-
apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts
-
apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html
-
apps/client/src/app/components/rule/rule.component.html
-
apps/client/src/app/components/rule/rule.component.ts
-
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
-
libs/common/src/lib/types/x-ray-rules-settings.type.ts
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 |
|
|
|
|
|
|
|
## Unreleased |
|
|
|
|
|
|
|
### Added |
|
|
|
|
|
|
|
- Added support to customize the rule thresholds in the _X-ray_ section (experimental) |
|
|
|
|
|
|
|
### Changed |
|
|
|
|
|
|
|
- Improved the language localization for German (`de`) |
|
|
|
|
|
@ -1,4 +1,5 @@ |
|
|
|
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
|
|
|
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; |
|
|
|
import type { |
|
|
|
ColorScheme, |
|
|
|
DateRange, |
|
|
|
|
|
@ -76,11 +76,11 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): Settings { |
|
|
|
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { |
|
|
|
return { |
|
|
|
baseCurrency: aUserSettings.baseCurrency, |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: 0.5 |
|
|
|
baseCurrency, |
|
|
|
isActive: xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: xRayRules[this.getKey()]?.thresholdMax ?? 0.5 |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
@ -34,9 +34,9 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): RuleSettings { |
|
|
|
public getSettings({ xRayRules }: UserSettings): RuleSettings { |
|
|
|
return { |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive |
|
|
|
isActive: xRayRules[this.getKey()].isActive |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
@ -62,10 +62,10 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): Settings { |
|
|
|
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { |
|
|
|
return { |
|
|
|
baseCurrency: aUserSettings.baseCurrency, |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive |
|
|
|
baseCurrency, |
|
|
|
isActive: xRayRules[this.getKey()].isActive |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
@ -62,11 +62,11 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): Settings { |
|
|
|
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { |
|
|
|
return { |
|
|
|
baseCurrency: aUserSettings.baseCurrency, |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: 0.5 |
|
|
|
baseCurrency, |
|
|
|
isActive: xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: xRayRules[this.getKey()]?.thresholdMax ?? 0.5 |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
@ -19,7 +19,7 @@ export class EmergencyFundSetup extends Rule<Settings> { |
|
|
|
} |
|
|
|
|
|
|
|
public evaluate(ruleSettings: Settings) { |
|
|
|
if (this.emergencyFund < ruleSettings.thresholdMin) { |
|
|
|
if (!this.emergencyFund) { |
|
|
|
return { |
|
|
|
evaluation: 'No emergency fund has been set up', |
|
|
|
value: false |
|
|
@ -32,16 +32,14 @@ export class EmergencyFundSetup extends Rule<Settings> { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): Settings { |
|
|
|
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { |
|
|
|
return { |
|
|
|
baseCurrency: aUserSettings.baseCurrency, |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMin: 0 |
|
|
|
baseCurrency, |
|
|
|
isActive: xRayRules[this.getKey()].isActive |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
interface Settings extends RuleSettings { |
|
|
|
baseCurrency: string; |
|
|
|
thresholdMin: number; |
|
|
|
} |
|
|
|
|
|
@ -43,11 +43,11 @@ export class FeeRatioInitialInvestment extends Rule<Settings> { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public getSettings(aUserSettings: UserSettings): Settings { |
|
|
|
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { |
|
|
|
return { |
|
|
|
baseCurrency: aUserSettings.baseCurrency, |
|
|
|
isActive: aUserSettings.xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: 0.01 |
|
|
|
baseCurrency, |
|
|
|
isActive: xRayRules[this.getKey()].isActive, |
|
|
|
thresholdMax: xRayRules[this.getKey()]?.thresholdMax ?? 0.01 |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
@ -2,6 +2,7 @@ import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; |
|
|
|
|
|
|
|
import { CommonModule } from '@angular/common'; |
|
|
|
import { Component, Inject } from '@angular/core'; |
|
|
|
import { FormsModule } from '@angular/forms'; |
|
|
|
import { MatButtonModule } from '@angular/material/button'; |
|
|
|
import { |
|
|
|
MAT_DIALOG_DATA, |
|
|
@ -16,6 +17,7 @@ import { IRuleSettingsDialogParams } from './interfaces/interfaces'; |
|
|
|
@Component({ |
|
|
|
imports: [ |
|
|
|
CommonModule, |
|
|
|
FormsModule, |
|
|
|
MatButtonModule, |
|
|
|
MatDialogModule, |
|
|
|
MatFormFieldModule, |
|
|
|
|
|
@ -1,23 +1,37 @@ |
|
|
|
<div mat-dialog-title>{{ data.rule.name }}</div> |
|
|
|
|
|
|
|
<div class="py-3" mat-dialog-content> |
|
|
|
<mat-form-field appearance="outline" class="w-100"> |
|
|
|
<mat-form-field |
|
|
|
appearance="outline" |
|
|
|
class="w-100" |
|
|
|
[ngClass]="{ 'd-none': settings.thresholdMin === undefined }" |
|
|
|
> |
|
|
|
<mat-label i18n>Threshold Min</mat-label> |
|
|
|
<input matInput name="thresholdMin" type="number" /> |
|
|
|
<input |
|
|
|
matInput |
|
|
|
name="thresholdMin" |
|
|
|
type="number" |
|
|
|
[(ngModel)]="settings.thresholdMin" |
|
|
|
/> |
|
|
|
</mat-form-field> |
|
|
|
<mat-form-field appearance="outline" class="w-100"> |
|
|
|
<mat-form-field |
|
|
|
appearance="outline" |
|
|
|
class="w-100" |
|
|
|
[ngClass]="{ 'd-none': settings.thresholdMax === undefined }" |
|
|
|
> |
|
|
|
<mat-label i18n>Threshold Max</mat-label> |
|
|
|
<input matInput name="thresholdMax" type="number" /> |
|
|
|
<input |
|
|
|
matInput |
|
|
|
name="thresholdMax" |
|
|
|
type="number" |
|
|
|
[(ngModel)]="settings.thresholdMax" |
|
|
|
/> |
|
|
|
</mat-form-field> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div align="end" mat-dialog-actions> |
|
|
|
<button i18n mat-button (click)="dialogRef.close()">Close</button> |
|
|
|
<button |
|
|
|
color="primary" |
|
|
|
mat-flat-button |
|
|
|
(click)="dialogRef.close({ settings })" |
|
|
|
> |
|
|
|
<button color="primary" mat-flat-button (click)="dialogRef.close(settings)"> |
|
|
|
<ng-container i18n>Save</ng-container> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
@ -62,7 +62,7 @@ |
|
|
|
<ion-icon name="ellipsis-horizontal" /> |
|
|
|
</button> |
|
|
|
<mat-menu #rulesMenu="matMenu" xPosition="before"> |
|
|
|
@if (rule?.isActive && !isEmpty(rule.settings) && false) { |
|
|
|
@if (rule?.isActive && !isEmpty(rule.settings)) { |
|
|
|
<button mat-menu-item (click)="onCustomizeRule(rule)"> |
|
|
|
<ng-container i18n>Customize</ng-container>... |
|
|
|
</button> |
|
|
|
|
|
@ -55,16 +55,15 @@ export class RuleComponent implements OnInit { |
|
|
|
dialogRef |
|
|
|
.afterClosed() |
|
|
|
.pipe(takeUntil(this.unsubscribeSubject)) |
|
|
|
.subscribe( |
|
|
|
({ settings }: { settings: PortfolioReportRule['settings'] }) => { |
|
|
|
if (settings) { |
|
|
|
console.log(settings); |
|
|
|
|
|
|
|
// TODO
|
|
|
|
// this.ruleUpdated.emit(settings);
|
|
|
|
} |
|
|
|
.subscribe((settings: PortfolioReportRule['settings']) => { |
|
|
|
if (settings) { |
|
|
|
this.ruleUpdated.emit({ |
|
|
|
xRayRules: { |
|
|
|
[rule.key]: settings |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public onUpdateRule(rule: PortfolioReportRule) { |
|
|
|
|
|
@ -134,8 +134,6 @@ export class FirePageComponent implements OnDestroy, OnInit { |
|
|
|
} |
|
|
|
|
|
|
|
public onRulesUpdated(event: UpdateUserSettingDto) { |
|
|
|
this.isLoading = true; |
|
|
|
|
|
|
|
this.dataService |
|
|
|
.putUserSetting(event) |
|
|
|
.pipe(takeUntil(this.unsubscribeSubject)) |
|
|
|
|
|
@ -1,3 +1,5 @@ |
|
|
|
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; |
|
|
|
|
|
|
|
export type XRayRulesSettings = { |
|
|
|
AccountClusterRiskCurrentInvestment?: RuleSettings; |
|
|
|
AccountClusterRiskSingleAccount?: RuleSettings; |
|
|
@ -7,6 +9,6 @@ export type XRayRulesSettings = { |
|
|
|
FeeRatioInitialInvestment?: RuleSettings; |
|
|
|
}; |
|
|
|
|
|
|
|
interface RuleSettings { |
|
|
|
interface RuleSettings extends Pick<PortfolioReportRule, 'settings'> { |
|
|
|
isActive: boolean; |
|
|
|
} |
|
|
|