Skip to content

Commit

Permalink
Merge pull request #101 from boostcampwm-2024/be/feature/order_market
Browse files Browse the repository at this point in the history
[BE/feature] 시장가 거래 및 검증 로직 추가
  • Loading branch information
HBLEEEEE authored Nov 26, 2024
2 parents c3e87fd + ca74e27 commit 2eb6d44
Show file tree
Hide file tree
Showing 14 changed files with 660 additions and 197 deletions.
30 changes: 30 additions & 0 deletions apps/backend/src/account/account.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,34 @@ export class AccountRepository {
quantity: result.rows[0].available_quantity || 0
};
}

async decrementPendingCrop(memberId: number, cropId: number, quantity: number): Promise<void> {
if (quantity <= 0) {
throw new Error('반드시 0보다 큰 값을 감소해야 합니다.');
}
const query = `
UPDATE member_crops
SET pending_quantity = GREATEST(pending_quantity - $1, 0),
available_quantity = available_quantity + $1
WHERE member_id = $2
AND crop_id = $3
`;
const values = [quantity, memberId, cropId];
await this.databaseService.query(query, values);
}

async incrementCash(memberId: number, amount: number): Promise<void> {
if (amount <= 0) {
throw new Error('반드시 0보다 큰 값을 증가해야 합니다.');
}

const query = `
UPDATE members
SET available_cash = available_cash + $1,
pending_cash = pending_cash - $1
WHERE member_id = $2
`;
const values = [amount, memberId];
await this.databaseService.query(query, values);
}
}
8 changes: 8 additions & 0 deletions apps/backend/src/account/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ export class AccountService {
async getCropFromMemberId(memberId: number, cropId: number): Promise<AccountCropDto> {
return await this.accountRepository.getCropsByMemberId(memberId, cropId);
}

async rollbackPendingCrop(cropId: number, memberId: number, quantity: number): Promise<void> {
await this.accountRepository.decrementPendingCrop(memberId, cropId, quantity);
}

async rollbackPendingCash(memberId: number, amount: number): Promise<void> {
await this.accountRepository.incrementCash(memberId, amount);
}
}
9 changes: 4 additions & 5 deletions apps/backend/src/account/guards/hasSufficientCashGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ export class HasSufficientCashGuard implements CanActivate {

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const { tradingType, memberId, price, quantity } = request.body;
const { tradingType, memberId, price, quantity, totalAmount } = request.body;

let hasEnoughCash = false;
// 지정가 주문 확인
if (tradingType === 'limit') {
hasEnoughCash = await this.canLimitBuyOrder(price, memberId, quantity);
} else if (tradingType === 'market') {
// TODO : 시장가 주문 기능 보안 예정
hasEnoughCash = await this.canMarketBuyOrder(price, memberId);
hasEnoughCash = await this.canMarketBuyOrder(totalAmount, memberId);
}

if (!hasEnoughCash) {
Expand All @@ -32,9 +31,9 @@ export class HasSufficientCashGuard implements CanActivate {
return availableCash >= total_price;
}

async canMarketBuyOrder(total_price: number, memberId: number): Promise<boolean> {
async canMarketBuyOrder(totalAmount: number, memberId: number): Promise<boolean> {
const memberCash = await this.accountService.getCashFromMemberId(memberId);
const availableCash = memberCash.availableCash;
return availableCash >= total_price;
return availableCash >= totalAmount;
}
}
1 change: 0 additions & 1 deletion apps/backend/src/account/guards/hasSufficientCropGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export class HasSufficientCropGuard implements CanActivate {
if (tradingType === 'limit') {
hasEnoughCrop = await this.canLimitSellOrder(cropId, memberId, quantity);
} else if (tradingType === 'market') {
// TODO : 시장가 주문 기능 보안 예정
hasEnoughCrop = await this.canMarketSellOrder(cropId, memberId, quantity);
}

Expand Down
10 changes: 10 additions & 0 deletions apps/backend/src/order/dto/marketOrder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { BaseOrderDto } from './baseOrder.dto';

export class MarketOrderDto extends BaseOrderDto {
@ApiProperty({ description: '총 주문 금액', example: 50000 })
totalAmount: number;

@ApiProperty({ description: '주문 수량', example: 100 })
quantity: number | null;
}
27 changes: 21 additions & 6 deletions apps/backend/src/order/dto/order.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { BaseOrderDto } from './baseOrder.dto';
import { OrderStatus } from '../enums/orderType';

Expand All @@ -9,18 +9,33 @@ export class OrderDto extends BaseOrderDto {
@ApiProperty({ description: '주문 상태', example: 'pending' })
status: OrderStatus;

@ApiProperty({ description: '주문 가격', example: 1500 })
price: number;
@ApiPropertyOptional({
description: '주문 가격 (지정가 주문일 경우 필수)',
example: 1500,
nullable: true
})
price: number | null;

@ApiProperty({ description: '주문 시간', example: new Date() })
time: Date;

@ApiProperty({ description: '주문 수량', example: 100 })
quantity: number;
@ApiPropertyOptional({
description: '주문 수량 (시장가 매수에서는 비워 둘 수 있음)',
example: 100,
nullable: true
})
quantity: number | null;

@ApiPropertyOptional({
description: '총 금액 (시장가 매수에서는 필수)',
example: 150000,
nullable: true
})
totalAmount: number | null;

@ApiProperty({ description: '체결된 수량', example: 50 })
filledQuantity: number;

@ApiProperty({ description: '미체결 수량', example: 50 })
unfilledQuantity: number;
unfilledQuantity: number | null;
}
29 changes: 22 additions & 7 deletions apps/backend/src/order/dto/orderBook.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { OrderType, TradingType } from '../enums/orderType';

export class OrderBookDto {
Expand All @@ -17,17 +17,32 @@ export class OrderBookDto {
@ApiProperty({ description: '오더 유형', example: 'limit' })
tradingType: TradingType;

@ApiProperty({ description: '가격', example: 50 })
price: number;
@ApiPropertyOptional({
description: '가격 (지정가 주문일 경우 필수)',
example: 50,
nullable: true
})
price: number | null;

@ApiProperty({ description: '주문 수량', example: 150 })
quantity: number;
@ApiPropertyOptional({
description: '주문 수량 (시장가 매수는 없음)',
example: 150,
nullable: true
})
quantity: number | null;

@ApiPropertyOptional({ description: '시장가 매수의 총 금액', example: 5000, nullable: true })
totalAmount: number | null;

@ApiProperty({ description: '체결된 수량', example: 50 })
filledQuantity: number;

@ApiProperty({ description: '미체결 수량', example: 100 })
unfilledQuantity: number;
@ApiPropertyOptional({
description: '미체결 수량 (시장가 매수는 없음)',
example: 100,
nullable: true
})
unfilledQuantity: number | null;

@ApiProperty({ description: '주문 시간', example: new Date() })
time: Date;
Expand Down
14 changes: 14 additions & 0 deletions apps/backend/src/order/enums/orderType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,17 @@ export enum OrderStatus {
COMPLETED = 'completed',
CANCELED = 'canceled'
}

export function toOrderType(orderType: string): OrderType {
if (orderType === 'buy') return OrderType.BUY;
if (orderType === 'sell') return OrderType.SELL;

throw new Error(`잘못된 오더 타입입니다. ${orderType}`);
}

export function toTradingType(tradingType: string): TradingType {
if (tradingType === 'limit') return TradingType.LIMIT;
if (tradingType === 'market') return TradingType.MARKET;

throw new Error(`잘못된 트레이딩 타입입니다. ${tradingType}`);
}
Loading

0 comments on commit 2eb6d44

Please sign in to comment.