188 lines
7.0 KiB
C#

using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Events;
using Ordering.Domain.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
{
public class Order
: Entity, IAggregateRoot
{
// DDD Patterns comment
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private DateTime _orderDate;
public Address Address { get; private set; }
private int? _buyerId;
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
private string _description;
// DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
private readonly List<OrderItem> _orderItems;
public IEnumerable<OrderItem> OrderItems => _orderItems.AsReadOnly();
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
private int? _paymentMethodId;
protected Order() { _orderItems = new List<OrderItem>(); }
public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
{
_orderItems = new List<OrderItem>();
_buyerId = buyerId;
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.Submited.Id;
_orderDate = DateTime.UtcNow;
Address = address;
// Add the OrderStarterDomainEvent to the domain events collection
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
AddOrderStartedDomainEvent(userId, cardTypeId, cardNumber,
cardSecurityNumber, cardHolderName, cardExpiration);
}
// DDD Patterns comment
// This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order,
// so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot
// in order to maintain consistency between the whole Aggregate.
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
{
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
.SingleOrDefault();
if (existingOrderForProduct != null)
{
//if previous line exist modify it with higher discount and units..
if (discount > existingOrderForProduct.GetCurrentDiscount())
{
existingOrderForProduct.SetNewDiscount(discount);
existingOrderForProduct.AddUnits(units);
}
}
else
{
//add validated new order item
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
_orderItems.Add(orderItem);
}
}
public void SetPaymentId(int id)
{
_paymentMethodId = id;
}
public void SetBuyerId(int id)
{
_buyerId = id;
}
#region Status Changes
public void SetSubmitedStatus()
{
_orderStatusId = OrderStatus.Submited.Id;
}
public void SetAwaitingValidationStatus()
{
if (_orderStatusId != OrderStatus.Submited.Id)
{
StatusChangeException();
}
_orderStatusId = OrderStatus.AwaitingValidation.Id;
AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, OrderItems));
}
public void SetStockConfirmedStatus(IEnumerable<int> orderStockNotConfirmedItems = null)
{
if (_orderStatusId != OrderStatus.AwaitingValidation.Id)
{
StatusChangeException();
}
if (orderStockNotConfirmedItems is null)
{
OrderStatus = OrderStatus.StockConfirmed;
_description = "All the items were confirmed with available stock.";
AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id));
}
else
{
OrderStatus = OrderStatus.Cancelled;
var itemsStockNotConfirmedProductNames = OrderItems
.Where(c => orderStockNotConfirmedItems.Contains(c.ProductId))
.Select(c => c.GetOrderItemProductName());
var itemsStockNotConfirmedDescription = string.Join(", ", itemsStockNotConfirmedProductNames);
_description = $"The product items don't have stock: ({itemsStockNotConfirmedDescription}).";
}
}
public void SetPaidStatus()
{
if (_orderStatusId != OrderStatus.StockConfirmed.Id)
{
StatusChangeException();
}
_orderStatusId = OrderStatus.Paid.Id;
_description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\"";
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
}
public void SetShippedStatus()
{
if (_orderStatusId != OrderStatus.Paid.Id)
{
StatusChangeException();
}
_orderStatusId = OrderStatus.Shipped.Id;
_description = "";
//Call Domain Event
}
#endregion
private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{
var orderStartedDomainEvent = new OrderStartedDomainEvent(
this, userId, cardTypeId, cardNumber, cardSecurityNumber,
cardHolderName, cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);
}
private void StatusChangeException()
{
throw new OrderingDomainException("Not able to process order event. Reason: no valid order status change");
}
}
}