Handler.cs 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Paper.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. using SharedKernel.Results;
  6. namespace Application.Features.Api.Paper.CancelOrder;
  7. /// <summary>
  8. /// 모의투자 주문 취소 — 본인 계좌의 Pending 주문 && now &lt; CancelableUntil 만 취소 (d4 §③/§⑤).
  9. /// 매수는 예약금 환원, 매도는 예약 수량 환원.
  10. /// </summary>
  11. internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result>
  12. {
  13. public async Task<Result> Handle(Command request, CancellationToken ct)
  14. {
  15. var order = await db.PaperOrder
  16. .Include(c => c.Account)
  17. .FirstOrDefaultAsync(c => c.ID == request.OrderID, ct);
  18. if (order is null || order.Account.MemberID != request.MemberID)
  19. {
  20. return Result.Failure(Error.NotFound("Paper.OrderNotFound", "주문을 찾을 수 없습니다."));
  21. }
  22. if (order.Status != PaperOrderStatus.Pending)
  23. {
  24. return Result.Failure(Error.Conflict("Paper.NotCancelable", "체결 대기 상태의 주문만 취소할 수 있습니다."));
  25. }
  26. if (DateTime.UtcNow >= order.CancelableUntil)
  27. {
  28. return Result.Failure(Error.Conflict("Paper.CancelWindowClosed", "취소 가능 시한이 지났습니다."));
  29. }
  30. if (!order.Cancel())
  31. {
  32. return Result.Failure(Error.Conflict("Paper.NotCancelable", "주문을 취소할 수 없습니다."));
  33. }
  34. if (order.Side == PaperOrderSide.Buy)
  35. {
  36. if (order.ReservedAmount > 0)
  37. {
  38. order.Account.ReleaseBuyReserve(order.ReservedAmount);
  39. }
  40. }
  41. else
  42. {
  43. var position = await db.PaperPosition.FirstOrDefaultAsync(c => c.AccountID == order.AccountID && c.StockCode == order.StockCode, ct);
  44. position?.ReleaseSellReserve(order.Quantity);
  45. }
  46. await db.SaveChangesAsync(ct);
  47. return Result.Success();
  48. }
  49. }