Optionally update cash balance when adding activity (#1926)
* Optionally update cash balance when adding activity * Update changelog
This commit is contained in:
parent
876b66f324
commit
8ba15f8f72
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Introduced the option to update the cash balance of an account when adding an activity
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Upgraded `class-transformer` from version `0.3.2` to `0.5.1`
|
- Upgraded `class-transformer` from version `0.3.2` to `0.5.1`
|
||||||
|
@ -172,4 +172,47 @@ export class AccountService {
|
|||||||
where
|
where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateAccountBalance({
|
||||||
|
accountId,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
date,
|
||||||
|
userId
|
||||||
|
}: {
|
||||||
|
accountId: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
date: Date;
|
||||||
|
userId: string;
|
||||||
|
}) {
|
||||||
|
const { balance, currency: currencyOfAccount } = await this.account({
|
||||||
|
id_userId: {
|
||||||
|
userId,
|
||||||
|
id: accountId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountInCurrencyOfAccount =
|
||||||
|
await this.exchangeRateDataService.toCurrencyAtDate(
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
currencyOfAccount,
|
||||||
|
date
|
||||||
|
);
|
||||||
|
|
||||||
|
if (amountInCurrencyOfAccount) {
|
||||||
|
await this.prismaService.account.update({
|
||||||
|
data: {
|
||||||
|
balance: new Big(balance).plus(amountInCurrencyOfAccount).toNumber()
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id_userId: {
|
||||||
|
userId,
|
||||||
|
id: accountId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,6 +303,7 @@ export class ImportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateAccountBalance: false,
|
||||||
User: { connect: { id: userId } }
|
User: { connect: { id: userId } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { Transform, TransformFnParams } from 'class-transformer';
|
import { Transform, TransformFnParams } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
@ -64,4 +65,8 @@ export class CreateOrderDto {
|
|||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
unitPrice: number;
|
unitPrice: number;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
updateAccountBalance: boolean;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ export interface Activities {
|
|||||||
|
|
||||||
export interface Activity extends OrderWithAccount {
|
export interface Activity extends OrderWithAccount {
|
||||||
feeInBaseCurrency: number;
|
feeInBaseCurrency: number;
|
||||||
|
updateAccountBalance?: boolean;
|
||||||
value: number;
|
value: number;
|
||||||
valueInBaseCurrency: number;
|
valueInBaseCurrency: number;
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ export class OrderService {
|
|||||||
dataSource?: DataSource;
|
dataSource?: DataSource;
|
||||||
symbol?: string;
|
symbol?: string;
|
||||||
tags?: Tag[];
|
tags?: Tag[];
|
||||||
|
updateAccountBalance?: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
): Promise<Order> {
|
): Promise<Order> {
|
||||||
@ -89,12 +90,16 @@ export class OrderService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accountId = data.accountId;
|
||||||
|
let currency = data.currency;
|
||||||
const tags = data.tags ?? [];
|
const tags = data.tags ?? [];
|
||||||
|
const updateAccountBalance = data.updateAccountBalance ?? false;
|
||||||
|
const userId = data.userId;
|
||||||
|
|
||||||
if (data.type === 'ITEM') {
|
if (data.type === 'ITEM') {
|
||||||
const assetClass = data.assetClass;
|
const assetClass = data.assetClass;
|
||||||
const assetSubClass = data.assetSubClass;
|
const assetSubClass = data.assetSubClass;
|
||||||
const currency = data.SymbolProfile.connectOrCreate.create.currency;
|
currency = data.SymbolProfile.connectOrCreate.create.currency;
|
||||||
const dataSource: DataSource = 'MANUAL';
|
const dataSource: DataSource = 'MANUAL';
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
const name = data.SymbolProfile.connectOrCreate.create.symbol;
|
const name = data.SymbolProfile.connectOrCreate.create.symbol;
|
||||||
@ -149,11 +154,12 @@ export class OrderService {
|
|||||||
delete data.dataSource;
|
delete data.dataSource;
|
||||||
delete data.symbol;
|
delete data.symbol;
|
||||||
delete data.tags;
|
delete data.tags;
|
||||||
|
delete data.updateAccountBalance;
|
||||||
delete data.userId;
|
delete data.userId;
|
||||||
|
|
||||||
const orderData: Prisma.OrderCreateInput = data;
|
const orderData: Prisma.OrderCreateInput = data;
|
||||||
|
|
||||||
return this.prismaService.order.create({
|
const order = await this.prismaService.order.create({
|
||||||
data: {
|
data: {
|
||||||
...orderData,
|
...orderData,
|
||||||
Account,
|
Account,
|
||||||
@ -165,6 +171,27 @@ export class OrderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (updateAccountBalance === true) {
|
||||||
|
let amount = new Big(data.unitPrice)
|
||||||
|
.mul(data.quantity)
|
||||||
|
.plus(data.fee)
|
||||||
|
.toNumber();
|
||||||
|
|
||||||
|
if (data.type === 'BUY') {
|
||||||
|
amount = new Big(amount).mul(-1).toNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.accountService.updateAccountBalance({
|
||||||
|
accountId,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
userId,
|
||||||
|
date: data.date as Date
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteOrder(
|
public async deleteOrder(
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { Transform, TransformFnParams } from 'class-transformer';
|
import { Transform, TransformFnParams } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
@ -66,4 +67,8 @@ export class UpdateOrderDto {
|
|||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
unitPrice: number;
|
unitPrice: number;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
updateAccountBalance: boolean;
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
unitPriceInCustomCurrency: [
|
unitPriceInCustomCurrency: [
|
||||||
this.data.activity?.unitPrice,
|
this.data.activity?.unitPrice,
|
||||||
Validators.required
|
Validators.required
|
||||||
]
|
],
|
||||||
|
updateAccountBalance: [false]
|
||||||
});
|
});
|
||||||
|
|
||||||
this.activityForm.valueChanges
|
this.activityForm.valueChanges
|
||||||
@ -297,6 +298,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
Validators.required
|
Validators.required
|
||||||
);
|
);
|
||||||
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['updateAccountBalance'].disable();
|
||||||
|
this.activityForm.controls['updateAccountBalance'].setValue(false);
|
||||||
} else {
|
} else {
|
||||||
this.activityForm.controls['accountId'].setValidators(
|
this.activityForm.controls['accountId'].setValidators(
|
||||||
Validators.required
|
Validators.required
|
||||||
@ -314,6 +317,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
Validators.required
|
Validators.required
|
||||||
);
|
);
|
||||||
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
|
||||||
|
this.activityForm.controls['updateAccountBalance'].enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
@ -411,7 +415,9 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
|
|||||||
: this.activityForm.controls['searchSymbol'].value.symbol,
|
: this.activityForm.controls['searchSymbol'].value.symbol,
|
||||||
tags: this.activityForm.controls['tags'].value,
|
tags: this.activityForm.controls['tags'].value,
|
||||||
type: this.activityForm.controls['type'].value,
|
type: this.activityForm.controls['type'].value,
|
||||||
unitPrice: this.activityForm.controls['unitPrice'].value
|
unitPrice: this.activityForm.controls['unitPrice'].value,
|
||||||
|
updateAccountBalance:
|
||||||
|
this.activityForm.controls['updateAccountBalance'].value
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.data.activity.id) {
|
if (this.data.activity.id) {
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="mb-1 without-hint w-100">
|
||||||
<mat-label i18n>Account</mat-label>
|
<mat-label i18n>Account</mat-label>
|
||||||
<mat-select formControlName="accountId">
|
<mat-select formControlName="accountId">
|
||||||
<mat-option
|
<mat-option
|
||||||
@ -32,6 +32,11 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<mat-checkbox color="primary" formControlName="updateAccountBalance" i18n
|
||||||
|
>Update Cash Balance</mat-checkbox
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }"
|
[ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }"
|
||||||
|
@ -8,6 +8,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
|
|||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
@ -24,6 +25,7 @@ import { CreateOrUpdateActivityDialog } from './create-or-update-activity-dialog
|
|||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatCheckboxModule,
|
||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
|
@ -59,7 +59,8 @@ export class ImportActivitiesService {
|
|||||||
quantity: this.parseQuantity({ content, index, item }),
|
quantity: this.parseQuantity({ content, index, item }),
|
||||||
symbol: this.parseSymbol({ content, index, item }),
|
symbol: this.parseSymbol({ content, index, item }),
|
||||||
type: this.parseType({ content, index, item }),
|
type: this.parseType({ content, index, item }),
|
||||||
unitPrice: this.parseUnitPrice({ content, index, item })
|
unitPrice: this.parseUnitPrice({ content, index, item }),
|
||||||
|
updateAccountBalance: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +127,8 @@ export class ImportActivitiesService {
|
|||||||
quantity,
|
quantity,
|
||||||
SymbolProfile,
|
SymbolProfile,
|
||||||
type,
|
type,
|
||||||
unitPrice
|
unitPrice,
|
||||||
|
updateAccountBalance
|
||||||
}: Activity): CreateOrderDto {
|
}: Activity): CreateOrderDto {
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
@ -134,6 +136,7 @@ export class ImportActivitiesService {
|
|||||||
quantity,
|
quantity,
|
||||||
type,
|
type,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
|
updateAccountBalance,
|
||||||
currency: SymbolProfile.currency,
|
currency: SymbolProfile.currency,
|
||||||
date: date.toString(),
|
date: date.toString(),
|
||||||
symbol: SymbolProfile.symbol
|
symbol: SymbolProfile.symbol
|
||||||
|
Loading…
x
Reference in New Issue
Block a user