From 7c17c5782d8e7c7052f94d4ef1717c75f753685f Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 14 Nov 2024 15:35:22 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20userStock=20domain=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/userStock/user-stock.controller.ts | 6 +++++ BE/src/userStock/user-stock.entity.ts | 27 +++++++++++++++++++++++ BE/src/userStock/user-stock.module.ts | 13 +++++++++++ BE/src/userStock/user-stock.repository.ts | 11 +++++++++ BE/src/userStock/user-stock.service.ts | 4 ++++ 5 files changed, 61 insertions(+) create mode 100644 BE/src/userStock/user-stock.controller.ts create mode 100644 BE/src/userStock/user-stock.entity.ts create mode 100644 BE/src/userStock/user-stock.module.ts create mode 100644 BE/src/userStock/user-stock.repository.ts create mode 100644 BE/src/userStock/user-stock.service.ts diff --git a/BE/src/userStock/user-stock.controller.ts b/BE/src/userStock/user-stock.controller.ts new file mode 100644 index 00000000..e1e4c209 --- /dev/null +++ b/BE/src/userStock/user-stock.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +@Controller('/api/userStock') +@ApiTags('사용자 보유 주식 API') +export class UserStockController {} diff --git a/BE/src/userStock/user-stock.entity.ts b/BE/src/userStock/user-stock.entity.ts new file mode 100644 index 00000000..60a1b169 --- /dev/null +++ b/BE/src/userStock/user-stock.entity.ts @@ -0,0 +1,27 @@ +import { + Column, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity('user_stocks') +export class UserStock { + @PrimaryGeneratedColumn() + id: number; + + @Column({ nullable: false }) + user_id: number; + + @Column({ nullable: false }) + stock_code: string; + + @Column({ nullable: false }) + quantity: number; + + @Column('decimal', { nullable: false, precision: 10, scale: 5 }) + avg_price: number; + + @UpdateDateColumn() + last_updated: Date; +} diff --git a/BE/src/userStock/user-stock.module.ts b/BE/src/userStock/user-stock.module.ts new file mode 100644 index 00000000..c7584fc0 --- /dev/null +++ b/BE/src/userStock/user-stock.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserStock } from './user-stock.entity'; +import { UserStockController } from './user-stock.controller'; +import { UserStockRepository } from './user-stock.repository'; +import { UserStockService } from './user-stock.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserStock])], + controllers: [UserStockController], + providers: [UserStockRepository, UserStockService], +}) +export class StockOrderModule {} diff --git a/BE/src/userStock/user-stock.repository.ts b/BE/src/userStock/user-stock.repository.ts new file mode 100644 index 00000000..e945a667 --- /dev/null +++ b/BE/src/userStock/user-stock.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { InjectDataSource } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; +import { UserStock } from './user-stock.entity'; + +@Injectable() +export class UserStockRepository extends Repository { + constructor(@InjectDataSource() private dataSource: DataSource) { + super(UserStock, dataSource.createEntityManager()); + } +} diff --git a/BE/src/userStock/user-stock.service.ts b/BE/src/userStock/user-stock.service.ts new file mode 100644 index 00000000..1c69dcaf --- /dev/null +++ b/BE/src/userStock/user-stock.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class UserStockService {} From e71c34874a300fb57cf04a2cd3a590b192b55020 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 14 Nov 2024 15:52:06 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EA=B5=AC=EB=A7=A4=20=EC=B2=B4=EA=B2=B0=20=EC=8B=9C=20user=5Fst?= =?UTF-8?q?ocks=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#53?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/order/stock-order-socket.service.ts | 2 +- BE/src/stock/order/stock-order.repository.ts | 38 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/BE/src/stock/order/stock-order-socket.service.ts b/BE/src/stock/order/stock-order-socket.service.ts index 0f56140f..4c2a0bfc 100644 --- a/BE/src/stock/order/stock-order-socket.service.ts +++ b/BE/src/stock/order/stock-order-socket.service.ts @@ -89,7 +89,7 @@ export class StockOrderSocketService { const totalPrice = order.price * order.amount; const fee = this.calculateFee(totalPrice); - await this.stockOrderRepository.updateOrderAndAssetWhenBuy( + await this.stockOrderRepository.updateOrderAndAssetAndUserStockWhenBuy( order, totalPrice + fee, ); diff --git a/BE/src/stock/order/stock-order.repository.ts b/BE/src/stock/order/stock-order.repository.ts index 88344cf2..c84c2f59 100644 --- a/BE/src/stock/order/stock-order.repository.ts +++ b/BE/src/stock/order/stock-order.repository.ts @@ -4,6 +4,9 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Order } from './stock-order.entity'; import { StatusType } from './enum/status-type'; import { Asset } from '../../asset/asset.entity'; +import { StockOrderRequestDto } from './dto/stock-order-request.dto'; +import { TradeType } from './enum/trade-type'; +import { UserStock } from '../../userStock/user-stock.entity'; @Injectable() export class StockOrderRepository extends Repository { @@ -17,7 +20,7 @@ export class StockOrderRepository extends Repository { .getRawMany(); } - async updateOrderAndAssetWhenBuy(order, realPrice) { + async updateOrderAndAssetAndUserStockWhenBuy(order, realPrice) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.startTransaction(); @@ -32,14 +35,32 @@ export class StockOrderRepository extends Repository { .createQueryBuilder() .update(Asset) .set({ - cash_balance: () => 'cash_balance - :realPrice', - total_asset: () => 'total_asset - :realPrice', - total_profit: () => 'total_profit - :realPrice', + cash_balance: () => `cash_balance - ${realPrice}`, + total_asset: () => `total_asset - ${realPrice}`, + total_profit: () => `total_profit - ${realPrice}`, total_profit_rate: () => `total_profit / 10000000`, last_updated: new Date(), }) .where({ user_id: order.user_id }) - .setParameter('realPrice', realPrice) + .execute(); + + await queryRunner.manager + .createQueryBuilder() + .insert() + .into(UserStock) + .values({ + user_id: order.user_id, + stock_code: order.stock_code, + quantity: order.amount, + avg_price: order.price, + }) + .orUpdate( + [ + `quantity = quantity + ${order.amount}`, + `avg_price = ((avg_price * quantity + ${order.price} * ${order.amount}) / (quantity + ${order.amount}))`, + ], + ['user_id', 'stock_code'], + ) .execute(); await queryRunner.commitTransaction(); @@ -66,14 +87,13 @@ export class StockOrderRepository extends Repository { .createQueryBuilder() .update(Asset) .set({ - cash_balance: () => 'cash_balance + :realPrice', - total_asset: () => 'total_asset + :realPrice', - total_profit: () => 'total_profit + :realPrice', + cash_balance: () => `cash_balance + ${realPrice}`, + total_asset: () => `total_asset + ${realPrice}`, + total_profit: () => `total_profit + ${realPrice}`, total_profit_rate: () => `total_profit / 10000000`, last_updated: new Date(), }) .where({ user_id: order.user_id }) - .setParameter('realPrice', realPrice) .execute(); await queryRunner.commitTransaction(); From 30a57db519ad9625530a4a2154bedcaf1618dc46 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 14 Nov 2024 16:11:15 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EB=A7=A4=EB=8F=84=20=EC=B2=B4=EA=B2=B0=20=EC=8B=9C=20user=5Fst?= =?UTF-8?q?ocks=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20#53?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 매도할 개수만큼 매수하지 않은 주식에 대해 매도 막음 --- BE/src/stock/order/stock-order-socket.service.ts | 2 +- BE/src/stock/order/stock-order.module.ts | 8 +++++++- BE/src/stock/order/stock-order.repository.ts | 11 ++++++++++- BE/src/stock/order/stock-order.service.ts | 11 +++++++++++ BE/src/userStock/user-stock.module.ts | 3 ++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/BE/src/stock/order/stock-order-socket.service.ts b/BE/src/stock/order/stock-order-socket.service.ts index 4c2a0bfc..6e2bec3b 100644 --- a/BE/src/stock/order/stock-order-socket.service.ts +++ b/BE/src/stock/order/stock-order-socket.service.ts @@ -100,7 +100,7 @@ export class StockOrderSocketService { const totalPrice = order.price * order.amount; const fee = this.calculateFee(totalPrice); - await this.stockOrderRepository.updateOrderAndAssetWhenSell( + await this.stockOrderRepository.updateOrderAndAssetAndUserStockWhenSell( order, totalPrice - fee, ); diff --git a/BE/src/stock/order/stock-order.module.ts b/BE/src/stock/order/stock-order.module.ts index 454ca252..155c0855 100644 --- a/BE/src/stock/order/stock-order.module.ts +++ b/BE/src/stock/order/stock-order.module.ts @@ -7,9 +7,15 @@ import { StockOrderRepository } from './stock-order.repository'; import { SocketModule } from '../../websocket/socket.module'; import { AssetModule } from '../../asset/asset.module'; import { StockOrderSocketService } from './stock-order-socket.service'; +import { UserStockModule } from '../../userStock/user-stock.module'; @Module({ - imports: [TypeOrmModule.forFeature([Order]), SocketModule, AssetModule], + imports: [ + TypeOrmModule.forFeature([Order]), + SocketModule, + AssetModule, + UserStockModule, + ], controllers: [StockOrderController], providers: [StockOrderService, StockOrderRepository, StockOrderSocketService], }) diff --git a/BE/src/stock/order/stock-order.repository.ts b/BE/src/stock/order/stock-order.repository.ts index c84c2f59..4ae1b58a 100644 --- a/BE/src/stock/order/stock-order.repository.ts +++ b/BE/src/stock/order/stock-order.repository.ts @@ -72,7 +72,7 @@ export class StockOrderRepository extends Repository { } } - async updateOrderAndAssetWhenSell(order, realPrice) { + async updateOrderAndAssetAndUserStockWhenSell(order, realPrice) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.startTransaction(); @@ -96,6 +96,15 @@ export class StockOrderRepository extends Repository { .where({ user_id: order.user_id }) .execute(); + await queryRunner.manager + .createQueryBuilder() + .update(UserStock) + .set({ + quantity: () => `quantity - ${order.amount}`, + }) + .where({ user_id: order.user_id, stock_code: order.stock_code }) + .execute(); + await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); diff --git a/BE/src/stock/order/stock-order.service.ts b/BE/src/stock/order/stock-order.service.ts index b558dc5e..bdbfe8ce 100644 --- a/BE/src/stock/order/stock-order.service.ts +++ b/BE/src/stock/order/stock-order.service.ts @@ -1,4 +1,5 @@ import { + BadRequestException, ConflictException, ForbiddenException, Injectable, @@ -9,12 +10,14 @@ import { StockOrderRepository } from './stock-order.repository'; import { TradeType } from './enum/trade-type'; import { StatusType } from './enum/status-type'; import { StockOrderSocketService } from './stock-order-socket.service'; +import { UserStockRepository } from '../../userStock/user-stock.repository'; @Injectable() export class StockOrderService { constructor( private readonly stockOrderRepository: StockOrderRepository, private readonly stockOrderSocketService: StockOrderSocketService, + private readonly userStockRepository: UserStockRepository, ) {} async buy(userId: number, stockOrderRequest: StockOrderRequestDto) { @@ -32,6 +35,14 @@ export class StockOrderService { } async sell(userId: number, stockOrderRequest: StockOrderRequestDto) { + const userStock = await this.userStockRepository.findOneBy({ + user_id: userId, + stock_code: stockOrderRequest.stock_code, + }); + + if (!userStock || userStock.quantity === 0) + throw new BadRequestException('주식을 매도 수만큼 가지고 있지 않습니다.'); + const order = this.stockOrderRepository.create({ user_id: userId, stock_code: stockOrderRequest.stock_code, diff --git a/BE/src/userStock/user-stock.module.ts b/BE/src/userStock/user-stock.module.ts index c7584fc0..a6466976 100644 --- a/BE/src/userStock/user-stock.module.ts +++ b/BE/src/userStock/user-stock.module.ts @@ -9,5 +9,6 @@ import { UserStockService } from './user-stock.service'; imports: [TypeOrmModule.forFeature([UserStock])], controllers: [UserStockController], providers: [UserStockRepository, UserStockService], + exports: [UserStockRepository], }) -export class StockOrderModule {} +export class UserStockModule {} From 712cabd9da56d1cd59c16ac0810399c40062a9b9 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 14 Nov 2024 16:20:59 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20=EB=A6=B0?= =?UTF-8?q?=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/order/stock-order.repository.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/BE/src/stock/order/stock-order.repository.ts b/BE/src/stock/order/stock-order.repository.ts index 4ae1b58a..6cd70c39 100644 --- a/BE/src/stock/order/stock-order.repository.ts +++ b/BE/src/stock/order/stock-order.repository.ts @@ -4,8 +4,6 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Order } from './stock-order.entity'; import { StatusType } from './enum/status-type'; import { Asset } from '../../asset/asset.entity'; -import { StockOrderRequestDto } from './dto/stock-order-request.dto'; -import { TradeType } from './enum/trade-type'; import { UserStock } from '../../userStock/user-stock.entity'; @Injectable()