diff --git a/CHANGELOG.md b/CHANGELOG.md index bb550c2fd..1311c54ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/), 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 ### Added diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 696f5442e..cc9718e7d 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -96,7 +96,7 @@ export class OrderService { const updateAccountBalance = data.updateAccountBalance ?? false; const userId = data.userId; - if (data.type === 'ITEM') { + if (data.type === 'ITEM' || data.type === 'LIABILITY') { const assetClass = data.assetClass; const assetSubClass = data.assetSubClass; 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) { // Gather symbol data of order in the background, if not draft @@ -320,7 +323,11 @@ export class OrderService { }) ) .filter((order) => { - return withExcludedAccounts || order.Account?.isExcluded === false; + return ( + withExcludedAccounts || + !order.Account || + order.Account?.isExcluded === false + ); }) .map((order) => { const value = new Big(order.quantity).mul(order.unitPrice).toNumber(); diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 1747936fd..06f841e12 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -162,6 +162,7 @@ export class PortfolioController { 'excludedAccountsAndActivities', 'fees', 'items', + 'liabilities', 'netWorth', 'totalBuy', 'totalSell' diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index aea522f40..66f3841a4 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1302,12 +1302,11 @@ export class PortfolioService { }: { activities: OrderWithAccount[]; date?: Date; - userCurrency: string; }) { return activities .filter((activity) => { - // Filter out all activities before given date and type dividend + // Filter out all activities before given date (drafts) and type dividend return ( isBefore(date, new Date(activity.date)) && activity.type === TypeOfOrder.DIVIDEND @@ -1431,7 +1430,7 @@ export class PortfolioService { }) { return activities .filter((activity) => { - // Filter out all activities before given date + // Filter out all activities before given date (drafts) return isBefore(date, new Date(activity.date)); }) .map(({ fee, SymbolProfile }) => { @@ -1478,19 +1477,37 @@ export class PortfolioService { }; } - private getItems(orders: OrderWithAccount[], date = new Date(0)) { - return orders - .filter((order) => { - // Filter out all orders before given date and type item + private getItems(activities: OrderWithAccount[], date = new Date(0)) { + return activities + .filter((activity) => { + // Filter out all activities before given date (drafts) and type item return ( - isBefore(date, new Date(order.date)) && - order.type === TypeOfOrder.ITEM + isBefore(date, new Date(activity.date)) && + activity.type === TypeOfOrder.ITEM ); }) - .map((order) => { + .map(({ quantity, SymbolProfile, unitPrice }) => { return this.exchangeRateDataService.toCurrency( - new Big(order.quantity).mul(order.unitPrice).toNumber(), - order.SymbolProfile.currency, + new Big(quantity).mul(unitPrice).toNumber(), + 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 ); }) @@ -1601,6 +1618,7 @@ export class PortfolioService { const fees = this.getFees({ activities, userCurrency }).toNumber(); const firstOrderDate = activities[0]?.date; const items = this.getItems(activities).toNumber(); + const liabilities = this.getLiabilities(activities).toNumber(); const totalBuy = this.getTotalByType(activities, userCurrency, 'BUY'); const totalSell = this.getTotalByType(activities, userCurrency, 'SELL'); @@ -1633,6 +1651,7 @@ export class PortfolioService { .plus(performanceInformation.performance.currentValue) .plus(items) .plus(excludedAccountsAndActivities) + .minus(liabilities) .toNumber(); const daysInMarket = differenceInDays(new Date(), firstOrderDate); @@ -1659,6 +1678,7 @@ export class PortfolioService { fees, firstOrderDate, items, + liabilities, netWorth, totalBuy, totalSell, diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html index 8b507f553..3ceadb048 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html @@ -194,6 +194,26 @@

+
+
Liabilities
+
+ - + +
+
+
+

+
Net Worth
diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 4f7b8aa92..d9fdb3fe4 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -300,6 +300,33 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.activityForm.controls['searchSymbol'].updateValueAndValidity(); this.activityForm.controls['updateAccountBalance'].disable(); 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 { this.activityForm.controls['accountId'].setValidators( Validators.required diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 33a2f4c73..85511f012 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -14,6 +14,7 @@ Buy Dividend Item + Liability Sell @@ -116,7 +117,10 @@
-
+
Quantity @@ -130,6 +134,7 @@ >Dividend Value + Value Unit Price @@ -177,6 +182,7 @@ >Dividend Value + Value Unit Price @@ -186,7 +192,10 @@ >
-
+
Fee diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 97a50cfed..c520da9fb 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -10,6 +10,7 @@ export interface PortfolioSummary extends PortfolioPerformance { fees: number; firstOrderDate: Date; items: number; + liabilities: number; netWorth: number; ordersCount: number; totalBuy: number; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 36d5e0c8f..5fa80124e 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -162,6 +162,7 @@ buy: element.type === 'BUY', dividend: element.type === 'DIVIDEND', item: element.type === 'ITEM', + liability: element.type === 'LIABILITY', sell: element.type === 'SELL' }" > @@ -173,6 +174,10 @@ *ngIf="element.type === 'ITEM'" name="cube-outline" > + diff --git a/libs/ui/src/lib/activities-table/activities-table.component.scss b/libs/ui/src/lib/activities-table/activities-table.component.scss index 270dfaf66..c74cc50ff 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.scss +++ b/libs/ui/src/lib/activities-table/activities-table.component.scss @@ -37,6 +37,10 @@ color: var(--purple); } + &.liability { + color: var(--red); + } + &.sell { color: var(--orange); } diff --git a/prisma/migrations/20230614345544_added_liability_to_order_type/migration.sql b/prisma/migrations/20230614345544_added_liability_to_order_type/migration.sql new file mode 100644 index 000000000..67daa4dc2 --- /dev/null +++ b/prisma/migrations/20230614345544_added_liability_to_order_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "Type" ADD VALUE 'LIABILITY'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2957ce7e5..712e71ca9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -238,6 +238,7 @@ enum Type { BUY DIVIDEND ITEM + LIABILITY SELL }