Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 12m28s

This commit is contained in:
2025-07-05 16:20:04 -07:00
39 changed files with 156 additions and 181 deletions

View File

@@ -5,13 +5,15 @@ 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
## 2.178.0 - 2025-07-05
### Changed
- 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`)
- Upgraded `prisma` from version `6.10.1` to `6.11.1`

View File

@@ -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/<INSERT_SECURITY_TO
}
```
| Field | Type | Description |
| ------------ | ------------------- | ----------------------------------------------------------------------------- |
| `accountId` | `string` (optional) | Id of the account |
| `comment` | `string` (optional) | Comment of the activity |
| `currency` | `string` | `CHF` \| `EUR` \| `USD` etc. |
| `dataSource` | `string` | `COINGECKO` \| `MANUAL` (for type `ITEM`) \| `YAHOO` |
| `date` | `string` | Date in the format `ISO-8601` |
| `fee` | `number` | Fee of the activity |
| `quantity` | `number` | Quantity of the activity |
| `symbol` | `string` | Symbol of the activity (suitable for `dataSource`) |
| `type` | `string` | `BUY` \| `DIVIDEND` \| `FEE` \| `INTEREST` \| `ITEM` \| `LIABILITY` \| `SELL` |
| `unitPrice` | `number` | Price per unit of the activity |
| Field | Type | Description |
| ------------ | ------------------- | ------------------------------------------------------------------- |
| `accountId` | `string` (optional) | Id of the account |
| `comment` | `string` (optional) | Comment of the activity |
| `currency` | `string` | `CHF` \| `EUR` \| `USD` etc. |
| `dataSource` | `string` | `COINGECKO` \| `MANUAL` \| `YAHOO` |
| `date` | `string` | Date in the format `ISO-8601` |
| `fee` | `number` | Fee of the activity |
| `quantity` | `number` | Quantity of the activity |
| `symbol` | `string` | Symbol of the activity (suitable for `dataSource`) |
| `type` | `string` | `BUY` \| `DIVIDEND` \| `FEE` \| `INTEREST` \| `LIABILITY` \| `SELL` |
| `unitPrice` | `number` | Price per unit of the activity |
#### Response

View File

@@ -141,9 +141,11 @@ export class ExportService {
currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toISOString(),
symbol: ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(type)
? SymbolProfile.name
: SymbolProfile.symbol,
symbol:
['FEE', 'INTEREST', 'LIABILITY'].includes(type) ||
(SymbolProfile.dataSource === 'MANUAL' && type === 'BUY')
? SymbolProfile.name
: SymbolProfile.symbol,
tags: currentTags.map(({ id: tagId }) => {
return tagId;
})

View File

@@ -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}")`

View File

@@ -189,6 +189,7 @@ export class OrderController {
public async createOrder(@Body() data: CreateOrderDto): Promise<OrderModel> {
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
}
},

View File

@@ -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(

View File

@@ -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;

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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([

View File

@@ -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(

View File

@@ -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([

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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([]);

View File

@@ -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(

View File

@@ -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(

View File

@@ -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
})
);
});

View File

@@ -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,

View File

@@ -8,5 +8,4 @@ export interface TransactionPoint {
interest: Big;
items: TransactionPointSymbol[];
liabilities: Big;
valuables: Big;
}

View File

@@ -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

View File

@@ -152,18 +152,6 @@
/>
</div>
</div>
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Valuables</div>
<div class="justify-content-end">
<gf-value
class="justify-content-end"
[isCurrency]="true"
[locale]="locale"
[unit]="baseCurrency"
[value]="isLoading ? undefined : summary?.items"
/>
</div>
</div>
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Emergency Fund</div>
<div

View File

@@ -194,9 +194,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
)
.subscribe(async () => {
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) {

View File

@@ -343,8 +343,6 @@ export class ImportActivitiesService {
return 'FEE';
case 'interest':
return 'INTEREST';
case 'item':
return 'ITEM';
case 'liability':
return 'LIABILITY';
case 'sell':

View File

@@ -7762,7 +7762,7 @@
</trans-unit>
<trans-unit id="routes.resources.personalFinanceTools" datatype="html">
<source>personal-finance-tools</source>
<target state="new">personal-finance-tools</target>
<target state="translated">hulpmiddelen-voor-persoonlijke-financien</target>
<note priority="1" from="description">kebab-case</note>
<context-group purpose="location">
<context context-type="sourcefile">libs/common/src/lib/routes/routes.ts</context>
@@ -7792,7 +7792,7 @@
</trans-unit>
<trans-unit id="df98e97764e5a97f077f8a565189b670eff41300" datatype="html">
<source> Fuel your <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>self-hosted Ghostfolio<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> with a <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>powerful data provider<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> to access <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>80,000+ tickers<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> from over <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>50 exchanges<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> worldwide. </source>
<target state="new"> Fuel your <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>self-hosted Ghostfolio<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> with a <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>powerful data provider<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> to access <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>80,000+ tickers<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> from over <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>50 exchanges<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> worldwide. </target>
<target state="translated">Vul je <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>self-hosted Ghostfolio<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> met een <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>krachtige dataleverancier<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> om toegang te krijgen tot <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>80.000+ tickers<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> van meer dan <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>50 beurzen<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> wereldwijd.</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-settings/admin-settings.component.html</context>
<context context-type="linenumber">15</context>
@@ -7832,7 +7832,7 @@
</trans-unit>
<trans-unit id="48a079ff7a0ed08569e9ee315a387efeaa05f09c" datatype="html">
<source>Get <x id="INTERPOLATION" equiv-text="{{ durationExtension }}"/> extra</source>
<target state="new">Get <x id="INTERPOLATION" equiv-text="{{ durationExtension }}"/> extra</target>
<target state="translated">Krijg <x id="INTERPOLATION" equiv-text="{{ durationExtension }}"/> extra</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/pricing/pricing-page.html</context>
<context context-type="linenumber">313</context>
@@ -7896,7 +7896,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.false.max" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) exceeds ${thresholdMax}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) exceeds ${thresholdMax}% </target>
<target state="translated">De aandelenbijdrage van uw huidige investering (${equityValueRatio}%) overschrijdt ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">32</context>
@@ -7904,7 +7904,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.false.min" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) is below ${thresholdMin}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) is below ${thresholdMin}% </target>
<target state="translated">De inbreng in eigen vermogen van uw huidige investering (${equityValueRatio}%) ligt onder de ${thresholdMin}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">36</context>
@@ -7912,7 +7912,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.true" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </target>
<target state="translated">De aandelenbijdrage van uw huidige investering (${equityValueRatio}%) ligt binnen het bereik van ${thresholdMin}% en ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">40</context>
@@ -7920,7 +7920,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome" datatype="html">
<source>Fixed Income</source>
<target state="new">Fixed Income</target>
<target state="translated">Vastrentende waarden</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">45</context>
@@ -7928,7 +7928,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.false.max" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% </target>
<target state="translated">De bijdrage van vastrentende waarden in uw huidige investering (${fixedIncomeValueRatio}%) overschrijdt ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">46</context>
@@ -7936,7 +7936,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.false.min" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% </target>
<target state="translated">De bijdrage van vastrentende waarden in uw huidige investering (${fixedIncomeValueRatio}%) ligt onder ${thresholdMin}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">50</context>
@@ -7944,7 +7944,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.true" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </target>
<target state="translated">De bijdrage van vastrentende waarden in uw huidige investering (${fixedIncomeValueRatio}%) ligt binnen het bereik van ${thresholdMin}% en ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">55</context>
@@ -7952,7 +7952,7 @@
</trans-unit>
<trans-unit id="rule.currencyClusterRiskBaseCurrencyCurrentInvestment" datatype="html">
<source> Investment: Base Currency </source>
<target state="new"> Investment: Base Currency </target>
<target state="translated">Investering: basisvaluta</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">60</context>

View File

@@ -20,7 +20,6 @@ export interface PortfolioSummary extends PortfolioPerformance {
grossPerformance: number;
grossPerformanceWithCurrencyEffect: number;
interest: number;
items: number;
liabilities: number;
totalBuy: number;
totalSell: number;

View File

@@ -51,6 +51,4 @@ export interface SymbolMetrics {
totalInvestmentWithCurrencyEffect: Big;
totalLiabilities: Big;
totalLiabilitiesInBaseCurrency: Big;
totalValuables: Big;
totalValuablesInBaseCurrency: Big;
}

View File

@@ -45,8 +45,4 @@ export class PortfolioSnapshot {
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalLiabilitiesWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalValuablesWithCurrencyEffect: Big;
}

View File

@@ -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 @@
<ion-icon name="add-circle-outline" />
} @else if (activityType === 'FEE') {
<ion-icon name="hammer-outline" />
} @else if (activityType === 'ITEM') {
<ion-icon name="cube-outline" />
} @else if (activityType === 'LIABILITY') {
<ion-icon name="flame-outline" />
} @else if (activityType === 'SELL') {

View File

@@ -26,10 +26,6 @@
color: var(--cyan);
}
&.item {
color: var(--purple);
}
&.liability {
color: var(--red);
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.177.0",
"version": "2.178.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.177.0",
"version": "2.178.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.177.0",
"version": "2.178.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",

View File

@@ -0,0 +1,2 @@
-- AlterTable
UPDATE "Order" SET "type" = 'BUY' WHERE "type" = 'ITEM';

View File

@@ -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"
}
]
}

View File

@@ -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,
1 Date Code DataSource Currency Price Quantity Action Fee Note
2 01-09-2021 Account Opening Fee MANUAL USD 0 0 fee 49
3 16-09-2021 MSFT YAHOO USD 298.580 5 buy 19.00 My first order 🤓
4 17/11/2021 MSFT YAHOO USD 0.62 5 dividend 0.00
5 01.01.2022 Penthouse Apartment MANUAL USD 500000.0 1 item buy 0.00
6 20500606 US5949181045 YAHOO USD 0.00 0 buy 0.00

View File

@@ -41,7 +41,7 @@
"comment": null,
"fee": 0,
"quantity": 1,
"type": "ITEM",
"type": "BUY",
"unitPrice": 500000,
"currency": "USD",
"dataSource": "MANUAL",

View File

@@ -17,7 +17,7 @@
{
"fee": 0,
"quantity": 1,
"type": "ITEM",
"type": "BUY",
"unitPrice": 500000,
"currency": "USD",
"dataSource": "MANUAL",