Files
pos-system/services/order-service-net/src/OrderService.Domain/AggregatesModel/OrderAggregate/Order.cs

223 lines
6.6 KiB
C#

// EN: Order aggregate root - the orchestrator.
// VI: Aggregate root Order - bộ điều phối.
using OrderService.Domain.Events;
using OrderService.Domain.Exceptions;
using OrderService.Domain.SeedWork;
namespace OrderService.Domain.AggregatesModel.OrderAggregate;
/// <summary>
/// EN: Order aggregate root - orchestrates order processing across verticals.
/// VI: Aggregate root Order - điều phối xử lý đơn hàng qua các ngành dọc.
/// </summary>
public class Order : Entity, IAggregateRoot
{
private Guid _shopId;
private Guid? _customerId;
private OrderStatus _status = null!;
private decimal _totalAmount;
private DateTime _createdAt;
private DateTime? _updatedAt;
private decimal _discountAmount;
private string? _discountType;
private string? _discountReference;
private readonly List<OrderItem> _items = new();
/// <summary>
/// EN: Shop ID that owns this order.
/// VI: ID shop sở hữu đơn hàng này.
/// </summary>
public Guid ShopId => _shopId;
/// <summary>
/// EN: Customer ID (optional for walk-in customers).
/// VI: ID khách hàng (tùy chọn cho khách vãng lai).
/// </summary>
public Guid? CustomerId => _customerId;
/// <summary>
/// EN: Order status.
/// VI: Trạng thái đơn hàng.
/// </summary>
public OrderStatus Status => _status;
/// <summary>
/// EN: Status ID for EF Core mapping.
/// VI: Status ID cho EF Core mapping.
/// </summary>
public int StatusId { get; private set; }
/// <summary>
/// EN: Total amount of the order.
/// VI: Tổng số tiền của đơn hàng.
/// </summary>
public decimal TotalAmount => _totalAmount;
/// <summary>
/// EN: Order items (line items).
/// VI: Các items trong đơn hàng (dòng hàng).
/// </summary>
public decimal DiscountAmount => _discountAmount;
public string? DiscountType => _discountType;
public string? DiscountReference => _discountReference;
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
/// <summary>
/// EN: Creation timestamp.
/// VI: Thời gian tạo.
/// </summary>
public DateTime CreatedAt => _createdAt;
/// <summary>
/// EN: Last update timestamp.
/// VI: Thời gian cập nhật cuối.
/// </summary>
public DateTime? UpdatedAt => _updatedAt;
/// <summary>
/// EN: Private constructor for EF Core.
/// VI: Constructor private cho EF Core.
/// </summary>
protected Order()
{
}
/// <summary>
/// EN: Create a new order.
/// VI: Tạo đơn hàng mới.
/// </summary>
public Order(Guid shopId, Guid? customerId = null)
{
if (shopId == Guid.Empty)
throw new DomainException("Shop ID cannot be empty");
Id = Guid.NewGuid();
_shopId = shopId;
_customerId = customerId;
_status = OrderStatus.Draft;
StatusId = OrderStatus.Draft.Id;
_totalAmount = 0;
_createdAt = DateTime.UtcNow;
AddDomainEvent(new OrderCreatedDomainEvent(this));
}
/// <summary>
/// EN: Add an item to the order.
/// VI: Thêm item vào đơn hàng.
/// </summary>
public void AddItem(OrderItem item)
{
if (_status != OrderStatus.Draft)
throw new DomainException("Cannot add items to non-draft orders");
_items.Add(item);
RecalculateTotal();
_updatedAt = DateTime.UtcNow;
}
/// <summary>
/// EN: Mark order as validated.
/// VI: Đánh dấu đơn hàng đã xác thực.
/// </summary>
public void MarkAsValidated()
{
if (_status != OrderStatus.Draft)
throw new DomainException($"Cannot validate order with status {_status.Name}");
if (!_items.Any())
throw new DomainException("Cannot validate order with no items");
_status = OrderStatus.Validated;
StatusId = OrderStatus.Validated.Id;
_updatedAt = DateTime.UtcNow;
}
/// <summary>
/// EN: Mark order as paid.
/// VI: Đánh dấu đơn hàng đã thanh toán.
/// </summary>
public void MarkAsPaid()
{
if (_status != OrderStatus.Validated)
throw new DomainException($"Cannot mark as paid order with status {_status.Name}");
_status = OrderStatus.Paid;
StatusId = OrderStatus.Paid.Id;
_updatedAt = DateTime.UtcNow;
AddDomainEvent(new OrderPaidDomainEvent(this));
}
/// <summary>
/// EN: Mark order as processing.
/// VI: Đánh dấu đơn hàng đang xử lý.
/// </summary>
public void MarkAsProcessing()
{
if (_status != OrderStatus.Paid)
throw new DomainException($"Cannot process order with status {_status.Name}");
_status = OrderStatus.Processing;
StatusId = OrderStatus.Processing.Id;
_updatedAt = DateTime.UtcNow;
}
/// <summary>
/// EN: Mark order as completed.
/// VI: Đánh dấu đơn hàng hoàn thành.
/// </summary>
public void MarkAsCompleted()
{
if (_status != OrderStatus.Processing)
throw new DomainException($"Cannot complete order with status {_status.Name}");
_status = OrderStatus.Completed;
StatusId = OrderStatus.Completed.Id;
_updatedAt = DateTime.UtcNow;
AddDomainEvent(new OrderCompletedDomainEvent(this));
}
/// <summary>
/// EN: Cancel the order.
/// VI: Hủy đơn hàng.
/// </summary>
public void Cancel(string reason)
{
if (_status == OrderStatus.Completed)
throw new DomainException("Cannot cancel completed order");
if (_status == OrderStatus.Cancelled)
throw new DomainException("Order is already cancelled");
_status = OrderStatus.Cancelled;
StatusId = OrderStatus.Cancelled.Id;
_updatedAt = DateTime.UtcNow;
AddDomainEvent(new OrderCancelledDomainEvent(this, reason));
}
/// <summary>
/// EN: Apply discount to order.
/// VI: Áp dụng giảm giá cho đơn hàng.
/// </summary>
public void ApplyDiscount(decimal amount, string? type = null, string? reference = null)
{
if (amount < 0) throw new DomainException("Discount amount cannot be negative");
_discountAmount = amount;
_discountType = type;
_discountReference = reference;
RecalculateTotal();
_updatedAt = DateTime.UtcNow;
}
private void RecalculateTotal()
{
_totalAmount = _items.Sum(i => i.TotalPrice) - _discountAmount;
if (_totalAmount < 0) _totalAmount = 0;
}
}