feat(order-processing): execute order item strategies during order creation and add kitchen ticket API with session management.

This commit is contained in:
Ho Ngoc Hai
2026-03-05 06:19:18 +07:00
parent 926d4ee83c
commit 802c03995a
4 changed files with 62 additions and 34 deletions

View File

@@ -6,6 +6,7 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using FnbEngine.API.Application.Commands;
using FnbEngine.API.Application.Queries;
using FnbEngine.Domain.AggregatesModel.SessionAggregate;
namespace FnbEngine.API.Controllers;
@@ -15,10 +16,12 @@ namespace FnbEngine.API.Controllers;
public class KitchenController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ISessionRepository _sessionRepository;
public KitchenController(IMediator mediator)
public KitchenController(IMediator mediator, ISessionRepository sessionRepository)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_sessionRepository = sessionRepository ?? throw new ArgumentNullException(nameof(sessionRepository));
}
/// <summary>
@@ -62,6 +65,42 @@ public class KitchenController : ControllerBase
var result = await _mediator.Send(new UpdateTicketStatusCommand(id, request.Status), ct);
return Ok(new ApiResponse<bool> { Success = true, Data = result });
}
/// <summary>
/// EN: Create a kitchen ticket from order-service.
/// VI: Tạo phiếu bếp từ order-service.
/// </summary>
[HttpPost("tickets")]
[ProducesResponseType(typeof(CreateTicketResponse), 200)]
public async Task<ActionResult<CreateTicketResponse>> CreateTicket(
[FromBody] CreateTicketRequest request,
CancellationToken ct = default)
{
// EN: Find or create an active session for this shop
// VI: Tìm hoặc tạo session hoạt động cho shop này
var sessions = await _sessionRepository.GetByShopAsync(request.ShopId, ct);
var activeSession = sessions.FirstOrDefault(s => s.Status == "Active");
if (activeSession == null)
{
activeSession = new Session(request.ShopId, request.ShopId, 1);
await _sessionRepository.AddAsync(activeSession, ct);
await _sessionRepository.UnitOfWork.SaveEntitiesAsync(ct);
}
// EN: Create kitchen ticket via MediatR
// VI: Tạo phiếu bếp qua MediatR
var ticketId = await _mediator.Send(new CreateKitchenTicketCommand(
activeSession.Id,
request.ProductId,
request.ProductName,
null,
0
), ct);
return Ok(new CreateTicketResponse(ticketId));
}
// ═══ RECIPE ENDPOINTS ═══
[HttpGet("recipes")]
@@ -102,3 +141,12 @@ public class KitchenController : ControllerBase
}
public record UpdateStatusRequest(string Status);
public record CreateTicketRequest(
Guid ProductId,
string ProductName,
Guid ShopId,
int Quantity,
string? Notes);
public record CreateTicketResponse(Guid TicketId);

View File

@@ -67,6 +67,14 @@ public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Cre
}
}
// EN: Execute all items via their strategies (e.g., create kitchen tickets for PreparedFood)
// VI: Thực thi tất cả items qua strategies (vd: tạo phiếu bếp cho PreparedFood)
foreach (var item in order.Items)
{
var strategy = GetStrategy(item.ProductType);
await strategy.ExecuteAsync(item, request.ShopId, cancellationToken);
}
// EN: Apply discount if provided
// VI: Áp dụng giảm giá nếu có
if (request.DiscountAmount is > 0)

View File

@@ -4,27 +4,23 @@
using MediatR;
using OrderService.Domain.AggregatesModel.OrderAggregate;
using OrderService.Domain.Exceptions;
using OrderService.Domain.Strategies;
namespace OrderService.API.Application.Commands;
/// <summary>
/// EN: Handler for processing payment and executing order items via strategies.
/// VI: Handler xử lý thanh toán và thực thi order items qua strategies.
/// EN: Handler for processing payment.
/// VI: Handler xử lý thanh toán.
/// </summary>
public class PayOrderCommandHandler : IRequestHandler<PayOrderCommand, PayOrderResult>
{
private readonly IOrderRepository _orderRepository;
private readonly IEnumerable<ILineItemStrategy> _strategies;
private readonly ILogger<PayOrderCommandHandler> _logger;
public PayOrderCommandHandler(
IOrderRepository orderRepository,
IEnumerable<ILineItemStrategy> strategies,
ILogger<PayOrderCommandHandler> logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_strategies = strategies ?? throw new ArgumentNullException(nameof(strategies));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -55,21 +51,8 @@ public class PayOrderCommandHandler : IRequestHandler<PayOrderCommand, PayOrderR
// VI: Đánh dấu order là đã thanh toán (sẽ validate status bên trong)
order.MarkAsPaid();
// EN: Execute all items via their strategies
// VI: Thực thi tất cả items qua strategies của chúng
foreach (var item in order.Items)
{
var strategy = GetStrategy(item.ProductType);
await strategy.ExecuteAsync(item, request.ShopId, cancellationToken);
_logger.LogInformation(
"EN: Executed item {ProductName} via {StrategyType} / VI: Đã thực thi item {ProductName} qua {StrategyType}",
item.ProductName,
strategy.GetType().Name);
}
// EN: Mark order as processing
// VI: Đánh dấu order là đang xử lý
// EN: Mark order as processing (strategies already executed at order creation)
// VI: Đánh dấu order là đang xử lý (strategies đã được thực thi khi tạo order)
order.MarkAsProcessing();
// EN: Save changes
@@ -82,15 +65,4 @@ public class PayOrderCommandHandler : IRequestHandler<PayOrderCommand, PayOrderR
return new PayOrderResult(true, order.Status.Name);
}
private ILineItemStrategy GetStrategy(string productType)
{
var strategy = _strategies.FirstOrDefault(s => s.SupportedType == productType);
if (strategy == null)
{
throw new InvalidOperationException(
$"EN: No strategy found for product type / VI: Không tìm thấy strategy cho loại sản phẩm: {productType}");
}
return strategy;
}
}

View File

@@ -46,7 +46,7 @@ public class FnbEngineClient
notes);
var response = await _httpClient.PostAsJsonAsync(
"/api/v1/fnb/kitchen-tickets",
"/api/v1/kitchen/tickets",
request,
cancellationToken);