diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommand.cs new file mode 100644 index 000000000..e8ec80253 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommand.cs @@ -0,0 +1,16 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; + +public class CompleteOrderCommand : IRequest +{ + + [DataMember] + public int OrderNumber { get; set; } + public CompleteOrderCommand() + { + + } + public CompleteOrderCommand(int orderNumber) + { + OrderNumber = orderNumber; + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommandHandler.cs new file mode 100644 index 000000000..0f0a5da18 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommandHandler.cs @@ -0,0 +1,48 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; + +// Regular CommandHandler +public class CompleteOrderCommandHandler : IRequestHandler +{ + private readonly IOrderRepository _orderRepository; + + public CompleteOrderCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// customer executes complete order from app + /// + /// + /// + public async Task Handle(CompleteOrderCommand command, CancellationToken cancellationToken) + { + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if (orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetCompletedStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + } +} + + +// Use for Idempotency in Command process +public class CompleteOrderIdentifiedCommandHandler : IdentifiedCommandHandler +{ + public CompleteOrderIdentifiedCommandHandler( + IMediator mediator, + IRequestManager requestManager, + ILogger> logger) + : base(mediator, requestManager, logger) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } +} diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index df7572bb7..e6843591c 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -55,6 +55,36 @@ public class OrdersController : ControllerBase return Ok(); } + [Route("complete")] + [HttpPut] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CompleteOrderAsync([FromBody] CompleteOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) + { + bool commandResult = false; + + if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) + { + var requestCompleteOrder = new IdentifiedCommand(command, guid); + + _logger.LogInformation( + "Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + requestCompleteOrder.GetGenericTypeName(), + nameof(requestCompleteOrder.Command.OrderNumber), + requestCompleteOrder.Command.OrderNumber, + requestCompleteOrder); + + commandResult = await _mediator.Send(requestCompleteOrder); + } + + if (!commandResult) + { + return BadRequest(); + } + + return Ok(); + } + [Route("ship")] [HttpPut] [ProducesResponseType(StatusCodes.Status200OK)] diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 8e452c42f..2670f5e49 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -156,6 +156,20 @@ public class Order AddDomainEvent(new OrderCancelledDomainEvent(this)); } + public void SetCompletedStatus() + { + // make sure it is shipped and paid before completing + if (_orderStatusId == OrderStatus.Paid.Id || + _orderStatusId == OrderStatus.Shipped.Id) + { + StatusChangeException(OrderStatus.Completed); + } + + _orderStatusId = OrderStatus.Completed.Id; + _description = $"The order is completed."; + AddDomainEvent(new OrderCompletedDomainEvent(this)); // a postponed way to raise domain events + } + public void SetCancelledStatusWhenStockIsRejected(IEnumerable orderStockRejectedItems) { if (_orderStatusId == OrderStatus.AwaitingValidation.Id) diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs index 8c3cc50fb..91657d6e6 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs @@ -11,6 +11,7 @@ public class OrderStatus public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant()); public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant()); public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant()); + public static OrderStatus Completed = new OrderStatus(7, nameof(Completed).ToLowerInvariant()); public OrderStatus(int id, string name) : base(id, name) @@ -18,7 +19,7 @@ public class OrderStatus } public static IEnumerable List() => - new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled }; + new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled, Completed }; public static OrderStatus FromName(string name) { diff --git a/src/Services/Ordering/Ordering.Domain/Events/OrderCompletedDomainEvent.cs b/src/Services/Ordering/Ordering.Domain/Events/OrderCompletedDomainEvent.cs new file mode 100644 index 000000000..bf7255c6f --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/Events/OrderCompletedDomainEvent.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events; + +public class OrderCompletedDomainEvent : INotification +{ + public Order Order { get; } + + public OrderCompletedDomainEvent(Order order) + { + Order = order; + } +} +