diff --git a/BE/src/stock/order/stock-order-socket.service.ts b/BE/src/stock/order/stock-order-socket.service.ts index 0f56140f..6e2bec3b 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, ); @@ -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 88344cf2..6cd70c39 100644 --- a/BE/src/stock/order/stock-order.repository.ts +++ b/BE/src/stock/order/stock-order.repository.ts @@ -4,6 +4,7 @@ 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 { UserStock } from '../../userStock/user-stock.entity'; @Injectable() export class StockOrderRepository extends Repository { @@ -17,7 +18,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 +33,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(); @@ -51,7 +70,7 @@ export class StockOrderRepository extends Repository { } } - async updateOrderAndAssetWhenSell(order, realPrice) { + async updateOrderAndAssetAndUserStockWhenSell(order, realPrice) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.startTransaction(); @@ -66,14 +85,22 @@ 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() + .update(UserStock) + .set({ + quantity: () => `quantity - ${order.amount}`, + }) + .where({ user_id: order.user_id, stock_code: order.stock_code }) .execute(); await queryRunner.commitTransaction(); 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.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..a6466976 --- /dev/null +++ b/BE/src/userStock/user-stock.module.ts @@ -0,0 +1,14 @@ +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], + exports: [UserStockRepository], +}) +export class UserStockModule {} 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 {}