Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
9bbb856f66 | |||
d3707bbb87 | |||
7df53896f3 | |||
b2b3fde80e |
11
CHANGELOG.md
11
CHANGELOG.md
@ -5,6 +5,17 @@ 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).
|
||||||
|
|
||||||
|
## 1.102.0 - 11.01.2022
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Start eliminating `dataSource` from activity
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the support for multiple accounts with the same name
|
||||||
|
- Fixed the preselected default account of the create activity dialog
|
||||||
|
|
||||||
## 1.101.0 - 08.01.2022
|
## 1.101.0 - 08.01.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -85,19 +85,6 @@ describe('CurrentRateService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getValue', async () => {
|
|
||||||
expect(
|
|
||||||
await currentRateService.getValue({
|
|
||||||
currency: 'USD',
|
|
||||||
date: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)),
|
|
||||||
symbol: 'AMZN',
|
|
||||||
userCurrency: 'CHF'
|
|
||||||
})
|
|
||||||
).toMatchObject({
|
|
||||||
marketPrice: 1847.839966
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('getValues', async () => {
|
it('getValues', async () => {
|
||||||
expect(
|
expect(
|
||||||
await currentRateService.getValues({
|
await currentRateService.getValues({
|
||||||
|
@ -7,7 +7,6 @@ import { isBefore, isToday } from 'date-fns';
|
|||||||
import { flatten } from 'lodash';
|
import { flatten } from 'lodash';
|
||||||
|
|
||||||
import { GetValueObject } from './interfaces/get-value-object.interface';
|
import { GetValueObject } from './interfaces/get-value-object.interface';
|
||||||
import { GetValueParams } from './interfaces/get-value-params.interface';
|
|
||||||
import { GetValuesParams } from './interfaces/get-values-params.interface';
|
import { GetValuesParams } from './interfaces/get-values-params.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -18,46 +17,6 @@ export class CurrentRateService {
|
|||||||
private readonly marketDataService: MarketDataService
|
private readonly marketDataService: MarketDataService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getValue({
|
|
||||||
currency,
|
|
||||||
date,
|
|
||||||
symbol,
|
|
||||||
userCurrency
|
|
||||||
}: GetValueParams): Promise<GetValueObject> {
|
|
||||||
if (isToday(date)) {
|
|
||||||
const dataProviderResult = await this.dataProviderService.get([
|
|
||||||
{
|
|
||||||
symbol,
|
|
||||||
dataSource: this.dataProviderService.getPrimaryDataSource()
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
return {
|
|
||||||
symbol,
|
|
||||||
date: resetHours(date),
|
|
||||||
marketPrice: dataProviderResult?.[symbol]?.marketPrice ?? 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const marketData = await this.marketDataService.get({
|
|
||||||
date,
|
|
||||||
symbol
|
|
||||||
});
|
|
||||||
|
|
||||||
if (marketData) {
|
|
||||||
return {
|
|
||||||
date: marketData.date,
|
|
||||||
marketPrice: this.exchangeRateDataService.toCurrency(
|
|
||||||
marketData.marketPrice,
|
|
||||||
currency,
|
|
||||||
userCurrency
|
|
||||||
),
|
|
||||||
symbol: marketData.symbol
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Value not found for ${symbol} at ${resetHours(date)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getValues({
|
public async getValues({
|
||||||
currencies,
|
currencies,
|
||||||
dataGatheringItems,
|
dataGatheringItems,
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export interface GetValueParams {
|
|
||||||
currency: string;
|
|
||||||
date: Date;
|
|
||||||
symbol: string;
|
|
||||||
userCurrency: string;
|
|
||||||
}
|
|
@ -1,17 +1,9 @@
|
|||||||
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import {
|
import { addDays, endOfDay, format, isBefore, isSameDay } from 'date-fns';
|
||||||
addDays,
|
|
||||||
differenceInCalendarDays,
|
|
||||||
endOfDay,
|
|
||||||
format,
|
|
||||||
isBefore,
|
|
||||||
isSameDay
|
|
||||||
} from 'date-fns';
|
|
||||||
|
|
||||||
import { CurrentRateService } from './current-rate.service';
|
import { CurrentRateService } from './current-rate.service';
|
||||||
import { GetValueParams } from './interfaces/get-value-params.interface';
|
|
||||||
import { GetValuesParams } from './interfaces/get-values-params.interface';
|
import { GetValuesParams } from './interfaces/get-values-params.interface';
|
||||||
import { PortfolioOrder } from './interfaces/portfolio-order.interface';
|
import { PortfolioOrder } from './interfaces/portfolio-order.interface';
|
||||||
import { TimelinePeriod } from './interfaces/timeline-period.interface';
|
import { TimelinePeriod } from './interfaces/timeline-period.interface';
|
||||||
@ -275,9 +267,6 @@ jest.mock('./current-rate.service', () => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
CurrentRateService: jest.fn().mockImplementation(() => {
|
CurrentRateService: jest.fn().mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
getValue: ({ date, symbol }: GetValueParams) => {
|
|
||||||
return Promise.resolve(mockGetValue(symbol, date));
|
|
||||||
},
|
|
||||||
getValues: ({ dataGatheringItems, dateQuery }: GetValuesParams) => {
|
getValues: ({ dataGatheringItems, dateQuery }: GetValuesParams) => {
|
||||||
const result = [];
|
const result = [];
|
||||||
if (dateQuery.lt) {
|
if (dateQuery.lt) {
|
||||||
|
@ -107,7 +107,7 @@ export class PortfolioService {
|
|||||||
account.currency,
|
account.currency,
|
||||||
userCurrency
|
userCurrency
|
||||||
),
|
),
|
||||||
value: details.accounts[account.name]?.current ?? 0
|
value: details.accounts[account.id]?.current ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
delete result.Order;
|
delete result.Order;
|
||||||
@ -428,7 +428,7 @@ export class PortfolioService {
|
|||||||
})
|
})
|
||||||
.map((order) => ({
|
.map((order) => ({
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
dataSource: order.dataSource,
|
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||||
date: format(order.date, DATE_FORMAT),
|
date: format(order.date, DATE_FORMAT),
|
||||||
fee: new Big(order.fee),
|
fee: new Big(order.fee),
|
||||||
name: order.SymbolProfile?.name,
|
name: order.SymbolProfile?.name,
|
||||||
@ -1038,7 +1038,7 @@ export class PortfolioService {
|
|||||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
dataSource: order.dataSource,
|
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||||
date: format(order.date, DATE_FORMAT),
|
date: format(order.date, DATE_FORMAT),
|
||||||
fee: new Big(
|
fee: new Big(
|
||||||
this.exchangeRateDataService.toCurrency(
|
this.exchangeRateDataService.toCurrency(
|
||||||
@ -1091,10 +1091,11 @@ export class PortfolioService {
|
|||||||
account.currency,
|
account.currency,
|
||||||
userCurrency
|
userCurrency
|
||||||
);
|
);
|
||||||
accounts[account.name] = {
|
accounts[account.id] = {
|
||||||
balance: convertedBalance,
|
balance: convertedBalance,
|
||||||
currency: account.currency,
|
currency: account.currency,
|
||||||
current: convertedBalance,
|
current: convertedBalance,
|
||||||
|
name: account.name,
|
||||||
original: convertedBalance
|
original: convertedBalance
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1108,16 +1109,17 @@ export class PortfolioService {
|
|||||||
originalValueOfSymbol *= -1;
|
originalValueOfSymbol *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounts[order.Account?.name || UNKNOWN_KEY]?.current) {
|
if (accounts[order.Account?.id || UNKNOWN_KEY]?.current) {
|
||||||
accounts[order.Account?.name || UNKNOWN_KEY].current +=
|
accounts[order.Account?.id || UNKNOWN_KEY].current +=
|
||||||
currentValueOfSymbol;
|
currentValueOfSymbol;
|
||||||
accounts[order.Account?.name || UNKNOWN_KEY].original +=
|
accounts[order.Account?.id || UNKNOWN_KEY].original +=
|
||||||
originalValueOfSymbol;
|
originalValueOfSymbol;
|
||||||
} else {
|
} else {
|
||||||
accounts[order.Account?.name || UNKNOWN_KEY] = {
|
accounts[order.Account?.id || UNKNOWN_KEY] = {
|
||||||
balance: 0,
|
balance: 0,
|
||||||
currency: order.Account?.currency,
|
currency: order.Account?.currency,
|
||||||
current: currentValueOfSymbol,
|
current: currentValueOfSymbol,
|
||||||
|
name: account.name,
|
||||||
original: originalValueOfSymbol
|
original: originalValueOfSymbol
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -162,10 +162,10 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, { current, original }] of Object.entries(
|
for (const [id, { current, name, original }] of Object.entries(
|
||||||
this.portfolioDetails.accounts
|
this.portfolioDetails.accounts
|
||||||
)) {
|
)) {
|
||||||
this.accounts[name] = {
|
this.accounts[id] = {
|
||||||
name,
|
name,
|
||||||
value: aPeriod === 'original' ? original : current
|
value: aPeriod === 'original' ? original : current
|
||||||
};
|
};
|
||||||
|
@ -106,20 +106,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
this.user = state.user;
|
this.updateUser(state.user);
|
||||||
|
|
||||||
this.defaultAccountId = this.user?.accounts.find((account) => {
|
|
||||||
return account.isDefault;
|
|
||||||
})?.id;
|
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.createOrder
|
|
||||||
);
|
|
||||||
this.hasPermissionToDeleteOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.deleteOrder
|
|
||||||
);
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
@ -352,6 +339,12 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
||||||
|
this.userService
|
||||||
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.updateUser(user);
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
||||||
data: {
|
data: {
|
||||||
accounts: this.user?.accounts?.filter((account) => {
|
accounts: this.user?.accounts?.filter((account) => {
|
||||||
@ -390,6 +383,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
this.router.navigate(['.'], { relativeTo: this.route });
|
this.router.navigate(['.'], { relativeTo: this.route });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private openPositionDialog({ symbol }: { symbol: string }) {
|
private openPositionDialog({ symbol }: { symbol: string }) {
|
||||||
@ -397,7 +391,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
.get()
|
.get()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((user) => {
|
.subscribe((user) => {
|
||||||
this.user = user;
|
this.updateUser(user);
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
@ -419,4 +413,21 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateUser(aUser: User) {
|
||||||
|
this.user = aUser;
|
||||||
|
|
||||||
|
this.defaultAccountId = this.user?.accounts.find((account) => {
|
||||||
|
return account.isDefault;
|
||||||
|
})?.id;
|
||||||
|
|
||||||
|
this.hasPermissionToCreateOrder = hasPermission(
|
||||||
|
this.user.permissions,
|
||||||
|
permissions.createOrder
|
||||||
|
);
|
||||||
|
this.hasPermissionToDeleteOrder = hasPermission(
|
||||||
|
this.user.permissions,
|
||||||
|
permissions.deleteOrder
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|||||||
|
|
||||||
export interface PortfolioDetails {
|
export interface PortfolioDetails {
|
||||||
accounts: {
|
accounts: {
|
||||||
[name: string]: {
|
[id: string]: {
|
||||||
balance: number;
|
balance: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
current: number;
|
current: number;
|
||||||
|
name: string;
|
||||||
original: number;
|
original: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "1.101.0",
|
"version": "1.102.0",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Reference in New Issue
Block a user