Compare commits

..

4 Commits

Author SHA1 Message Date
9bbb856f66 Release 1.102.0 (#625) 2022-01-11 19:53:23 +01:00
d3707bbb87 Bugfix/fix preselected default account in create activity dialog (#624)
* Fix preselected default account

* Update changelog
2022-01-11 19:50:22 +01:00
7df53896f3 Feature/start eliminating data source from order (#622)
* Start eliminating dataSource from order

* Update changelog
2022-01-11 19:49:45 +01:00
b2b3fde80e Bugfix/support multiple accounts with the same name (#623)
* Support multiple accounts with the same name

* Update changelog
2022-01-10 21:23:47 +01:00
10 changed files with 86 additions and 132 deletions

View File

@ -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/),
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
### Added

View File

@ -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 () => {
expect(
await currentRateService.getValues({

View File

@ -7,7 +7,6 @@ import { isBefore, isToday } from 'date-fns';
import { flatten } from 'lodash';
import { GetValueObject } from './interfaces/get-value-object.interface';
import { GetValueParams } from './interfaces/get-value-params.interface';
import { GetValuesParams } from './interfaces/get-values-params.interface';
@Injectable()
@ -18,46 +17,6 @@ export class CurrentRateService {
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({
currencies,
dataGatheringItems,

View File

@ -1,6 +0,0 @@
export interface GetValueParams {
currency: string;
date: Date;
symbol: string;
userCurrency: string;
}

View File

@ -1,17 +1,9 @@
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import { DataSource } from '@prisma/client';
import Big from 'big.js';
import {
addDays,
differenceInCalendarDays,
endOfDay,
format,
isBefore,
isSameDay
} from 'date-fns';
import { addDays, endOfDay, format, isBefore, isSameDay } from 'date-fns';
import { CurrentRateService } from './current-rate.service';
import { GetValueParams } from './interfaces/get-value-params.interface';
import { GetValuesParams } from './interfaces/get-values-params.interface';
import { PortfolioOrder } from './interfaces/portfolio-order.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
CurrentRateService: jest.fn().mockImplementation(() => {
return {
getValue: ({ date, symbol }: GetValueParams) => {
return Promise.resolve(mockGetValue(symbol, date));
},
getValues: ({ dataGatheringItems, dateQuery }: GetValuesParams) => {
const result = [];
if (dateQuery.lt) {

View File

@ -107,7 +107,7 @@ export class PortfolioService {
account.currency,
userCurrency
),
value: details.accounts[account.name]?.current ?? 0
value: details.accounts[account.id]?.current ?? 0
};
delete result.Order;
@ -428,7 +428,7 @@ export class PortfolioService {
})
.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
@ -1038,7 +1038,7 @@ export class PortfolioService {
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(
this.exchangeRateDataService.toCurrency(
@ -1091,10 +1091,11 @@ export class PortfolioService {
account.currency,
userCurrency
);
accounts[account.name] = {
accounts[account.id] = {
balance: convertedBalance,
currency: account.currency,
current: convertedBalance,
name: account.name,
original: convertedBalance
};
@ -1108,16 +1109,17 @@ export class PortfolioService {
originalValueOfSymbol *= -1;
}
if (accounts[order.Account?.name || UNKNOWN_KEY]?.current) {
accounts[order.Account?.name || UNKNOWN_KEY].current +=
if (accounts[order.Account?.id || UNKNOWN_KEY]?.current) {
accounts[order.Account?.id || UNKNOWN_KEY].current +=
currentValueOfSymbol;
accounts[order.Account?.name || UNKNOWN_KEY].original +=
accounts[order.Account?.id || UNKNOWN_KEY].original +=
originalValueOfSymbol;
} else {
accounts[order.Account?.name || UNKNOWN_KEY] = {
accounts[order.Account?.id || UNKNOWN_KEY] = {
balance: 0,
currency: order.Account?.currency,
current: currentValueOfSymbol,
name: account.name,
original: originalValueOfSymbol
};
}

View File

@ -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.accounts[name] = {
this.accounts[id] = {
name,
value: aPeriod === 'original' ? original : current
};

View File

@ -106,20 +106,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = 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.updateUser(state.user);
this.changeDetectorRef.markForCheck();
}
@ -352,43 +339,50 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
}
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
data: {
accounts: this.user?.accounts?.filter((account) => {
return account.accountType === 'SECURITIES';
}),
transaction: {
accountId: aTransaction?.accountId ?? this.defaultAccountId,
currency: aTransaction?.currency ?? null,
dataSource: aTransaction?.dataSource ?? null,
date: new Date(),
fee: 0,
quantity: null,
symbol: aTransaction?.symbol ?? null,
type: aTransaction?.type ?? 'BUY',
unitPrice: null
},
user: this.user
},
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef
.afterClosed()
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data: any) => {
const transaction: CreateOrderDto = data?.transaction;
.subscribe((user) => {
this.updateUser(user);
if (transaction) {
this.dataService.postOrder(transaction).subscribe({
next: () => {
this.fetchOrders();
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
data: {
accounts: this.user?.accounts?.filter((account) => {
return account.accountType === 'SECURITIES';
}),
transaction: {
accountId: aTransaction?.accountId ?? this.defaultAccountId,
currency: aTransaction?.currency ?? null,
dataSource: aTransaction?.dataSource ?? null,
date: new Date(),
fee: 0,
quantity: null,
symbol: aTransaction?.symbol ?? null,
type: aTransaction?.type ?? 'BUY',
unitPrice: null
},
user: this.user
},
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data: any) => {
const transaction: CreateOrderDto = data?.transaction;
if (transaction) {
this.dataService.postOrder(transaction).subscribe({
next: () => {
this.fetchOrders();
}
});
}
});
}
this.router.navigate(['.'], { relativeTo: this.route });
this.router.navigate(['.'], { relativeTo: this.route });
});
});
}
@ -397,7 +391,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
this.updateUser(user);
const dialogRef = this.dialog.open(PositionDetailDialog, {
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
);
}
}

View File

@ -2,10 +2,11 @@ import { PortfolioPosition } from '@ghostfolio/common/interfaces';
export interface PortfolioDetails {
accounts: {
[name: string]: {
[id: string]: {
balance: number;
currency: string;
current: number;
name: string;
original: number;
};
};

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "1.101.0",
"version": "1.102.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {