| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Domain.Entities.Paper.ValueObject;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Api.Paper.CancelOrder;
- /// <summary>
- /// 모의투자 주문 취소 — 본인 계좌의 Pending 주문 && now < CancelableUntil 만 취소 (d4 §③/§⑤).
- /// 매수는 예약금 환원, 매도는 예약 수량 환원.
- /// </summary>
- internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result>
- {
- public async Task<Result> Handle(Command request, CancellationToken ct)
- {
- var order = await db.PaperOrder
- .Include(c => c.Account)
- .FirstOrDefaultAsync(c => c.ID == request.OrderID, ct);
- if (order is null || order.Account.MemberID != request.MemberID)
- {
- return Result.Failure(Error.NotFound("Paper.OrderNotFound", "주문을 찾을 수 없습니다."));
- }
- if (order.Status != PaperOrderStatus.Pending)
- {
- return Result.Failure(Error.Conflict("Paper.NotCancelable", "체결 대기 상태의 주문만 취소할 수 있습니다."));
- }
- if (DateTime.UtcNow >= order.CancelableUntil)
- {
- return Result.Failure(Error.Conflict("Paper.CancelWindowClosed", "취소 가능 시한이 지났습니다."));
- }
- if (!order.Cancel())
- {
- return Result.Failure(Error.Conflict("Paper.NotCancelable", "주문을 취소할 수 없습니다."));
- }
- if (order.Side == PaperOrderSide.Buy)
- {
- if (order.ReservedAmount > 0)
- {
- order.Account.ReleaseBuyReserve(order.ReservedAmount);
- }
- }
- else
- {
- var position = await db.PaperPosition.FirstOrDefaultAsync(c => c.AccountID == order.AccountID && c.StockCode == order.StockCode, ct);
- position?.ReleaseSellReserve(order.Quantity);
- }
- await db.SaveChangesAsync(ct);
- return Result.Success();
- }
- }
|