diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db1aa18..41f949b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Introduced the option to update the cash balance of an account when adding an activity + ### Changed - Upgraded `class-transformer` from version `0.3.2` to `0.5.1` diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index eb3ad814..c6da815e 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -172,4 +172,47 @@ export class AccountService { 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 + } + } + }); + } + } } diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index c3b8f63b..98dc0e24 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -303,6 +303,7 @@ export class ImportService { } } }, + updateAccountBalance: false, User: { connect: { id: userId } } }); } diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index 33e6f9cc..e43d1dad 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -8,6 +8,7 @@ import { import { Transform, TransformFnParams } from 'class-transformer'; import { IsArray, + IsBoolean, IsEnum, IsISO8601, IsNumber, @@ -64,4 +65,8 @@ export class CreateOrderDto { @IsNumber() unitPrice: number; + + @IsBoolean() + @IsOptional() + updateAccountBalance: boolean; } diff --git a/apps/api/src/app/order/interfaces/activities.interface.ts b/apps/api/src/app/order/interfaces/activities.interface.ts index 31b345b4..33fe8b40 100644 --- a/apps/api/src/app/order/interfaces/activities.interface.ts +++ b/apps/api/src/app/order/interfaces/activities.interface.ts @@ -6,6 +6,7 @@ export interface Activities { export interface Activity extends OrderWithAccount { feeInBaseCurrency: number; + updateAccountBalance?: boolean; value: number; valueInBaseCurrency: number; } diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 9bc2c6a8..696f5442 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -73,6 +73,7 @@ export class OrderService { dataSource?: DataSource; symbol?: string; tags?: Tag[]; + updateAccountBalance?: boolean; userId: string; } ): Promise { @@ -89,12 +90,16 @@ export class OrderService { }; } + const accountId = data.accountId; + let currency = data.currency; const tags = data.tags ?? []; + const updateAccountBalance = data.updateAccountBalance ?? false; + const userId = data.userId; if (data.type === 'ITEM') { const assetClass = data.assetClass; const assetSubClass = data.assetSubClass; - const currency = data.SymbolProfile.connectOrCreate.create.currency; + currency = data.SymbolProfile.connectOrCreate.create.currency; const dataSource: DataSource = 'MANUAL'; const id = uuidv4(); const name = data.SymbolProfile.connectOrCreate.create.symbol; @@ -149,11 +154,12 @@ export class OrderService { delete data.dataSource; delete data.symbol; delete data.tags; + delete data.updateAccountBalance; delete data.userId; const orderData: Prisma.OrderCreateInput = data; - return this.prismaService.order.create({ + const order = await this.prismaService.order.create({ data: { ...orderData, 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( diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index 7c709ea7..6802c135 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -8,6 +8,7 @@ import { import { Transform, TransformFnParams } from 'class-transformer'; import { IsArray, + IsBoolean, IsEnum, IsISO8601, IsNumber, @@ -66,4 +67,8 @@ export class UpdateOrderDto { @IsNumber() unitPrice: number; + + @IsBoolean() + @IsOptional() + updateAccountBalance: boolean; } diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index b64fe548..4336e941 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -139,7 +139,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { unitPriceInCustomCurrency: [ this.data.activity?.unitPrice, Validators.required - ] + ], + updateAccountBalance: [false] }); this.activityForm.valueChanges @@ -297,6 +298,8 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { Validators.required ); this.activityForm.controls['searchSymbol'].updateValueAndValidity(); + this.activityForm.controls['updateAccountBalance'].disable(); + this.activityForm.controls['updateAccountBalance'].setValue(false); } else { this.activityForm.controls['accountId'].setValidators( Validators.required @@ -314,6 +317,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { Validators.required ); this.activityForm.controls['searchSymbol'].updateValueAndValidity(); + this.activityForm.controls['updateAccountBalance'].enable(); } this.changeDetectorRef.markForCheck(); @@ -411,7 +415,9 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { : this.activityForm.controls['searchSymbol'].value.symbol, tags: this.activityForm.controls['tags'].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) { diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 4ef9b448..9ad56c90 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -18,8 +18,8 @@ -
- +
+ Account
+
+ Update Cash Balance +