Feature/add support for liabilities (#1789)
* Add support for liabilities * Update changelog
This commit is contained in:
parent
04eb452e04
commit
7931e6950d
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for liabilities
|
||||||
|
|
||||||
## 1.279.0 - 2023-06-10
|
## 1.279.0 - 2023-06-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -96,7 +96,7 @@ export class OrderService {
|
|||||||
const updateAccountBalance = data.updateAccountBalance ?? false;
|
const updateAccountBalance = data.updateAccountBalance ?? false;
|
||||||
const userId = data.userId;
|
const userId = data.userId;
|
||||||
|
|
||||||
if (data.type === 'ITEM') {
|
if (data.type === 'ITEM' || data.type === 'LIABILITY') {
|
||||||
const assetClass = data.assetClass;
|
const assetClass = data.assetClass;
|
||||||
const assetSubClass = data.assetSubClass;
|
const assetSubClass = data.assetSubClass;
|
||||||
currency = data.SymbolProfile.connectOrCreate.create.currency;
|
currency = data.SymbolProfile.connectOrCreate.create.currency;
|
||||||
@ -129,7 +129,10 @@ export class OrderService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDraft = isAfter(data.date as Date, endOfToday());
|
const isDraft =
|
||||||
|
data.type === 'LIABILITY'
|
||||||
|
? false
|
||||||
|
: isAfter(data.date as Date, endOfToday());
|
||||||
|
|
||||||
if (!isDraft) {
|
if (!isDraft) {
|
||||||
// Gather symbol data of order in the background, if not draft
|
// Gather symbol data of order in the background, if not draft
|
||||||
@ -320,7 +323,11 @@ export class OrderService {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.filter((order) => {
|
.filter((order) => {
|
||||||
return withExcludedAccounts || order.Account?.isExcluded === false;
|
return (
|
||||||
|
withExcludedAccounts ||
|
||||||
|
!order.Account ||
|
||||||
|
order.Account?.isExcluded === false
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.map((order) => {
|
.map((order) => {
|
||||||
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
|
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
|
||||||
|
@ -162,6 +162,7 @@ export class PortfolioController {
|
|||||||
'excludedAccountsAndActivities',
|
'excludedAccountsAndActivities',
|
||||||
'fees',
|
'fees',
|
||||||
'items',
|
'items',
|
||||||
|
'liabilities',
|
||||||
'netWorth',
|
'netWorth',
|
||||||
'totalBuy',
|
'totalBuy',
|
||||||
'totalSell'
|
'totalSell'
|
||||||
|
@ -1302,12 +1302,11 @@ export class PortfolioService {
|
|||||||
}: {
|
}: {
|
||||||
activities: OrderWithAccount[];
|
activities: OrderWithAccount[];
|
||||||
date?: Date;
|
date?: Date;
|
||||||
|
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
}) {
|
}) {
|
||||||
return activities
|
return activities
|
||||||
.filter((activity) => {
|
.filter((activity) => {
|
||||||
// Filter out all activities before given date and type dividend
|
// Filter out all activities before given date (drafts) and type dividend
|
||||||
return (
|
return (
|
||||||
isBefore(date, new Date(activity.date)) &&
|
isBefore(date, new Date(activity.date)) &&
|
||||||
activity.type === TypeOfOrder.DIVIDEND
|
activity.type === TypeOfOrder.DIVIDEND
|
||||||
@ -1431,7 +1430,7 @@ export class PortfolioService {
|
|||||||
}) {
|
}) {
|
||||||
return activities
|
return activities
|
||||||
.filter((activity) => {
|
.filter((activity) => {
|
||||||
// Filter out all activities before given date
|
// Filter out all activities before given date (drafts)
|
||||||
return isBefore(date, new Date(activity.date));
|
return isBefore(date, new Date(activity.date));
|
||||||
})
|
})
|
||||||
.map(({ fee, SymbolProfile }) => {
|
.map(({ fee, SymbolProfile }) => {
|
||||||
@ -1478,19 +1477,37 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getItems(orders: OrderWithAccount[], date = new Date(0)) {
|
private getItems(activities: OrderWithAccount[], date = new Date(0)) {
|
||||||
return orders
|
return activities
|
||||||
.filter((order) => {
|
.filter((activity) => {
|
||||||
// Filter out all orders before given date and type item
|
// Filter out all activities before given date (drafts) and type item
|
||||||
return (
|
return (
|
||||||
isBefore(date, new Date(order.date)) &&
|
isBefore(date, new Date(activity.date)) &&
|
||||||
order.type === TypeOfOrder.ITEM
|
activity.type === TypeOfOrder.ITEM
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((order) => {
|
.map(({ quantity, SymbolProfile, unitPrice }) => {
|
||||||
return this.exchangeRateDataService.toCurrency(
|
return this.exchangeRateDataService.toCurrency(
|
||||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
new Big(quantity).mul(unitPrice).toNumber(),
|
||||||
order.SymbolProfile.currency,
|
SymbolProfile.currency,
|
||||||
|
this.request.user.Settings.settings.baseCurrency
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.reduce(
|
||||||
|
(previous, current) => new Big(previous).plus(current),
|
||||||
|
new Big(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLiabilities(activities: OrderWithAccount[]) {
|
||||||
|
return activities
|
||||||
|
.filter(({ type }) => {
|
||||||
|
return type === TypeOfOrder.LIABILITY;
|
||||||
|
})
|
||||||
|
.map(({ quantity, SymbolProfile, unitPrice }) => {
|
||||||
|
return this.exchangeRateDataService.toCurrency(
|
||||||
|
new Big(quantity).mul(unitPrice).toNumber(),
|
||||||
|
SymbolProfile.currency,
|
||||||
this.request.user.Settings.settings.baseCurrency
|
this.request.user.Settings.settings.baseCurrency
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -1601,6 +1618,7 @@ export class PortfolioService {
|
|||||||
const fees = this.getFees({ activities, userCurrency }).toNumber();
|
const fees = this.getFees({ activities, userCurrency }).toNumber();
|
||||||
const firstOrderDate = activities[0]?.date;
|
const firstOrderDate = activities[0]?.date;
|
||||||
const items = this.getItems(activities).toNumber();
|
const items = this.getItems(activities).toNumber();
|
||||||
|
const liabilities = this.getLiabilities(activities).toNumber();
|
||||||
|
|
||||||
const totalBuy = this.getTotalByType(activities, userCurrency, 'BUY');
|
const totalBuy = this.getTotalByType(activities, userCurrency, 'BUY');
|
||||||
const totalSell = this.getTotalByType(activities, userCurrency, 'SELL');
|
const totalSell = this.getTotalByType(activities, userCurrency, 'SELL');
|
||||||
@ -1633,6 +1651,7 @@ export class PortfolioService {
|
|||||||
.plus(performanceInformation.performance.currentValue)
|
.plus(performanceInformation.performance.currentValue)
|
||||||
.plus(items)
|
.plus(items)
|
||||||
.plus(excludedAccountsAndActivities)
|
.plus(excludedAccountsAndActivities)
|
||||||
|
.minus(liabilities)
|
||||||
.toNumber();
|
.toNumber();
|
||||||
|
|
||||||
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
||||||
@ -1659,6 +1678,7 @@ export class PortfolioService {
|
|||||||
fees,
|
fees,
|
||||||
firstOrderDate,
|
firstOrderDate,
|
||||||
items,
|
items,
|
||||||
|
liabilities,
|
||||||
netWorth,
|
netWorth,
|
||||||
totalBuy,
|
totalBuy,
|
||||||
totalSell,
|
totalSell,
|
||||||
|
@ -194,6 +194,26 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><hr /></div>
|
<div class="col"><hr /></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-nowrap px-3 py-1 row">
|
||||||
|
<div class="flex-grow-1 text-truncate" i18n>Liabilities</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<span
|
||||||
|
*ngIf="summary?.liabilities || summary?.liabilities === 0"
|
||||||
|
class="mr-1"
|
||||||
|
>-</span
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
[isCurrency]="true"
|
||||||
|
[locale]="locale"
|
||||||
|
[unit]="baseCurrency"
|
||||||
|
[value]="isLoading ? undefined : summary?.liabilities"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><hr /></div>
|
||||||
|
</div>
|
||||||
<div class="flex-nowrap px-3 py-1 row">
|
<div class="flex-nowrap px-3 py-1 row">
|
||||||
<div class="flex-grow-1 font-weight-bold text-truncate" i18n>Net Worth</div>
|
<div class="flex-grow-1 font-weight-bold text-truncate" i18n>Net Worth</div>
|
||||||
<div class="justify-content-end">
|
<div class="justify-content-end">
|
||||||
|
@ -300,6 +300,33 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
||||||
this.activityForm.controls['updateAccountBalance'].disable();
|
this.activityForm.controls['updateAccountBalance'].disable();
|
||||||
this.activityForm.controls['updateAccountBalance'].setValue(false);
|
this.activityForm.controls['updateAccountBalance'].setValue(false);
|
||||||
|
} else if (type === 'LIABILITY') {
|
||||||
|
this.activityForm.controls['accountId'].removeValidators(
|
||||||
|
Validators.required
|
||||||
|
);
|
||||||
|
this.activityForm.controls['accountId'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['currency'].setValue(
|
||||||
|
this.data.user.settings.baseCurrency
|
||||||
|
);
|
||||||
|
this.activityForm.controls['currencyOfFee'].setValue(
|
||||||
|
this.data.user.settings.baseCurrency
|
||||||
|
);
|
||||||
|
this.activityForm.controls['currencyOfUnitPrice'].setValue(
|
||||||
|
this.data.user.settings.baseCurrency
|
||||||
|
);
|
||||||
|
this.activityForm.controls['dataSource'].removeValidators(
|
||||||
|
Validators.required
|
||||||
|
);
|
||||||
|
this.activityForm.controls['dataSource'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['name'].setValidators(Validators.required);
|
||||||
|
this.activityForm.controls['name'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['quantity'].setValue(1);
|
||||||
|
this.activityForm.controls['searchSymbol'].removeValidators(
|
||||||
|
Validators.required
|
||||||
|
);
|
||||||
|
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['updateAccountBalance'].disable();
|
||||||
|
this.activityForm.controls['updateAccountBalance'].setValue(false);
|
||||||
} else {
|
} else {
|
||||||
this.activityForm.controls['accountId'].setValidators(
|
this.activityForm.controls['accountId'].setValidators(
|
||||||
Validators.required
|
Validators.required
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<mat-option i18n value="BUY">Buy</mat-option>
|
<mat-option i18n value="BUY">Buy</mat-option>
|
||||||
<mat-option i18n value="DIVIDEND">Dividend</mat-option>
|
<mat-option i18n value="DIVIDEND">Dividend</mat-option>
|
||||||
<mat-option i18n value="ITEM">Item</mat-option>
|
<mat-option i18n value="ITEM">Item</mat-option>
|
||||||
|
<mat-option i18n value="LIABILITY">Liability</mat-option>
|
||||||
<mat-option i18n value="SELL">Sell</mat-option>
|
<mat-option i18n value="SELL">Sell</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -116,7 +117,10 @@
|
|||||||
<mat-datepicker #date disabled="false"></mat-datepicker>
|
<mat-datepicker #date disabled="false"></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div
|
||||||
|
class="mb-3"
|
||||||
|
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
||||||
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Quantity</mat-label>
|
<mat-label i18n>Quantity</mat-label>
|
||||||
<input formControlName="quantity" matInput type="number" />
|
<input formControlName="quantity" matInput type="number" />
|
||||||
@ -130,6 +134,7 @@
|
|||||||
>Dividend</ng-container
|
>Dividend</ng-container
|
||||||
>
|
>
|
||||||
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'LIABILITY'" i18n>Value</ng-container>
|
||||||
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-label>
|
</mat-label>
|
||||||
@ -177,6 +182,7 @@
|
|||||||
>Dividend</ng-container
|
>Dividend</ng-container
|
||||||
>
|
>
|
||||||
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'LIABILITY'" i18n>Value</ng-container>
|
||||||
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-label>
|
</mat-label>
|
||||||
@ -186,7 +192,10 @@
|
|||||||
>
|
>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div
|
||||||
|
class="mb-3"
|
||||||
|
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
||||||
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Fee</mat-label>
|
<mat-label i18n>Fee</mat-label>
|
||||||
<input formControlName="feeInCustomCurrency" matInput type="number" />
|
<input formControlName="feeInCustomCurrency" matInput type="number" />
|
||||||
|
@ -10,6 +10,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
|
|||||||
fees: number;
|
fees: number;
|
||||||
firstOrderDate: Date;
|
firstOrderDate: Date;
|
||||||
items: number;
|
items: number;
|
||||||
|
liabilities: number;
|
||||||
netWorth: number;
|
netWorth: number;
|
||||||
ordersCount: number;
|
ordersCount: number;
|
||||||
totalBuy: number;
|
totalBuy: number;
|
||||||
|
@ -162,6 +162,7 @@
|
|||||||
buy: element.type === 'BUY',
|
buy: element.type === 'BUY',
|
||||||
dividend: element.type === 'DIVIDEND',
|
dividend: element.type === 'DIVIDEND',
|
||||||
item: element.type === 'ITEM',
|
item: element.type === 'ITEM',
|
||||||
|
liability: element.type === 'LIABILITY',
|
||||||
sell: element.type === 'SELL'
|
sell: element.type === 'SELL'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
@ -173,6 +174,10 @@
|
|||||||
*ngIf="element.type === 'ITEM'"
|
*ngIf="element.type === 'ITEM'"
|
||||||
name="cube-outline"
|
name="cube-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="element.type === 'LIABILITY'"
|
||||||
|
name="flame-outline"
|
||||||
|
></ion-icon>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="element.type === 'SELL'"
|
*ngIf="element.type === 'SELL'"
|
||||||
name="arrow-down-circle-outline"
|
name="arrow-down-circle-outline"
|
||||||
@ -538,7 +543,10 @@
|
|||||||
mat-row
|
mat-row
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'cursor-pointer':
|
'cursor-pointer':
|
||||||
hasPermissionToOpenDetails && !row.isDraft && row.type !== 'ITEM'
|
hasPermissionToOpenDetails &&
|
||||||
|
!row.isDraft &&
|
||||||
|
row.type !== 'ITEM' &&
|
||||||
|
row.type !== 'LIABILITY'
|
||||||
}"
|
}"
|
||||||
(click)="onClickActivity(row)"
|
(click)="onClickActivity(row)"
|
||||||
></tr>
|
></tr>
|
||||||
|
@ -37,6 +37,10 @@
|
|||||||
color: var(--purple);
|
color: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.liability {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
&.sell {
|
&.sell {
|
||||||
color: var(--orange);
|
color: var(--orange);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "Type" ADD VALUE 'LIABILITY';
|
@ -238,6 +238,7 @@ enum Type {
|
|||||||
BUY
|
BUY
|
||||||
DIVIDEND
|
DIVIDEND
|
||||||
ITEM
|
ITEM
|
||||||
|
LIABILITY
|
||||||
SELL
|
SELL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user