From 55e4d2fc7a609af03b782687f7d33e511780a2c5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 5 Jul 2025 20:24:04 +0200 Subject: [PATCH] Feature/deprecate ITEM activity type in favor of BUY (#5093) * Deprecate ITEM activity type in favor of BUY * Update changelog --- CHANGELOG.md | 1 + README.md | 25 +++++++------- apps/api/src/app/export/export.service.ts | 8 +++-- apps/api/src/app/import/import.service.ts | 14 ++++++-- apps/api/src/app/order/order.controller.ts | 16 ++++++--- apps/api/src/app/order/order.service.ts | 31 +++++++++-------- .../calculator/portfolio-calculator.ts | 30 ++--------------- ...aln-buy-and-sell-in-two-activities.spec.ts | 3 +- ...folio-calculator-baln-buy-and-sell.spec.ts | 3 +- .../portfolio-calculator-baln-buy.spec.ts | 3 +- .../roai/portfolio-calculator-btceur.spec.ts | 3 +- ...ator-btcusd-buy-and-sell-partially.spec.ts | 3 +- .../roai/portfolio-calculator-btcusd.spec.ts | 3 +- .../roai/portfolio-calculator-fee.spec.ts | 3 +- .../portfolio-calculator-googl-buy.spec.ts | 3 +- ...-calculator-msft-buy-with-dividend.spec.ts | 3 +- .../portfolio-calculator-no-orders.spec.ts | 3 +- ...ulator-novn-buy-and-sell-partially.spec.ts | 3 +- ...folio-calculator-novn-buy-and-sell.spec.ts | 3 +- ... => portfolio-calculator-valuable.spec.ts} | 33 +++++++++++-------- .../calculator/roai/portfolio-calculator.ts | 22 ++----------- .../interfaces/transaction-point.interface.ts | 1 - .../src/app/portfolio/portfolio.service.ts | 4 --- .../portfolio-summary.component.html | 12 ------- ...ate-or-update-activity-dialog.component.ts | 29 ++++++++++------ .../app/services/import-activities.service.ts | 2 -- .../interfaces/portfolio-summary.interface.ts | 1 - .../interfaces/symbol-metrics.interface.ts | 2 -- .../src/lib/models/portfolio-snapshot.ts | 4 --- .../activity-type.component.html | 3 -- .../activity-type.component.scss | 4 --- .../migration.sql | 2 ++ .../not-ok/invalid-type-deprecated.json | 20 +++++++++++ test/import/ok/sample.csv | 2 +- test/import/ok/sample.json | 2 +- test/import/ok/without-accounts.json | 2 +- 36 files changed, 140 insertions(+), 166 deletions(-) rename apps/api/src/app/portfolio/calculator/roai/{portfolio-calculator-item.spec.ts => portfolio-calculator-valuable.spec.ts} (87%) create mode 100644 prisma/migrations/20250704214021_changed_type_from_item_to_buy_in_order/migration.sql create mode 100644 test/import/not-ok/invalid-type-deprecated.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e41d11625..efb9c396c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Increased the width of the markets overview - Increased the width of the watchlist +- Deprecated the `ITEM` activity type in favor of `BUY` - Renamed `Access` to `accessesGet` in the `User` database schema - Improved the language localization for Dutch (`nl`) - Improved the language localization for Italian (`it`) diff --git a/README.md b/README.md index d2f5394ff..f2e9de4f1 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,6 @@ docker compose -f docker/docker-compose.build.yml up -d #### Upgrade Version 1. Update the _Ghostfolio_ Docker image - - Increase the version of the `ghostfolio/ghostfolio` Docker image in `docker/docker-compose.yml` - Run the following command if `ghostfolio:latest` is set: ```bash @@ -222,18 +221,18 @@ Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/ { return tagId; }) diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index a526bf86c..c48df08d0 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -232,7 +232,7 @@ export class ImportService { for (const activity of activitiesDto) { if (!activity.dataSource) { - if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(activity.type)) { + if (['FEE', 'INTEREST', 'LIABILITY'].includes(activity.type)) { activity.dataSource = DataSource.MANUAL; } else { activity.dataSource = @@ -564,6 +564,12 @@ export class ImportService { index, { currency, dataSource, symbol, type } ] of activitiesDto.entries()) { + if (type === 'ITEM') { + throw new Error( + `activities.${index}.type ("${type}") is deprecated, please use "BUY" instead` + ); + } + if (!dataSources.includes(dataSource)) { throw new Error( `activities.${index}.dataSource ("${dataSource}") is not valid` @@ -595,7 +601,11 @@ export class ImportService { )?.[symbol] }; - if (type === 'BUY' || type === 'DIVIDEND' || type === 'SELL') { + if ( + (dataSource !== 'MANUAL' && type === 'BUY') || + type === 'DIVIDEND' || + type === 'SELL' + ) { if (!assetProfile?.name) { throw new Error( `activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")` diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 33403e85d..b30920e90 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -189,6 +189,7 @@ export class OrderController { public async createOrder(@Body() data: CreateOrderDto): Promise { const currency = data.currency; const customCurrency = data.customCurrency; + const dataSource = data.dataSource; if (customCurrency) { data.currency = customCurrency; @@ -196,6 +197,8 @@ export class OrderController { delete data.customCurrency; } + delete data.dataSource; + const order = await this.orderService.createOrder({ ...data, date: parseISO(data.date), @@ -203,12 +206,12 @@ export class OrderController { connectOrCreate: { create: { currency, - dataSource: data.dataSource, + dataSource, symbol: data.symbol }, where: { dataSource_symbol: { - dataSource: data.dataSource, + dataSource, symbol: data.symbol } } @@ -218,13 +221,13 @@ export class OrderController { userId: this.request.user.id }); - if (data.dataSource && !order.isDraft) { + if (dataSource && !order.isDraft) { // Gather symbol data in the background, if data source is set // (not MANUAL) and not draft this.dataGatheringService.gatherSymbols({ dataGatheringItems: [ { - dataSource: data.dataSource, + dataSource, date: order.date, symbol: data.symbol } @@ -256,6 +259,7 @@ export class OrderController { const accountId = data.accountId; const customCurrency = data.customCurrency; + const dataSource = data.dataSource; delete data.accountId; @@ -265,6 +269,8 @@ export class OrderController { delete data.customCurrency; } + delete data.dataSource; + return this.orderService.updateOrder({ data: { ...data, @@ -277,7 +283,7 @@ export class OrderController { SymbolProfile: { connect: { dataSource_symbol: { - dataSource: data.dataSource, + dataSource, symbol: data.symbol } }, diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index c26d53b8f..9c96af865 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -93,7 +93,6 @@ export class OrderService { assetClass?: AssetClass; assetSubClass?: AssetSubClass; currency?: string; - dataSource?: DataSource; symbol?: string; tags?: Tag[]; updateAccountBalance?: boolean; @@ -118,7 +117,11 @@ export class OrderService { const updateAccountBalance = data.updateAccountBalance ?? false; const userId = data.userId; - if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(data.type)) { + if ( + ['FEE', 'INTEREST', 'LIABILITY'].includes(data.type) || + (data.SymbolProfile.connectOrCreate.create.dataSource === 'MANUAL' && + data.type === 'BUY') + ) { const assetClass = data.assetClass; const assetSubClass = data.assetSubClass; const dataSource: DataSource = 'MANUAL'; @@ -164,7 +167,6 @@ export class OrderService { delete data.comment; } - delete data.dataSource; delete data.symbol; delete data.tags; delete data.updateAccountBalance; @@ -172,7 +174,7 @@ export class OrderService { const orderData: Prisma.OrderCreateInput = data; - const isDraft = ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(data.type) + const isDraft = ['FEE', 'INTEREST', 'LIABILITY'].includes(data.type) ? false : isAfter(data.date as Date, endOfToday()); @@ -631,7 +633,6 @@ export class OrderService { assetClass?: AssetClass; assetSubClass?: AssetSubClass; currency?: string; - dataSource?: DataSource; symbol?: string; tags?: Tag[]; type?: ActivityType; @@ -646,12 +647,17 @@ export class OrderService { let isDraft = false; - if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(data.type)) { - delete data.SymbolProfile.connect; - + if ( + ['FEE', 'INTEREST', 'LIABILITY'].includes(data.type) || + (data.SymbolProfile.connect.dataSource_symbol.dataSource === 'MANUAL' && + data.type === 'BUY') + ) { if (data.account?.connect?.id_userId?.id === null) { data.account = { disconnect: true }; } + + delete data.SymbolProfile.connect; + delete data.SymbolProfile.update.name; } else { delete data.SymbolProfile.update; @@ -675,17 +681,17 @@ export class OrderService { delete data.assetClass; delete data.assetSubClass; - delete data.dataSource; delete data.symbol; delete data.tags; // Remove existing tags await this.prismaService.order.update({ - data: { tags: { set: [] } }, - where + where, + data: { tags: { set: [] } } }); const order = await this.prismaService.order.update({ + where, data: { ...data, isDraft, @@ -694,8 +700,7 @@ export class OrderService { return { id }; }) } - }, - where + } }); this.eventEmitter.emit( diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 99aeb42f8..ac7e02027 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -187,8 +187,7 @@ export abstract class PortfolioCalculator { totalInterestWithCurrencyEffect: new Big(0), totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0), - totalLiabilitiesWithCurrencyEffect: new Big(0), - totalValuablesWithCurrencyEffect: new Big(0) + totalLiabilitiesWithCurrencyEffect: new Big(0) }; } @@ -198,7 +197,6 @@ export abstract class PortfolioCalculator { let firstTransactionPoint: TransactionPoint = null; let totalInterestWithCurrencyEffect = new Big(0); let totalLiabilitiesWithCurrencyEffect = new Big(0); - let totalValuablesWithCurrencyEffect = new Big(0); for (const { currency, dataSource, symbol } of transactionPoints[ firstIndex - 1 @@ -364,8 +362,7 @@ export abstract class PortfolioCalculator { totalInterestInBaseCurrency, totalInvestment, totalInvestmentWithCurrencyEffect, - totalLiabilitiesInBaseCurrency, - totalValuablesInBaseCurrency + totalLiabilitiesInBaseCurrency } = this.getSymbolMetrics({ chartDateMap, marketSymbolMap, @@ -444,10 +441,6 @@ export abstract class PortfolioCalculator { totalLiabilitiesWithCurrencyEffect = totalLiabilitiesWithCurrencyEffect.plus(totalLiabilitiesInBaseCurrency); - totalValuablesWithCurrencyEffect = totalValuablesWithCurrencyEffect.plus( - totalValuablesInBaseCurrency - ); - if ( (hasErrors || currentRateErrors.find(({ dataSource, symbol }) => { @@ -597,7 +590,6 @@ export abstract class PortfolioCalculator { netPerformance: totalNetPerformanceValue.toNumber(), netPerformanceWithCurrencyEffect: totalNetPerformanceValueWithCurrencyEffect.toNumber(), - // TODO: Add valuables netWorth: totalCurrentValueWithCurrencyEffect .plus(totalAccountBalanceWithCurrencyEffect) .toNumber(), @@ -619,7 +611,6 @@ export abstract class PortfolioCalculator { positions, totalInterestWithCurrencyEffect, totalLiabilitiesWithCurrencyEffect, - totalValuablesWithCurrencyEffect, hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors }; } @@ -754,7 +745,7 @@ export abstract class PortfolioCalculator { ? 0 : netPerformanceWithCurrencyEffectSinceStartDate / timeWeightedInvestmentValue - // TODO: Add net worth with valuables + // TODO: Add net worth // netWorth: totalCurrentValueWithCurrencyEffect // .plus(totalAccountBalanceWithCurrencyEffect) // .toNumber() @@ -819,12 +810,6 @@ export abstract class PortfolioCalculator { return this.transactionPoints; } - public async getValuablesInBaseCurrency() { - await this.snapshotPromise; - - return this.snapshot.totalValuablesWithCurrencyEffect; - } - private getChartDateMap({ endDate, startDate, @@ -1000,19 +985,12 @@ export abstract class PortfolioCalculator { liabilities = quantity.mul(unitPrice); } - let valuables = new Big(0); - - if (type === 'ITEM') { - valuables = quantity.mul(unitPrice); - } - if (lastDate !== date || lastTransactionPoint === null) { lastTransactionPoint = { date, fees, interest, liabilities, - valuables, items: newItems }; @@ -1024,8 +1002,6 @@ export abstract class PortfolioCalculator { lastTransactionPoint.items = newItems; lastTransactionPoint.liabilities = lastTransactionPoint.liabilities.plus(liabilities); - lastTransactionPoint.valuables = - lastTransactionPoint.valuables.plus(valuables); } lastDate = date; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 589c2dcee..ab8000702 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -194,8 +194,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('0'), totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index 033f2622d..cc65fa8a2 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -179,8 +179,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('0'), totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index e7f95aea8..7d9544666 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -170,8 +170,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('273.2'), totalInvestmentWithCurrencyEffect: new Big('273.2'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 5c633817e..3594b4427 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -224,8 +224,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('44558.42'), totalInvestmentWithCurrencyEffect: new Big('44558.42'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(investments).toEqual([ diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index acd07344e..de3f5d3cd 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -198,8 +198,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('320.43').mul(0.97373), totalInvestmentWithCurrencyEffect: new Big('318.542667299999967957'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index 4e84ba05c..d1d9fbd93 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -224,8 +224,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('44558.42'), totalInvestmentWithCurrencyEffect: new Big('44558.42'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(investments).toEqual([ diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts index a8aea0841..31c6afe66 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts @@ -151,8 +151,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('0'), totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index 18e8dd5df..f7d0e9e6d 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -177,8 +177,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('89.12').mul(0.8854), totalInvestmentWithCurrencyEffect: new Big('82.329056'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index 4c1962c1b..36b6e8be9 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -170,8 +170,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('298.58'), totalInvestmentWithCurrencyEffect: new Big('298.58'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts index 9d5b74bb2..f64328d39 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts @@ -105,8 +105,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(investments).toEqual([]); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 2d66695a2..96bbbc757 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -177,8 +177,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('75.80'), totalInvestmentWithCurrencyEffect: new Big('75.80'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index 1a090496a..c19f6917e 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -228,8 +228,7 @@ describe('PortfolioCalculator', () => { totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('0'), totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts similarity index 87% rename from apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts rename to apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 21d208d9f..f883e67a2 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -82,7 +82,7 @@ describe('PortfolioCalculator', () => { }); describe('compute portfolio snapshot', () => { - it.only('with item activity', async () => { + it.only('with valuable activity', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-01-31').getTime()); const activities: Activity[] = [ @@ -98,7 +98,7 @@ describe('PortfolioCalculator', () => { name: 'Penthouse Apartment', symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde' }, - type: 'ITEM', + type: 'BUY', unitPriceInAssetProfileCurrency: 500000 } ]; @@ -113,9 +113,15 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); expect(portfolioSnapshot).toMatchObject({ - currentValueInBaseCurrency: new Big('0'), - errors: [], - hasErrors: true, + currentValueInBaseCurrency: new Big('500000'), + // TODO: [] + errors: [ + { + dataSource: 'MANUAL', + symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde' + } + ], + hasErrors: true, // TODO: false positions: [ { averagePrice: new Big('500000'), @@ -130,29 +136,28 @@ describe('PortfolioCalculator', () => { grossPerformancePercentage: null, grossPerformancePercentageWithCurrencyEffect: null, grossPerformanceWithCurrencyEffect: null, - investment: new Big('0'), - investmentWithCurrencyEffect: new Big('0'), + investment: new Big('0'), // TODO: new Big('500000') + investmentWithCurrencyEffect: new Big('0'), // TODO: new Big('500000') marketPrice: null, marketPriceInBaseCurrency: 500000, netPerformance: null, netPerformancePercentage: null, netPerformancePercentageWithCurrencyEffectMap: null, netPerformanceWithCurrencyEffectMap: null, - quantity: new Big('0'), + quantity: new Big('1'), symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde', tags: [], timeWeightedInvestment: new Big('0'), timeWeightedInvestmentWithCurrencyEffect: new Big('0'), transactionCount: 1, - valueInBaseCurrency: new Big('0') + valueInBaseCurrency: new Big('500000') } ], totalFeesWithCurrencyEffect: new Big('0'), totalInterestWithCurrencyEffect: new Big('0'), - totalInvestment: new Big('0'), - totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0'), - totalValuablesWithCurrencyEffect: new Big('0') + totalInvestment: new Big('0'), // TODO: new Big('500000') + totalInvestmentWithCurrencyEffect: new Big('0'), // TODO: new Big('500000') + totalLiabilitiesWithCurrencyEffect: new Big('0') }); expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( @@ -161,7 +166,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0, netPerformanceInPercentageWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0, - totalInvestmentValueWithCurrencyEffect: 0 + totalInvestmentValueWithCurrencyEffect: 0 // TODO: 500000 }) ); }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts index d9da465f9..d697c7c69 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -108,8 +108,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { createdAt: new Date(), errors: [], historicalData: [], - totalLiabilitiesWithCurrencyEffect: new Big(0), - totalValuablesWithCurrencyEffect: new Big(0) + totalLiabilitiesWithCurrencyEffect: new Big(0) }; } @@ -179,8 +178,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { let totalLiabilitiesInBaseCurrency = new Big(0); let totalQuantityFromBuyTransactions = new Big(0); let totalUnits = new Big(0); - let totalValuables = new Big(0); - let totalValuablesInBaseCurrency = new Big(0); let valueAtStartDate: Big; let valueAtStartDateWithCurrencyEffect: Big; @@ -224,9 +221,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0), totalLiabilities: new Big(0), - totalLiabilitiesInBaseCurrency: new Big(0), - totalValuables: new Big(0), - totalValuablesInBaseCurrency: new Big(0) + totalLiabilitiesInBaseCurrency: new Big(0) }; } @@ -274,9 +269,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0), totalLiabilities: new Big(0), - totalLiabilitiesInBaseCurrency: new Big(0), - totalValuables: new Big(0), - totalValuablesInBaseCurrency: new Big(0) + totalLiabilitiesInBaseCurrency: new Big(0) }; } @@ -412,13 +405,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { totalInterestInBaseCurrency = totalInterestInBaseCurrency.plus( interest.mul(exchangeRateAtOrderDate ?? 1) ); - } else if (order.type === 'ITEM') { - const valuables = order.quantity.mul(order.unitPrice); - - totalValuables = totalValuables.plus(valuables); - totalValuablesInBaseCurrency = totalValuablesInBaseCurrency.plus( - valuables.mul(exchangeRateAtOrderDate ?? 1) - ); } else if (order.type === 'LIABILITY') { const liabilities = order.quantity.mul(order.unitPrice); @@ -971,8 +957,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { totalInvestmentWithCurrencyEffect, totalLiabilities, totalLiabilitiesInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, grossPerformance: totalGrossPerformance, grossPerformanceWithCurrencyEffect: totalGrossPerformanceWithCurrencyEffect, diff --git a/apps/api/src/app/portfolio/interfaces/transaction-point.interface.ts b/apps/api/src/app/portfolio/interfaces/transaction-point.interface.ts index fcbea81ca..698b202e4 100644 --- a/apps/api/src/app/portfolio/interfaces/transaction-point.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/transaction-point.interface.ts @@ -8,5 +8,4 @@ export interface TransactionPoint { interest: Big; items: TransactionPointSymbol[]; liabilities: Big; - valuables: Big; } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b0739f5f9..c64995fe0 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1825,8 +1825,6 @@ export class PortfolioService { const liabilities = await portfolioCalculator.getLiabilitiesInBaseCurrency(); - const valuables = await portfolioCalculator.getValuablesInBaseCurrency(); - const totalBuy = this.getSumOfActivityType({ userCurrency, activities: nonExcludedActivities, @@ -1875,7 +1873,6 @@ export class PortfolioService { const netWorth = new Big(balanceInBaseCurrency) .plus(currentValueInBaseCurrency) - .plus(valuables) .plus(excludedAccountsAndActivities) .minus(liabilities) .toNumber(); @@ -1934,7 +1931,6 @@ export class PortfolioService { .plus(fees) .toNumber(), interest: interest.toNumber(), - items: valuables.toNumber(), liabilities: liabilities.toNumber(), totalInvestment: totalInvestment.toNumber(), totalValueInBaseCurrency: netWorth 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 265904b88..19f125523 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 @@ -152,18 +152,6 @@ /> -
-
Valuables
-
- -
-
Emergency Fund
{ if ( - this.activityForm.get('type').value === 'BUY' || - this.activityForm.get('type').value === 'FEE' || - this.activityForm.get('type').value === 'ITEM' + ['BUY', 'FEE', 'ITEM'].includes(this.activityForm.get('type').value) ) { this.total = this.activityForm.get('quantity').value * @@ -215,12 +213,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.activityForm.get('accountId').valueChanges.subscribe((accountId) => { const type = this.activityForm.get('type').value; - if ( - type === 'FEE' || - type === 'INTEREST' || - type === 'ITEM' || - type === 'LIABILITY' - ) { + if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(type)) { const currency = this.data.accounts.find(({ id }) => { return id === accountId; @@ -297,7 +290,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { .get('type') .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) .subscribe((type: Type) => { - if (type === 'ITEM') { + if ( + type === 'ITEM' || + (this.activityForm.get('dataSource').value === 'MANUAL' && + type === 'BUY') + ) { this.activityForm .get('accountId') .removeValidators(Validators.required); @@ -472,6 +469,12 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { object: activity }); + if (activity.type === 'ITEM') { + // Transform deprecated type ITEM + activity.dataSource = 'MANUAL'; + activity.type = 'BUY'; + } + this.dialogRef.close(activity); } else { (activity as UpdateOrderDto).id = this.data.activity?.id; @@ -483,6 +486,12 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { object: activity as UpdateOrderDto }); + if (activity.type === 'ITEM') { + // Transform deprecated type ITEM + activity.dataSource = 'MANUAL'; + activity.type = 'BUY'; + } + this.dialogRef.close(activity as UpdateOrderDto); } } catch (error) { diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 2164bd248..a4ffa7ec4 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -343,8 +343,6 @@ export class ImportActivitiesService { return 'FEE'; case 'interest': return 'INTEREST'; - case 'item': - return 'ITEM'; case 'liability': return 'LIABILITY'; case 'sell': diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 4030b9001..419915a79 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -20,7 +20,6 @@ export interface PortfolioSummary extends PortfolioPerformance { grossPerformance: number; grossPerformanceWithCurrencyEffect: number; interest: number; - items: number; liabilities: number; totalBuy: number; totalSell: number; diff --git a/libs/common/src/lib/interfaces/symbol-metrics.interface.ts b/libs/common/src/lib/interfaces/symbol-metrics.interface.ts index 24c1e1db4..c2b70a4bc 100644 --- a/libs/common/src/lib/interfaces/symbol-metrics.interface.ts +++ b/libs/common/src/lib/interfaces/symbol-metrics.interface.ts @@ -51,6 +51,4 @@ export interface SymbolMetrics { totalInvestmentWithCurrencyEffect: Big; totalLiabilities: Big; totalLiabilitiesInBaseCurrency: Big; - totalValuables: Big; - totalValuablesInBaseCurrency: Big; } diff --git a/libs/common/src/lib/models/portfolio-snapshot.ts b/libs/common/src/lib/models/portfolio-snapshot.ts index e30585b82..6b13ca048 100644 --- a/libs/common/src/lib/models/portfolio-snapshot.ts +++ b/libs/common/src/lib/models/portfolio-snapshot.ts @@ -45,8 +45,4 @@ export class PortfolioSnapshot { @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) totalLiabilitiesWithCurrencyEffect: Big; - - @Transform(transformToBig, { toClassOnly: true }) - @Type(() => Big) - totalValuablesWithCurrencyEffect: Big; } diff --git a/libs/ui/src/lib/activity-type/activity-type.component.html b/libs/ui/src/lib/activity-type/activity-type.component.html index 78eb2f17b..fe5ecfa01 100644 --- a/libs/ui/src/lib/activity-type/activity-type.component.html +++ b/libs/ui/src/lib/activity-type/activity-type.component.html @@ -5,7 +5,6 @@ dividend: activityType === 'DIVIDEND', fee: activityType === 'FEE', interest: activityType === 'INTEREST', - item: activityType === 'ITEM', liability: activityType === 'LIABILITY', sell: activityType === 'SELL' }" @@ -16,8 +15,6 @@ } @else if (activityType === 'FEE') { - } @else if (activityType === 'ITEM') { - } @else if (activityType === 'LIABILITY') { } @else if (activityType === 'SELL') { diff --git a/libs/ui/src/lib/activity-type/activity-type.component.scss b/libs/ui/src/lib/activity-type/activity-type.component.scss index 49889b665..34b951805 100644 --- a/libs/ui/src/lib/activity-type/activity-type.component.scss +++ b/libs/ui/src/lib/activity-type/activity-type.component.scss @@ -26,10 +26,6 @@ color: var(--cyan); } - &.item { - color: var(--purple); - } - &.liability { color: var(--red); } diff --git a/prisma/migrations/20250704214021_changed_type_from_item_to_buy_in_order/migration.sql b/prisma/migrations/20250704214021_changed_type_from_item_to_buy_in_order/migration.sql new file mode 100644 index 000000000..4831beae1 --- /dev/null +++ b/prisma/migrations/20250704214021_changed_type_from_item_to_buy_in_order/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +UPDATE "Order" SET "type" = 'BUY' WHERE "type" = 'ITEM'; diff --git a/test/import/not-ok/invalid-type-deprecated.json b/test/import/not-ok/invalid-type-deprecated.json new file mode 100644 index 000000000..77572df85 --- /dev/null +++ b/test/import/not-ok/invalid-type-deprecated.json @@ -0,0 +1,20 @@ +{ + "meta": { + "date": "2023-02-05T00:00:00.000Z", + "version": "dev" + }, + "activities": [ + { + "accountId": null, + "comment": null, + "fee": 0, + "quantity": 1, + "type": "ITEM", + "unitPrice": 500000, + "currency": "USD", + "dataSource": "MANUAL", + "date": "2022-01-01T00:00:00.000Z", + "symbol": "Penthouse Apartment" + } + ] +} diff --git a/test/import/ok/sample.csv b/test/import/ok/sample.csv index 9f334da15..a22a4b89e 100644 --- a/test/import/ok/sample.csv +++ b/test/import/ok/sample.csv @@ -2,5 +2,5 @@ Date,Code,DataSource,Currency,Price,Quantity,Action,Fee,Note 01-09-2021,Account Opening Fee,MANUAL,USD,0,0,fee,49, 16-09-2021,MSFT,YAHOO,USD,298.580,5,buy,19.00,My first order 🤓 17/11/2021,MSFT,YAHOO,USD,0.62,5,dividend,0.00, -01.01.2022,Penthouse Apartment,MANUAL,USD,500000.0,1,item,0.00, +01.01.2022,Penthouse Apartment,MANUAL,USD,500000.0,1,buy,0.00, 20500606,US5949181045,YAHOO,USD,0.00,0,buy,0.00, diff --git a/test/import/ok/sample.json b/test/import/ok/sample.json index d3e05e610..01bcc60d9 100644 --- a/test/import/ok/sample.json +++ b/test/import/ok/sample.json @@ -41,7 +41,7 @@ "comment": null, "fee": 0, "quantity": 1, - "type": "ITEM", + "type": "BUY", "unitPrice": 500000, "currency": "USD", "dataSource": "MANUAL", diff --git a/test/import/ok/without-accounts.json b/test/import/ok/without-accounts.json index 2ba0925b1..3a5320741 100644 --- a/test/import/ok/without-accounts.json +++ b/test/import/ok/without-accounts.json @@ -17,7 +17,7 @@ { "fee": 0, "quantity": 1, - "type": "ITEM", + "type": "BUY", "unitPrice": 500000, "currency": "USD", "dataSource": "MANUAL",