diff --git a/src/Common/SharedKernel/Domain/Identifiers/ProductId.cs b/src/Common/SharedKernel/Domain/Identifiers/ProductId.cs new file mode 100644 index 0000000..ece9f4f --- /dev/null +++ b/src/Common/SharedKernel/Domain/Identifiers/ProductId.cs @@ -0,0 +1,3 @@ +namespace SharedKernel.Domain.Identifiers; + +public record ProductId(Guid Value); \ No newline at end of file diff --git a/src/Modules/Orders/Modules.Orders.Domain/Orders/LineItem.cs b/src/Modules/Orders/Modules.Orders.Domain/Orders/LineItem.cs index 91beb61..ec3225f 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Orders/LineItem.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Orders/LineItem.cs @@ -1,9 +1,7 @@ using Ardalis.GuardClauses; - -using Modules.Orders.Domain.Products; - using SharedKernel.Domain.Base; using SharedKernel.Domain.Entities; +using SharedKernel.Domain.Identifiers; namespace Modules.Orders.Domain.Orders; @@ -13,7 +11,7 @@ public class LineItem : Entity public required ProductId ProductId { get; init; } - public Product? Product { get; init; } + //public Product? Product { get; init; } // Detatch price from product to capture the price at the time of purchase public required Money Price { get; init; } @@ -47,7 +45,8 @@ internal static LineItem Create(OrderId orderId, ProductId productId, Money pric internal void RemoveQuantity(int quantity) { - Guard.Against.Expression(_ => Quantity - quantity <= 0, quantity, "Can't remove all units. Remove the entire item instead."); + Guard.Against.Expression(_ => Quantity - quantity <= 0, quantity, + "Can't remove all units. Remove the entire item instead."); Quantity -= quantity; } -} \ No newline at end of file +} diff --git a/src/Modules/Orders/Modules.Orders.Domain/Orders/Order.cs b/src/Modules/Orders/Modules.Orders.Domain/Orders/Order.cs index 684945b..a2c490b 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Orders/Order.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Orders/Order.cs @@ -1,11 +1,9 @@ using Ardalis.GuardClauses; - using Modules.Orders.Domain.Customers; -using Modules.Orders.Domain.Products; - using SharedKernel.Domain.Base; using SharedKernel.Domain.Entities; using SharedKernel.Domain.Exceptions; +using SharedKernel.Domain.Identifiers; namespace Modules.Orders.Domain.Orders; @@ -61,10 +59,12 @@ public static Order Create(CustomerId customerId) public LineItem AddLineItem(ProductId productId, Money price, int quantity) { - Guard.Against.Expression(_ => Status != OrderStatus.PendingPayment, Status, "Can't modify order once payment is done"); + Guard.Against.Expression(_ => Status != OrderStatus.PendingPayment, Status, + "Can't modify order once payment is done"); if (OrderCurrency != null && OrderCurrency != price.Currency) - throw new DomainException($"Cannot add line item with currency {price.Currency} to and order than already contains a currency of {price.Currency}"); + throw new DomainException( + $"Cannot add line item with currency {price.Currency} to and order than already contains a currency of {price.Currency}"); var existingLineItem = _lineItems.FirstOrDefault(li => li.ProductId == productId); if (existingLineItem != null) @@ -82,7 +82,8 @@ public LineItem AddLineItem(ProductId productId, Money price, int quantity) public void RemoveLineItem(ProductId productId) { - Guard.Against.Expression(_ => Status != OrderStatus.PendingPayment, Status, "Can't modify order once payment is done"); + Guard.Against.Expression(_ => Status != OrderStatus.PendingPayment, Status, + "Can't modify order once payment is done"); var lineItem = _lineItems.RemoveAll(x => x.ProductId == productId); } @@ -123,4 +124,4 @@ public void ShipOrder(TimeProvider timeProvider) ShippingDate = timeProvider.GetUtcNow(); Status = OrderStatus.InTransit; } -} \ No newline at end of file +} diff --git a/src/Modules/Orders/Modules.Orders.Domain/Products/Product.cs b/src/Modules/Orders/Modules.Orders.Domain/Products/Product.cs index f71d4fd..32728bd 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Products/Product.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Products/Product.cs @@ -1,63 +1,63 @@ -using Ardalis.GuardClauses; - -using SharedKernel.Domain.Base; -using SharedKernel.Domain.Entities; - -namespace Modules.Orders.Domain.Products; - -public class Product : AggregateRoot -{ - //public CategoryId CategoryId { get; set; } = null!; - - //public Category Category { get; set; } = null!; - - public string Name { get; private set; } = null!; - - public Money Price { get; private set; } = null!; - - public Sku Sku { get; private set; } = null!; - - private Product() { } - - // NOTE: Need to use a factory, as EF does not let owned entities (i.e Money & Sku) be passed via the constructor - public static Product Create(string name, Money price, Sku sku/*, CategoryId categoryId*/) - { - Guard.Against.NullOrWhiteSpace(name); - Guard.Against.Null(sku); - Guard.Against.Null(price); - Guard.Against.ZeroOrNegative(price.Amount); - //Guard.Against.Null(categoryId); - - var product = new Product - { - Id = new ProductId(Guid.NewGuid()), -// CategoryId = categoryId, - Name = name, - Price = price, - Sku = sku - }; - - product.AddDomainEvent(ProductCreatedEvent.Create(product)); - - return product; - } - - public void UpdateName(string name) - { - Guard.Against.NullOrWhiteSpace(name); - Name = name; - } - - public void UpdatePrice(Money price) - { - Guard.Against.Null(price); - Guard.Against.ZeroOrNegative(price.Amount); - Price = price; - } - - public void UpdateSku(Sku sku) - { - Guard.Against.Null(sku); - Sku = sku; - } -} \ No newline at end of file +// using Ardalis.GuardClauses; +// +// using SharedKernel.Domain.Base; +// using SharedKernel.Domain.Entities; +// +// namespace Modules.Orders.Domain.Products; +// +// public class Product : AggregateRoot +// { +// //public CategoryId CategoryId { get; set; } = null!; +// +// //public Category Category { get; set; } = null!; +// +// public string Name { get; private set; } = null!; +// +// public Money Price { get; private set; } = null!; +// +// public Sku Sku { get; private set; } = null!; +// +// private Product() { } +// +// // NOTE: Need to use a factory, as EF does not let owned entities (i.e Money & Sku) be passed via the constructor +// public static Product Create(string name, Money price, Sku sku/*, CategoryId categoryId*/) +// { +// Guard.Against.NullOrWhiteSpace(name); +// Guard.Against.Null(sku); +// Guard.Against.Null(price); +// Guard.Against.ZeroOrNegative(price.Amount); +// //Guard.Against.Null(categoryId); +// +// var product = new Product +// { +// Id = new ProductId(Guid.NewGuid()), +// // CategoryId = categoryId, +// Name = name, +// Price = price, +// Sku = sku +// }; +// +// product.AddDomainEvent(ProductCreatedEvent.Create(product)); +// +// return product; +// } +// +// public void UpdateName(string name) +// { +// Guard.Against.NullOrWhiteSpace(name); +// Name = name; +// } +// +// public void UpdatePrice(Money price) +// { +// Guard.Against.Null(price); +// Guard.Against.ZeroOrNegative(price.Amount); +// Price = price; +// } +// +// public void UpdateSku(Sku sku) +// { +// Guard.Against.Null(sku); +// Sku = sku; +// } +// } \ No newline at end of file diff --git a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductByIdSpec.cs b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductByIdSpec.cs index 1baa3ee..6d18fef 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductByIdSpec.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductByIdSpec.cs @@ -1,11 +1,11 @@ -using Ardalis.Specification; - -namespace Modules.Orders.Domain.Products; - -public class ProductByIdSpec : Specification, ISingleResultSpecification -{ - public ProductByIdSpec(ProductId id) : base() - { - Query.Where(i => i.Id == id); - } -} \ No newline at end of file +// using Ardalis.Specification; +// +// namespace Modules.Orders.Domain.Products; +// +// public class ProductByIdSpec : Specification, ISingleResultSpecification +// { +// public ProductByIdSpec(ProductId id) : base() +// { +// Query.Where(i => i.Id == id); +// } +// } \ No newline at end of file diff --git a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductCreatedEvent.cs b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductCreatedEvent.cs index cfb6d81..e82fc13 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductCreatedEvent.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductCreatedEvent.cs @@ -1,8 +1,8 @@ -using SharedKernel.Domain.Base; - -namespace Modules.Orders.Domain.Products; - -public record ProductCreatedEvent(ProductId Product, string ProductName) : DomainEvent -{ - public static ProductCreatedEvent Create(Product product) => new(product.Id, product.Name); -} \ No newline at end of file +// using SharedKernel.Domain.Base; +// +// namespace Modules.Orders.Domain.Products; +// +// public record ProductCreatedEvent(ProductId Product, string ProductName) : DomainEvent +// { +// public static ProductCreatedEvent Create(Product product) => new(product.Id, product.Name); +// } \ No newline at end of file diff --git a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductId.cs b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductId.cs index 7a432fa..ead3449 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Products/ProductId.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Products/ProductId.cs @@ -1,3 +1,3 @@ -namespace Modules.Orders.Domain.Products; - -public record ProductId(Guid Value); +// namespace Modules.Orders.Domain.Products; +// +// public record ProductId(Guid Value); \ No newline at end of file diff --git a/src/Modules/Orders/Modules.Orders.Domain/Products/Sku.cs b/src/Modules/Orders/Modules.Orders.Domain/Products/Sku.cs index 91df6bb..74b9add 100644 --- a/src/Modules/Orders/Modules.Orders.Domain/Products/Sku.cs +++ b/src/Modules/Orders/Modules.Orders.Domain/Products/Sku.cs @@ -1,21 +1,21 @@ -namespace Modules.Orders.Domain.Products; - -public record Sku -{ - private const int DefaultLength = 8; - - public string Value { get; } - - private Sku(string value) => Value = value; - - public static Sku? Create(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - if (value.Length != DefaultLength) - return null; - - return new Sku(value); - } -} +// namespace Modules.Orders.Domain.Products; +// +// public record Sku +// { +// private const int DefaultLength = 8; +// +// public string Value { get; } +// +// private Sku(string value) => Value = value; +// +// public static Sku? Create(string value) +// { +// if (string.IsNullOrWhiteSpace(value)) +// return null; +// +// if (value.Length != DefaultLength) +// return null; +// +// return new Sku(value); +// } +// } \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/Category.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/Category.cs new file mode 100644 index 0000000..60cac17 --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/Category.cs @@ -0,0 +1,38 @@ +using Ardalis.GuardClauses; + +using SharedKernel.Domain.Base; +using SharedKernel.Domain.Exceptions; + +namespace Modules.Warehouse.Domain.Categories; + +public class Category : AggregateRoot +{ + public string Name { get; private set; } = default!; + + private Category() { } + + // NOTE: Need to use a factory, as EF does not let owned entities (i.e Money & Sku) be passed via the constructor + public static Category Create(string name, ICategoryService categoryService) + { + var category = new Category + { + Id = new CategoryId(Guid.NewGuid()), + }; + + category.UpdateName(name, categoryService); + + category.AddDomainEvent(new CategoryCreatedEvent(category.Id, category.Name)); + + return category; + } + + public void UpdateName(string name, ICategoryService categoryService) + { + Guard.Against.NullOrWhiteSpace(name); + + if (categoryService.CategoryExists(name)) + throw new DomainException($"Category {name} already exists"); + + Name = name; + } +} \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryByIdSpec.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryByIdSpec.cs new file mode 100644 index 0000000..a7c4adf --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryByIdSpec.cs @@ -0,0 +1,11 @@ +using Ardalis.Specification; + +namespace Modules.Warehouse.Domain.Categories; + +public class CategoryByIdSpec : Specification, ISingleResultSpecification +{ + public CategoryByIdSpec(CategoryId id) : base() + { + Query.Where(i => i.Id == id); + } +} diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryCreatedEvent.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryCreatedEvent.cs new file mode 100644 index 0000000..cfbe648 --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryCreatedEvent.cs @@ -0,0 +1,5 @@ +using SharedKernel.Domain.Base; + +namespace Modules.Warehouse.Domain.Categories; + +public record CategoryCreatedEvent(CategoryId Id, string Name) : DomainEvent; \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryId.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryId.cs new file mode 100644 index 0000000..17953a5 --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/CategoryId.cs @@ -0,0 +1,3 @@ +namespace Modules.Warehouse.Domain.Categories; + +public record CategoryId(Guid Value); diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/ICategoryService.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/ICategoryService.cs new file mode 100644 index 0000000..8269d6f --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Categories/ICategoryService.cs @@ -0,0 +1,6 @@ +namespace Modules.Warehouse.Domain.Categories; + +public interface ICategoryService +{ + bool CategoryExists(string categoryName); +} diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Class1.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Class1.cs deleted file mode 100644 index 2e50ce7..0000000 --- a/src/Modules/Warehouse/Modules.Warehouse.Domain/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Modules.Warehouse.Domain; - -public class Class1 -{ -} \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Modules.Warehouse.Domain.csproj b/src/Modules/Warehouse/Modules.Warehouse.Domain/Modules.Warehouse.Domain.csproj index 3a63532..21c4e65 100644 --- a/src/Modules/Warehouse/Modules.Warehouse.Domain/Modules.Warehouse.Domain.csproj +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Modules.Warehouse.Domain.csproj @@ -6,4 +6,12 @@ enable + + + + + + + + diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Product.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Product.cs new file mode 100644 index 0000000..4d308ad --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Product.cs @@ -0,0 +1,66 @@ +using Ardalis.GuardClauses; + +using Modules.Warehouse.Domain.Categories; + +using SharedKernel.Domain.Base; +using SharedKernel.Domain.Entities; +using SharedKernel.Domain.Identifiers; + +namespace Modules.Warehouse.Domain.Products; + +public class Product : AggregateRoot +{ + public CategoryId CategoryId { get; set; } = null!; + + public Category Category { get; set; } = null!; + + public string Name { get; private set; } = null!; + + public Money Price { get; private set; } = null!; + + public Sku Sku { get; private set; } = null!; + + private Product() { } + + // NOTE: Need to use a factory, as EF does not let owned entities (i.e Money & Sku) be passed via the constructor + public static Product Create(string name, Money price, Sku sku, CategoryId categoryId) + { + Guard.Against.NullOrWhiteSpace(name); + Guard.Against.Null(sku); + Guard.Against.Null(price); + Guard.Against.ZeroOrNegative(price.Amount); + Guard.Against.Null(categoryId); + + var product = new Product + { + Id = new ProductId(Guid.NewGuid()), + CategoryId = categoryId, + Name = name, + Price = price, + Sku = sku + }; + + product.AddDomainEvent(ProductCreatedEvent.Create(product)); + + return product; + } + + public void UpdateName(string name) + { + Guard.Against.NullOrWhiteSpace(name); + Name = name; + } + + public void UpdatePrice(Money price) + { + Guard.Against.Null(price); + Guard.Against.ZeroOrNegative(price.Amount); + Price = price; + } + + public void UpdateSku(Sku sku) + { + Guard.Against.Null(sku); + Sku = sku; + } +} \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductByIdSpec.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductByIdSpec.cs new file mode 100644 index 0000000..b93b596 --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductByIdSpec.cs @@ -0,0 +1,13 @@ +using Ardalis.Specification; + +using SharedKernel.Domain.Identifiers; + +namespace Modules.Warehouse.Domain.Products; + +public class ProductByIdSpec : Specification, ISingleResultSpecification +{ + public ProductByIdSpec(ProductId id) : base() + { + Query.Where(i => i.Id == id); + } +} \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductCreatedEvent.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductCreatedEvent.cs new file mode 100644 index 0000000..def3d3f --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/ProductCreatedEvent.cs @@ -0,0 +1,9 @@ +using SharedKernel.Domain.Base; +using SharedKernel.Domain.Identifiers; + +namespace Modules.Warehouse.Domain.Products; + +public record ProductCreatedEvent(ProductId Product, string ProductName) : DomainEvent +{ + public static ProductCreatedEvent Create(Product product) => new(product.Id, product.Name); +} \ No newline at end of file diff --git a/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Sku.cs b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Sku.cs new file mode 100644 index 0000000..e5ad954 --- /dev/null +++ b/src/Modules/Warehouse/Modules.Warehouse.Domain/Products/Sku.cs @@ -0,0 +1,21 @@ +namespace Modules.Warehouse.Domain.Products; + +public record Sku +{ + private const int DefaultLength = 8; + + public string Value { get; } + + private Sku(string value) => Value = value; + + public static Sku? Create(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + if (value.Length != DefaultLength) + return null; + + return new Sku(value); + } +}