@ -1,227 +0,0 @@ | |||
using System; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||
using Microsoft.EntityFrameworkCore.Metadata; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; | |||
namespace Ordering.API.Infrastructure.Migrations | |||
{ | |||
[DbContext(typeof(OrderingContext))] | |||
[Migration("20161124133626_InitialModel")] | |||
partial class InitialModel | |||
{ | |||
protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||
{ | |||
modelBuilder | |||
.HasAnnotation("ProductVersion", "1.0.1") | |||
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Address", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd(); | |||
b.Property<string>("City"); | |||
b.Property<string>("Country"); | |||
b.Property<string>("State"); | |||
b.Property<string>("Street"); | |||
b.Property<string>("ZipCode"); | |||
b.HasKey("Id"); | |||
b.ToTable("address","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Buyer", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq") | |||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||
b.Property<string>("FullName") | |||
.IsRequired() | |||
.HasAnnotation("MaxLength", 200); | |||
b.HasKey("Id"); | |||
b.HasIndex("FullName") | |||
.IsUnique(); | |||
b.ToTable("buyers","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.CardType", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasDefaultValue(1); | |||
b.Property<string>("Name") | |||
.IsRequired() | |||
.HasAnnotation("MaxLength", 200); | |||
b.HasKey("Id"); | |||
b.ToTable("cardtypes","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Order", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq") | |||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||
b.Property<int>("BuyerId"); | |||
b.Property<DateTime>("OrderDate"); | |||
b.Property<int>("PaymentId"); | |||
b.Property<int?>("ShippingAddressId"); | |||
b.Property<int>("StatusId"); | |||
b.HasKey("Id"); | |||
b.HasIndex("BuyerId"); | |||
b.HasIndex("PaymentId"); | |||
b.HasIndex("ShippingAddressId"); | |||
b.HasIndex("StatusId"); | |||
b.ToTable("orders","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.OrderItem", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd(); | |||
b.Property<decimal>("Discount"); | |||
b.Property<int>("OrderId"); | |||
b.Property<int>("ProductId"); | |||
b.Property<string>("ProductName") | |||
.IsRequired(); | |||
b.Property<decimal>("UnitPrice"); | |||
b.Property<int>("Units") | |||
.ValueGeneratedOnAdd() | |||
.HasAnnotation("SqlServer:DefaultValue", 1); | |||
b.HasKey("Id"); | |||
b.HasIndex("OrderId"); | |||
b.ToTable("orderItems","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.OrderStatus", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasDefaultValue(1); | |||
b.Property<string>("Name") | |||
.IsRequired() | |||
.HasAnnotation("MaxLength", 200); | |||
b.HasKey("Id"); | |||
b.ToTable("orderstatus","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Payment", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq") | |||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||
b.Property<int>("BuyerId"); | |||
b.Property<string>("CardHolderName") | |||
.IsRequired() | |||
.HasAnnotation("MaxLength", 200); | |||
b.Property<string>("CardNumber") | |||
.IsRequired() | |||
.HasAnnotation("MaxLength", 25); | |||
b.Property<int>("CardTypeId"); | |||
b.Property<DateTime>("Expiration"); | |||
b.Property<string>("SecurityNumber"); | |||
b.HasKey("Id"); | |||
b.HasIndex("BuyerId"); | |||
b.HasIndex("CardTypeId"); | |||
b.ToTable("payments","ordering"); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Order", b => | |||
{ | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.Buyer", "Buyer") | |||
.WithMany() | |||
.HasForeignKey("BuyerId") | |||
.OnDelete(DeleteBehavior.Cascade); | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.Payment", "Payment") | |||
.WithMany() | |||
.HasForeignKey("PaymentId"); | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.Address", "ShippingAddress") | |||
.WithMany() | |||
.HasForeignKey("ShippingAddressId"); | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.OrderStatus", "Status") | |||
.WithMany() | |||
.HasForeignKey("StatusId") | |||
.OnDelete(DeleteBehavior.Cascade); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.OrderItem", b => | |||
{ | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.Order") | |||
.WithMany("OrderItems") | |||
.HasForeignKey("OrderId") | |||
.OnDelete(DeleteBehavior.Cascade); | |||
}); | |||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.Payment", b => | |||
{ | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.Buyer") | |||
.WithMany("Payments") | |||
.HasForeignKey("BuyerId") | |||
.OnDelete(DeleteBehavior.Cascade); | |||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.CardType", "CardType") | |||
.WithMany() | |||
.HasForeignKey("CardTypeId") | |||
.OnDelete(DeleteBehavior.Cascade); | |||
}); | |||
} | |||
} | |||
} |
@ -1,28 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
namespace Ordering.API.Infrastructure.Migrations | |||
{ | |||
public partial class PaymentMethodWithAlias : Migration | |||
{ | |||
protected override void Up(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.AddColumn<string>( | |||
name: "Alias", | |||
schema: "ordering", | |||
table: "payments", | |||
maxLength: 200, | |||
nullable: false, | |||
defaultValue: ""); | |||
} | |||
protected override void Down(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.DropColumn( | |||
name: "Alias", | |||
schema: "ordering", | |||
table: "payments"); | |||
} | |||
} | |||
} |
@ -1,35 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Models | |||
{ | |||
//TO DO: Confirm if this class is not needed, if not, remove it | |||
//(CDLTLL) | |||
public class NewOrderViewModel | |||
{ | |||
public string ShippingCity { get; set; } | |||
public string ShippingStreet { get; set; } | |||
public string ShippingState { get; set; } | |||
public string ShippingCountry { get; set; } | |||
public string CardType { get; set; } | |||
public string CardNumber { get; set; } | |||
public string CardHolderName { get; set; } | |||
public DateTime CardExpiration { get; set; } | |||
public string CardSecurityNumber { get; set; } | |||
public List<OrderItemViewModel> Items { get; set; } | |||
public NewOrderViewModel() | |||
{ | |||
Items = new List<OrderItemViewModel>(); | |||
} | |||
} | |||
} |
@ -1,19 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Models | |||
{ | |||
//TO DO: Confirm if this class is not needed, if not, remove it | |||
//(CDLTLL) | |||
public class OrderItemViewModel | |||
{ | |||
public int ProductId { get; set; } | |||
public string ProductName { get; set; } | |||
public decimal UnitPrice { get; set; } | |||
public decimal Discount { get; set; } | |||
public int Units { get; set; } | |||
public string PictureUrl { get; set; } | |||
} | |||
} |
@ -1,5 +0,0 @@ | |||
| |||
REFACTORING TO DO: | |||
//TO DO: Confirm if these ViewModel classes are still needed, if not, remove it | |||
//and remove the related Unit Tests | |||
//(CDLTLL) |
@ -1,27 +1,50 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using System; | |||
using System.Collections.Generic; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate | |||
{ | |||
public class Buyer | |||
:Entity,IAggregateRoot | |||
: Entity, IAggregateRoot | |||
{ | |||
public string FullName { get; private set; } | |||
public HashSet<Payment> Payments { get; private set; } | |||
private HashSet<Payment> _payments; | |||
public IEnumerable<Payment> Payments => _payments?.ToList().AsEnumerable(); | |||
protected Buyer() { } | |||
public Buyer(string IdentityGuid) | |||
public Buyer(string identity) | |||
{ | |||
if (String.IsNullOrWhiteSpace(identity)) | |||
{ | |||
throw new ArgumentNullException(nameof(identity)); | |||
} | |||
FullName = identity; | |||
_payments = new HashSet<Payment>(); | |||
} | |||
public Payment AddPayment(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) | |||
{ | |||
if (String.IsNullOrWhiteSpace(IdentityGuid)) | |||
var existingPayment = Payments.Where(p => p.IsEqualTo(cardTypeId, cardNumber, expiration)) | |||
.SingleOrDefault(); | |||
if (existingPayment != null) | |||
{ | |||
throw new ArgumentNullException(nameof(IdentityGuid)); | |||
return existingPayment; | |||
} | |||
else | |||
{ | |||
var payment = new Payment(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); | |||
_payments.Add(payment); | |||
this.FullName = IdentityGuid; | |||
this.Payments = new HashSet<Payment>(); | |||
return payment; | |||
} | |||
} | |||
} | |||
} |
@ -1,22 +1,26 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using System; | |||
using System; | |||
//(CDLTLL) | |||
//TO DO: Need to convert this entity to a Value-Object (Address VO) | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
{ | |||
public class Address | |||
: Entity | |||
{ | |||
public String Street { get; set; } | |||
public String Street { get; } | |||
public String City { get; set; } | |||
public String City { get; } | |||
public String State { get; set; } | |||
public String State { get; } | |||
public String Country { get; set; } | |||
public String Country { get; } | |||
public String ZipCode { get; set; } | |||
public String ZipCode { get; } | |||
public Address(string street, string city, string state, string country, string zipcode) | |||
{ | |||
Street = street; | |||
City = city; | |||
State = state; | |||
Country = country; | |||
ZipCode = zipcode; | |||
} | |||
} | |||
} |
@ -1,79 +1,76 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; | |||
using System; | |||
using System.Collections.Generic; | |||
//(CDLTLL) | |||
//TO DO: Need to add additional Domain Logic to this Aggregate-Root for | |||
//scenarios related to Order state changes, stock availability validation, etc. | |||
public class Order | |||
: Entity, IAggregateRoot | |||
: Entity | |||
{ | |||
public int BuyerId { get; private set; } | |||
public Buyer Buyer { get; private set; } | |||
public DateTime OrderDate { get; private set; } | |||
public int StatusId { get; private set; } | |||
private string _street; | |||
private string _city; | |||
private string _state; | |||
private string _country; | |||
private string _zipCode; | |||
private DateTime _orderDate; | |||
public OrderStatus Status { get; private set; } | |||
public ICollection<OrderItem> OrderItems { get; private set; } | |||
public int? ShippingAddressId { get; private set; } | |||
public Buyer Buyer { get; private set; } | |||
int _buyerId; | |||
public Address ShippingAddress { get; private set; } | |||
public OrderStatus OrderStatus { get; private set; } | |||
int _orderStatusId; | |||
public int PaymentId { get; private set; } | |||
HashSet<OrderItem> _orderItems; | |||
public IEnumerable<OrderItem> OrderItems => _orderItems.ToList().AsEnumerable(); | |||
public Payment Payment { get; private set; } | |||
int _paymentId; | |||
protected Order() { } | |||
public Order(int buyerId, int paymentId) | |||
public Order(int buyerId, int paymentId, Address address) | |||
{ | |||
BuyerId = buyerId; | |||
PaymentId = paymentId; | |||
StatusId = OrderStatus.InProcess.Id; | |||
OrderDate = DateTime.UtcNow; | |||
OrderItems = new List<OrderItem>(); | |||
_buyerId = buyerId; | |||
_paymentId = paymentId; | |||
_orderStatusId = OrderStatus.InProcess.Id; | |||
_orderDate = DateTime.UtcNow; | |||
_street = address.Street; | |||
_city = address.City; | |||
_state = address.State; | |||
_country = address.Country; | |||
_zipCode = address.ZipCode; | |||
_orderItems = new HashSet<OrderItem>(); | |||
} | |||
public void SetAddress(Address address) | |||
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, int units = 1) | |||
{ | |||
if (address == null) | |||
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId) | |||
.SingleOrDefault(); | |||
if (existingOrderForProduct != null) | |||
{ | |||
throw new ArgumentNullException(nameof(address)); | |||
//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 | |||
ShippingAddress = address; | |||
} | |||
var orderItem = new OrderItem(productId, productName, unitPrice, discount, units); | |||
//TO DO: | |||
// (CDLTLL) Bad implementation, needs to be changed. | |||
// The AddOrderItem should have the needed data params | |||
// instead of an already created OrderItem object. | |||
// The Aggregate-Root is responsible for any Update/Creation of its child entities | |||
// If we are providing an already created OrderItem, that was created from the outside | |||
// and the AggregateRoot cannot control/validate any rule/invariants/consistency. | |||
public void AddOrderItem(OrderItem item) | |||
{ | |||
//TO DO: Bad implementation, need to change. | |||
// The code "new OrderItem(params);" should be done here | |||
// Plus any validation/rule related | |||
OrderItems.Add(item); | |||
//(CDLTLL) | |||
// TO DO: Some more logic needs to be added here, | |||
// Like consolidating items that are the same product in one single OrderItem with several units | |||
// Also validation logic could be added here (like ensuring it is adding at least one item unit) | |||
//Or, If there are different amounts of discounts per added OrderItem | |||
//but the product Id is the same to existing Order Items, you should | |||
//apply the higher discount, or any other domain logic that makes sense. | |||
_orderItems.Add(orderItem); | |||
} | |||
} | |||
} | |||
} |
@ -1,28 +1,74 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
using System; | |||
//TO DO: | |||
//(CDLTLL) Wrong implementation. Need to put Setters as private | |||
// and only be able to update the OrderItem through specific methods, if needed, so we can | |||
// have validations/control/logic in those "update or set methods". | |||
//We also need to have a constructor with the needed params, we must not use the "setters".. | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate | |||
{ | |||
public class OrderItem | |||
:Entity | |||
: Entity | |||
{ | |||
public int ProductId { get; set; } | |||
private string _productName; | |||
private string _pictureUrl; | |||
private int _orderId; | |||
private decimal _unitPrice; | |||
private decimal _discount; | |||
private int _units; | |||
public int ProductId { get; private set; } | |||
protected OrderItem() { } | |||
public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, int units = 1) | |||
{ | |||
if (units <= 0) | |||
{ | |||
throw new ArgumentNullException("Invalid number of units"); | |||
} | |||
if ((unitPrice * units) < discount) | |||
{ | |||
throw new ArgumentException("The total of order item is lower than applied discount"); | |||
} | |||
ProductId = productId; | |||
public string ProductName { get; set; } | |||
_productName = productName; | |||
_unitPrice = unitPrice; | |||
_discount = discount; | |||
_units = units; | |||
} | |||
public string PictureUrl { get; set; } | |||
public void SetPictureUri(string pictureUri) | |||
{ | |||
if (!String.IsNullOrWhiteSpace(pictureUri)) | |||
{ | |||
_pictureUrl = pictureUri; | |||
} | |||
} | |||
public int OrderId { get; set; } | |||
public decimal GetCurrentDiscount() | |||
{ | |||
return _discount; | |||
} | |||
public decimal UnitPrice { get; set; } | |||
public void SetNewDiscount(decimal discount) | |||
{ | |||
if (discount < 0) | |||
{ | |||
throw new ArgumentException("Discount is not valid"); | |||
} | |||
public decimal Discount { get; set; } | |||
_discount = discount; | |||
} | |||
public int Units { get; set; } | |||
public void AddUnits(int units) | |||
{ | |||
if (units < 0) | |||
{ | |||
throw new ArgumentException("Invalid units"); | |||
} | |||
_units += units; | |||
} | |||
} | |||
} |