223 lines
6.6 KiB
C#
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;
|
|
}
|
|
}
|