From d53b371a27aec44569273057789cf3709c7666d2 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:14:47 +0100 Subject: [PATCH 01/11] New domain model and unit tests for future work --- domain-model.md | 69 +++++++++++++++++++++++ exercise.main/Basket.cs | 60 ++++++++++++++++++++ exercise.main/BasketItem.cs | 6 ++ exercise.main/Inventory.cs | 57 +++++++++++++++++++ exercise.main/Order.cs | 16 ++++++ exercise.main/OrderLine.cs | 9 +++ exercise.main/Product.cs | 35 ++++++++++++ exercise.main/ProductModification.cs | 8 +++ exercise.main/Startup.cs | 11 ++++ exercise.main/exercise.main.csproj | 4 ++ exercise.tests/BasketTest.cs | 44 +++++++++++++++ exercise.tests/OrderTest.cs | 32 +++++++++++ exercise.tests/ProductModificationTest.cs | 39 +++++++++++++ exercise.tests/ProductTest.cs | 45 +++++++++++++++ exercise.tests/Seed.cs | 37 ++++++++++++ exercise.tests/UnitTest1.cs | 5 ++ exercise.tests/exercise.tests.csproj | 4 ++ 17 files changed, 481 insertions(+) create mode 100644 domain-model.md create mode 100644 exercise.main/Basket.cs create mode 100644 exercise.main/BasketItem.cs create mode 100644 exercise.main/Inventory.cs create mode 100644 exercise.main/Order.cs create mode 100644 exercise.main/OrderLine.cs create mode 100644 exercise.main/Product.cs create mode 100644 exercise.main/ProductModification.cs create mode 100644 exercise.main/Startup.cs create mode 100644 exercise.tests/BasketTest.cs create mode 100644 exercise.tests/OrderTest.cs create mode 100644 exercise.tests/ProductModificationTest.cs create mode 100644 exercise.tests/ProductTest.cs create mode 100644 exercise.tests/Seed.cs diff --git a/domain-model.md b/domain-model.md new file mode 100644 index 00000000..045e9d69 --- /dev/null +++ b/domain-model.md @@ -0,0 +1,69 @@ +# Domain Model + +## Simplified user stories + +- [ ] Must be able to add bagel to basket +- [ ] Must be able to remove bagel from basket +- [ ] Must be able to check if basket is full +- [ ] Must be able to change basket capacity +- [ ] Must be able to check if item exists in basket +- [ ] Warn when user removes non-existent item from basket +- [ ] Must be able to check total cost of items in basket +- [ ] Must be able to check cost of bagel before adding to basket +- [ ] Must be able to choose fillings for bagel +- [ ] Must be able to check cost of filling before adding to bagel order +- [ ] Must be able to add coffee to basket +- [ ] Must be able to check cost of coffee before adding to basket +- [ ] Must be able to add promotion to product +- [ ] Must be able to check stock of product + + +## Methods + +### Product (Bagel, coffee, etc.) + +| Function Name | Parameters | Behavior | Returns | +|----------------|--------------------------|--------------------------------|--------------| +| GetCost | double cost | | | +| SetCost | double newCost | | | +| AddPromo | int amount, double price | Add a new promotion on product | void | + +### Basket + +| Function Name | Parameters | Behavior | Returns | +|--------------------------|------------------------|-------------------------------------------------|---------| +| Basket | int? capacity | Constructor, sets capacity to cart | void | +| Add | string SKU, int amount | Adds product to basket | void | +| Remove | string SKU, int amount | | void | +| SetCapacity | int newCapacity | Sets the new capacity of a basket | void | +| [private] CheckDiscounts | | | | +| [private] CheckCapacity | | Checks whether the basket can fit more products | bool | +| GetTotal | | Sets the new capacity of a basket | double | +| Order | | Submits the bagel order | void | +| [override] ToString | | Generates a string representation of cart | string | + +### BasketItem +- Product +- Amount + +### Inventory + +| Function Name | Parameters | Behavior | Returns | +|---------------|-----------------------------|-----------------------------------|---------| +| Add | Product product, int amount | | | +| Remove | Product product, int amount | | | +| GetProduct | string SKU | Get product by SKU | Product | +| GetStock | | Get stock of the specific product | int | + +### Order + +| Function Name | Parameters | Behavior | Returns | +|---------------------|-----------------|---------------------------------------------------|---------| +| Order | List | Constructor | void | +| [override] ToString | | Generate string representation of order (receipt) | string | + +### OrderLine +- Product +- Amount +- Price +- Discount diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs new file mode 100644 index 00000000..922fd363 --- /dev/null +++ b/exercise.main/Basket.cs @@ -0,0 +1,60 @@ +namespace exercise.main; + +public class Basket +{ + private int _capacity; + private Dictionary _items; + + private readonly IInventory _inventory; + + public Basket(IInventory inventory, int capacity = 10) + { + _capacity = capacity; + _items = new Dictionary(); + _inventory = inventory; + } + public void Add(string SKU, int quantity) + { + throw new NotImplementedException(); + } + + public void Remove(string SKU, int quantity) + { + throw new NotImplementedException(); + } + + public void SetCapacity(int capacity) + { + throw new NotImplementedException(); + } + + public int GetCapacity() + { + throw new NotImplementedException(); + } + + public double GetTotal() + { + throw new NotImplementedException(); + } + + public Order Order() + { + throw new NotImplementedException(); + } + + public override string ToString() + { + throw new NotImplementedException(); + } + + private Dictionary CheckDiscounts() + { + throw new NotImplementedException(); + } + + private bool CheckCapacity(int numNewItems) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/exercise.main/BasketItem.cs b/exercise.main/BasketItem.cs new file mode 100644 index 00000000..86c979ca --- /dev/null +++ b/exercise.main/BasketItem.cs @@ -0,0 +1,6 @@ +namespace exercise.main; + +public class BasketItem +{ + +} \ No newline at end of file diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs new file mode 100644 index 00000000..9b482fec --- /dev/null +++ b/exercise.main/Inventory.cs @@ -0,0 +1,57 @@ +using System.Collections; + +namespace exercise.main; + +public class Inventory : IInventory, IEnumerable> +{ + private Dictionary _products; + + public Inventory() + { + _products = new Dictionary(); + } + + public IEnumerator> GetEnumerator() + { + return _products.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void AddProduct(Product product, int quantity) + { + throw new NotImplementedException(); + } + + public void RemoveProduct(Product product, int quantity) + { + throw new NotImplementedException(); + } + + public void SetStock(Product product, int quantity) + { + throw new NotImplementedException(); + } + + public int GetStock(Product product) + { + throw new NotImplementedException(); + } + + public Product GetProduct(string sku) + { + throw new NotImplementedException(); + } +} + +public interface IInventory +{ + void AddProduct(Product product, int quantity); + void RemoveProduct(Product product, int quantity); + void SetStock(Product product, int quantity); + int GetStock(Product product); + Product GetProduct(string sku); +} \ No newline at end of file diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs new file mode 100644 index 00000000..7fd9d880 --- /dev/null +++ b/exercise.main/Order.cs @@ -0,0 +1,16 @@ +namespace exercise.main; + +public class Order +{ + private List _orderLines; + + public Order() + { + _orderLines = new List(); + } + + public override string ToString() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/exercise.main/OrderLine.cs b/exercise.main/OrderLine.cs new file mode 100644 index 00000000..141b6e31 --- /dev/null +++ b/exercise.main/OrderLine.cs @@ -0,0 +1,9 @@ +namespace exercise.main; + +public class OrderLine +{ + public Product Product { get; set; } + public int Amount { get; set; } + public double Price { get; set; } + public double Discount { get; set; } +} \ No newline at end of file diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs new file mode 100644 index 00000000..9efca4b1 --- /dev/null +++ b/exercise.main/Product.cs @@ -0,0 +1,35 @@ +namespace exercise.main; + +public class Product +{ + public string Sku { get; set; } + public string Name { get; set; } + public double Price { get; set; } + public Dictionary Promotions { get; } + public List AllowedMofifications { get; set; } + + + public Product(string sku, string name, double price) + { + Sku = sku; + Name = name; + Price = price; + Promotions = new Dictionary(); + AllowedMofifications = new List(); + } + + public double GetPrice() + { + throw new NotImplementedException(); + } + + public void SetPrice(double price) + { + throw new NotImplementedException(); + } + + public void AddPromotion(int quantity, double price) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/exercise.main/ProductModification.cs b/exercise.main/ProductModification.cs new file mode 100644 index 00000000..228e0f85 --- /dev/null +++ b/exercise.main/ProductModification.cs @@ -0,0 +1,8 @@ +namespace exercise.main; + +public class ProductModification : Product +{ + public ProductModification(string sku, string name, double price) : base(sku, name, price) + { + } +} \ No newline at end of file diff --git a/exercise.main/Startup.cs b/exercise.main/Startup.cs new file mode 100644 index 00000000..fa7e16a4 --- /dev/null +++ b/exercise.main/Startup.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace exercise.main; + +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + } +} \ No newline at end of file diff --git a/exercise.main/exercise.main.csproj b/exercise.main/exercise.main.csproj index fd4bd08d..eaaf31f1 100644 --- a/exercise.main/exercise.main.csproj +++ b/exercise.main/exercise.main.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/exercise.tests/BasketTest.cs b/exercise.tests/BasketTest.cs new file mode 100644 index 00000000..ba7ae936 --- /dev/null +++ b/exercise.tests/BasketTest.cs @@ -0,0 +1,44 @@ +using exercise.main; +using Microsoft.Extensions.DependencyInjection; + +namespace exercise.tests; + +[TestFixture] +public class BasketTest +{ + private Basket _basket; + private IInventory _inventory; + + [SetUp] + public void Setup() + { + var services = new ServiceCollection(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + _inventory = serviceProvider.GetService(); + _basket = new Basket(_inventory); + } + + [Test] + public void TestAdd() + { + _basket.Add("A", 1); + Assert.AreEqual(1, _basket.GetTotal()); + } + + [Test] + public void TestRemove() + { + _basket.Add("A", 1); + _basket.Remove("A", 1); + Assert.AreEqual(0, _basket.GetTotal()); + } + + [Test] + public void TestSetCapacity() + { + _basket.SetCapacity(5); + Assert.AreEqual(5, _basket.GetCapacity()); + } +} \ No newline at end of file diff --git a/exercise.tests/OrderTest.cs b/exercise.tests/OrderTest.cs new file mode 100644 index 00000000..50a0e63f --- /dev/null +++ b/exercise.tests/OrderTest.cs @@ -0,0 +1,32 @@ +using exercise.main; +using Microsoft.Extensions.DependencyInjection; + +namespace exercise.tests; + +[TestFixture] +public class OrderTest +{ + private IInventory _inventory; + + [SetUp] + public void Setup() + { + var services = new ServiceCollection(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + _inventory = serviceProvider.GetService(); + } + + [Test] + public void TestOrder() + { + var _basket = new Basket(_inventory); + + _basket.Add("BGLO", 1); + _basket.Add("COFB", 1); + var order = _basket.Order(); + + Assert.IsNotEmpty(order.ToString()); + } +} \ No newline at end of file diff --git a/exercise.tests/ProductModificationTest.cs b/exercise.tests/ProductModificationTest.cs new file mode 100644 index 00000000..13605914 --- /dev/null +++ b/exercise.tests/ProductModificationTest.cs @@ -0,0 +1,39 @@ +using exercise.main; + +namespace exercise.tests; + +[TestFixture] +public class ProductModificationTest +{ + private static Product _product; + + [SetUp] + public void Setup() + { + _product = new Product("SKU0", "Test Product", 10.99); + _product.AllowedMofifications + .Add(new ProductModification("MOD1", "Test Modification", 9.99)); + } + + [Test] + public void ProductModConstructorTest() + { + Assert.That(_product.AllowedMofifications.First().Sku, Is.EqualTo("MOD1")); + Assert.That(_product.AllowedMofifications.First().Name, Is.EqualTo("Test Modification")); + Assert.That(_product.AllowedMofifications.First().Price, Is.EqualTo(9.99)); + } + + [Test] + public void ProductModGetPriceTest() + { + // Checks total price of product and modification + Assert.That(_product.GetPrice(), Is.EqualTo(20.99)); + } + + [Test] + public void ProductSetPriceTest() + { + _product.SetPrice(8.99); + Assert.That(_product.AllowedMofifications.First().GetPrice(), Is.EqualTo(8.99)); + } +} \ No newline at end of file diff --git a/exercise.tests/ProductTest.cs b/exercise.tests/ProductTest.cs new file mode 100644 index 00000000..cae2154f --- /dev/null +++ b/exercise.tests/ProductTest.cs @@ -0,0 +1,45 @@ +using exercise.main; + +namespace exercise.tests; + +[TestFixture] +public class ProductTest +{ + private static Product _product; + + [SetUp] + public void Setup() + { + _product = new Product("SKU0", "Test Product", 10.99); + } + + [Test] + public void ProductConstructorTest() + { + Assert.That(_product.Sku, Is.EqualTo("SKU0")); + Assert.That(_product.Name, Is.EqualTo("Test Product")); + Assert.That(_product.Price, Is.EqualTo(10.99)); + Assert.IsEmpty(_product.Promotions); + Assert.IsEmpty(_product.AllowedMofifications); + } + + [Test] + public void ProductGetPriceTest() + { + Assert.That(_product.GetPrice(), Is.EqualTo(10.99)); + } + + [Test] + public void ProductSetPriceTest() + { + _product.SetPrice(9.99); + Assert.That(_product.GetPrice(), Is.EqualTo(9.99)); + } + + [Test] + public void ProductAddPromotionTest() + { + _product.AddPromotion(2, 15.99); + Assert.That(_product.Promotions.First().Value, Is.EqualTo(15.99)); + } +} \ No newline at end of file diff --git a/exercise.tests/Seed.cs b/exercise.tests/Seed.cs new file mode 100644 index 00000000..22234535 --- /dev/null +++ b/exercise.tests/Seed.cs @@ -0,0 +1,37 @@ +using exercise.main; + +namespace exercise.tests; + +public class Seed +{ + public static void AddData(out Inventory inventory) + { + inventory = new Inventory(); + inventory.AddProduct(new Product("BGLO", "Onion Bagel", 0.49), 10); + inventory.AddProduct(new Product("BGLP", "Plain Bagel", 0.39), 10); + inventory.AddProduct(new Product("BGLE", "Everything Bagel", 0.49), 10); + inventory.AddProduct(new Product("BGLS", "Sesame Bagel", 0.49), 10); + inventory.AddProduct(new Product("COFB", "Black Coffee", 0.99), 10); + inventory.AddProduct(new Product("COFW", "White Coffee", 1.19), 10); + inventory.AddProduct(new Product("COFC", "Capuccino", 1.29), 10); + inventory.AddProduct(new Product("COFL", "Latte", 1.29), 10); + + ProductModification filb = new ProductModification("FILB", "Bacon", 0.12); + ProductModification file = new ProductModification("FILE", "Egg", 0.12); + ProductModification filc = new ProductModification("FILC", "Cheese", 0.12); + ProductModification filx = new ProductModification("FILX", "Cream Cheese", 0.12); + ProductModification fils = new ProductModification("FILS", "Smoked Salmon", 0.12); + ProductModification filh = new ProductModification("FILH", "Ham", 0.12); + + List modifications = new List { filb, file, filc, filx, fils, filh }; + + foreach (KeyValuePair inventoryItem in inventory) + { + var product = inventoryItem.Key; + if (product.Sku.StartsWith("BGL")) + { + product.AllowedMofifications = modifications; + } + } + } +} \ No newline at end of file diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 7bdb8968..5585b540 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,10 +1,15 @@ +using exercise.main; + namespace exercise.tests; public class Tests { + protected static Inventory inventory; [SetUp] public void Setup() { + inventory = new Inventory(); + Seed.AddData(out inventory); } [Test] diff --git a/exercise.tests/exercise.tests.csproj b/exercise.tests/exercise.tests.csproj index 9fed8e17..a3a97d4f 100644 --- a/exercise.tests/exercise.tests.csproj +++ b/exercise.tests/exercise.tests.csproj @@ -17,4 +17,8 @@ + + + + From cc5b06b9029334405626de6584d296570a318b43 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:14:11 +0100 Subject: [PATCH 02/11] Improvements to product and basket tests. Higher quality test cases --- exercise.tests/BasketTest.cs | 25 +++++++++++++------------ exercise.tests/ProductTest.cs | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/exercise.tests/BasketTest.cs b/exercise.tests/BasketTest.cs index ba7ae936..1415e194 100644 --- a/exercise.tests/BasketTest.cs +++ b/exercise.tests/BasketTest.cs @@ -7,31 +7,32 @@ namespace exercise.tests; public class BasketTest { private Basket _basket; - private IInventory _inventory; + private Inventory _inventory; [SetUp] public void Setup() { - var services = new ServiceCollection(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - - _inventory = serviceProvider.GetService(); + _inventory = new Inventory(); + Seed.AddData(out _inventory); _basket = new Basket(_inventory); } - [Test] - public void TestAdd() + [TestCase("BGLO", 1, 0.49)] + [TestCase("BGLP", 1, 0.39)] + [TestCase("BGLP", 3, 3.51)] + [TestCase("COFC", 1, 1.29)] + + public void TestAdd(string sku, int quantity, double total) { - _basket.Add("A", 1); - Assert.AreEqual(1, _basket.GetTotal()); + _basket.Add(sku, quantity); + Assert.AreEqual(total, _basket.GetTotal()); } [Test] public void TestRemove() { - _basket.Add("A", 1); - _basket.Remove("A", 1); + _basket.Add("BGLO", 1); + _basket.Remove("BGLO", 1); Assert.AreEqual(0, _basket.GetTotal()); } diff --git a/exercise.tests/ProductTest.cs b/exercise.tests/ProductTest.cs index cae2154f..2e71f8b2 100644 --- a/exercise.tests/ProductTest.cs +++ b/exercise.tests/ProductTest.cs @@ -29,11 +29,22 @@ public void ProductGetPriceTest() Assert.That(_product.GetPrice(), Is.EqualTo(10.99)); } - [Test] - public void ProductSetPriceTest() + [TestCase(9.9)] + [TestCase(0.01)] + [TestCase(double.MaxValue)] + + public void ProductSetPriceTest(double newPrice) + { + _product.SetPrice(newPrice); + Assert.That(_product.GetPrice(), Is.EqualTo(newPrice)); + } + + [TestCase(0)] + [TestCase(-1.0)] + [TestCase(double.MinValue)] + public void ProductSetNegativePriceTest(double newPrice) { - _product.SetPrice(9.99); - Assert.That(_product.GetPrice(), Is.EqualTo(9.99)); + Assert.Throws(() => _product.SetPrice(newPrice)); } [Test] From e05be17f34eccf1f5baae3ecac7098cb205b4f95 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:22:23 +0100 Subject: [PATCH 03/11] Product and Basket classes working --- exercise.main/Basket.cs | 48 +++++++++++++++++++++++++++++++++++----- exercise.main/Product.cs | 17 +++++++++++--- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index 922fd363..74f3e85d 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -15,27 +15,65 @@ public Basket(IInventory inventory, int capacity = 10) } public void Add(string SKU, int quantity) { - throw new NotImplementedException(); + // Do not allow overfilled bagel basket! + if (GetNumberOfItems() + quantity > _capacity) + { + throw new Exception("Basket is full"); + } + + if (!_items.ContainsKey(SKU)) + { + _items.Add(SKU, quantity); + return; + } + + _items[SKU] += quantity; } public void Remove(string SKU, int quantity) { - throw new NotImplementedException(); + if (!_items.ContainsKey(SKU)) + { + return; + } + + _items[SKU] -= quantity; } public void SetCapacity(int capacity) { - throw new NotImplementedException(); + _capacity = capacity; } public int GetCapacity() { - throw new NotImplementedException(); + return _capacity; + } + + private int GetNumberOfItems() + { + var numItems = 0; + + foreach (var item in _items) + { + numItems += item.Value; + } + + return numItems; } public double GetTotal() { - throw new NotImplementedException(); + double total = 0; + + foreach (var item in _items) + { + var price = _inventory.GetProduct(item.Key).GetPrice(); + + total += price * item.Value * item.Value; + } + + return total; } public Order Order() diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 9efca4b1..6d32c142 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -20,16 +20,27 @@ public Product(string sku, string name, double price) public double GetPrice() { - throw new NotImplementedException(); + return Price; } public void SetPrice(double price) { - throw new NotImplementedException(); + if (price <= 0) + { + throw new Exception("Price must be greater than 0"); + } + + Price = price; } public void AddPromotion(int quantity, double price) { - throw new NotImplementedException(); + if (quantity > 0 && price > 0) + { + Promotions.Add(quantity, price); + return; + } + + throw new Exception("Invalid promotion. Quantity and promotional price must be greater than 0"); } } \ No newline at end of file From 1e11e3d9fd1d17a38536a55c5482855247a99943 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:22:47 +0100 Subject: [PATCH 04/11] Critically important Inventory stuff --- exercise.main/Inventory.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs index 9b482fec..3aaee9d6 100644 --- a/exercise.main/Inventory.cs +++ b/exercise.main/Inventory.cs @@ -23,7 +23,7 @@ IEnumerator IEnumerable.GetEnumerator() public void AddProduct(Product product, int quantity) { - throw new NotImplementedException(); + _products.Add(product, quantity); } public void RemoveProduct(Product product, int quantity) @@ -43,7 +43,15 @@ public int GetStock(Product product) public Product GetProduct(string sku) { - throw new NotImplementedException(); + try + { + + return _products.First(p => p.Key.Sku == sku).Key; + } + catch (Exception e) + { + throw new Exception("Product not found", e); + } } } From 4d0fb5ec21bbb1ec93d546a88014749aa80548da Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:11:08 +0100 Subject: [PATCH 05/11] Order class finalized --- exercise.main/Basket.cs | 9 +++++++- exercise.main/Order.cs | 41 ++++++++++++++++++++++++++++++++++++- exercise.tests/OrderTest.cs | 14 ++++++------- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index 74f3e85d..6004ac2a 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -78,7 +78,14 @@ public double GetTotal() public Order Order() { - throw new NotImplementedException(); + Order order = new Order(); + + foreach (var item in _items) + { + order.Add(_inventory.GetProduct(item.Key), item.Value); + } + + return order; } public override string ToString() diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs index 7fd9d880..33575e18 100644 --- a/exercise.main/Order.cs +++ b/exercise.main/Order.cs @@ -1,3 +1,5 @@ +using System.Text; + namespace exercise.main; public class Order @@ -9,8 +11,45 @@ public Order() _orderLines = new List(); } + + public override string ToString() { - throw new NotImplementedException(); + var sb = new StringBuilder(); + + foreach (var orderLine in _orderLines) + { + sb.AppendLine($"{ + FixedLengthString(orderLine.Product.Name, 15)} " + + $"{FixedLengthString(orderLine.Amount.ToString(), 2)} " + + $"{FormatPrice(orderLine.Price)}"); + } + + return sb.ToString(); + } + + public void Add(Product product, int amount) + { + _orderLines.Add(new OrderLine + { + Product = product, + Amount = amount, + Price = product.GetPrice() + }); + } + + private string FixedLengthString(string value, int length) + { + return value.PadRight(length).Substring(0, length); + } + + private string FormatPrice(double price) + { + if (price < 0) + { + return $"(-€{price})"; + } + + return $" €{price}"; } } \ No newline at end of file diff --git a/exercise.tests/OrderTest.cs b/exercise.tests/OrderTest.cs index 50a0e63f..ce704829 100644 --- a/exercise.tests/OrderTest.cs +++ b/exercise.tests/OrderTest.cs @@ -6,16 +6,14 @@ namespace exercise.tests; [TestFixture] public class OrderTest { - private IInventory _inventory; + private Inventory _inventory; [SetUp] public void Setup() { - var services = new ServiceCollection(); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - - _inventory = serviceProvider.GetService(); + _inventory = new Inventory(); + + Seed.AddData(out _inventory); } [Test] @@ -23,10 +21,12 @@ public void TestOrder() { var _basket = new Basket(_inventory); - _basket.Add("BGLO", 1); + _basket.Add("BGLO", 3); _basket.Add("COFB", 1); var order = _basket.Order(); + Console.WriteLine(order); + Assert.IsNotEmpty(order.ToString()); } } \ No newline at end of file From 8d1e5c25f74188ccd070127d0c0a5fc16863265a Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:44:53 +0100 Subject: [PATCH 06/11] Fixed failing unit test for Basket --- exercise.tests/BasketTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise.tests/BasketTest.cs b/exercise.tests/BasketTest.cs index 1415e194..1e77d12e 100644 --- a/exercise.tests/BasketTest.cs +++ b/exercise.tests/BasketTest.cs @@ -19,7 +19,7 @@ public void Setup() [TestCase("BGLO", 1, 0.49)] [TestCase("BGLP", 1, 0.39)] - [TestCase("BGLP", 3, 3.51)] + [TestCase("BGLP", 3, 1.17)] [TestCase("COFC", 1, 1.29)] public void TestAdd(string sku, int quantity, double total) From 2b814968d2ec04584d39e8ac0965bd947024e23c Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:08:35 +0100 Subject: [PATCH 07/11] Typo fixes for modification --- exercise.main/Product.cs | 14 ++++++++++++-- exercise.tests/ProductModificationTest.cs | 10 +++++----- exercise.tests/ProductTest.cs | 2 +- exercise.tests/Seed.cs | 2 +- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 6d32c142..8c08227b 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -6,7 +6,8 @@ public class Product public string Name { get; set; } public double Price { get; set; } public Dictionary Promotions { get; } - public List AllowedMofifications { get; set; } + public List AllowedModifications { get; set; } + public List Modifications { get; set; } public Product(string sku, string name, double price) @@ -15,7 +16,7 @@ public Product(string sku, string name, double price) Name = name; Price = price; Promotions = new Dictionary(); - AllowedMofifications = new List(); + AllowedModifications = new List(); } public double GetPrice() @@ -33,6 +34,15 @@ public void SetPrice(double price) Price = price; } + public void AddModification(ProductModification modification) + { + // Checks if the modification SKU is allowed, then add and sort + if (AllowedModifications.Any(m => m.Sku == modification.Sku)) + { + Modifications.Add(modification.Sku); + } + } + public void AddPromotion(int quantity, double price) { if (quantity > 0 && price > 0) diff --git a/exercise.tests/ProductModificationTest.cs b/exercise.tests/ProductModificationTest.cs index 13605914..6f73cd43 100644 --- a/exercise.tests/ProductModificationTest.cs +++ b/exercise.tests/ProductModificationTest.cs @@ -11,16 +11,16 @@ public class ProductModificationTest public void Setup() { _product = new Product("SKU0", "Test Product", 10.99); - _product.AllowedMofifications + _product.AllowedModifications .Add(new ProductModification("MOD1", "Test Modification", 9.99)); } [Test] public void ProductModConstructorTest() { - Assert.That(_product.AllowedMofifications.First().Sku, Is.EqualTo("MOD1")); - Assert.That(_product.AllowedMofifications.First().Name, Is.EqualTo("Test Modification")); - Assert.That(_product.AllowedMofifications.First().Price, Is.EqualTo(9.99)); + Assert.That(_product.AllowedModifications.First().Sku, Is.EqualTo("MOD1")); + Assert.That(_product.AllowedModifications.First().Name, Is.EqualTo("Test Modification")); + Assert.That(_product.AllowedModifications.First().Price, Is.EqualTo(9.99)); } [Test] @@ -34,6 +34,6 @@ public void ProductModGetPriceTest() public void ProductSetPriceTest() { _product.SetPrice(8.99); - Assert.That(_product.AllowedMofifications.First().GetPrice(), Is.EqualTo(8.99)); + Assert.That(_product.AllowedModifications.First().GetPrice(), Is.EqualTo(8.99)); } } \ No newline at end of file diff --git a/exercise.tests/ProductTest.cs b/exercise.tests/ProductTest.cs index 2e71f8b2..a689cbb9 100644 --- a/exercise.tests/ProductTest.cs +++ b/exercise.tests/ProductTest.cs @@ -20,7 +20,7 @@ public void ProductConstructorTest() Assert.That(_product.Name, Is.EqualTo("Test Product")); Assert.That(_product.Price, Is.EqualTo(10.99)); Assert.IsEmpty(_product.Promotions); - Assert.IsEmpty(_product.AllowedMofifications); + Assert.IsEmpty(_product.AllowedModifications); } [Test] diff --git a/exercise.tests/Seed.cs b/exercise.tests/Seed.cs index 22234535..2c5519a2 100644 --- a/exercise.tests/Seed.cs +++ b/exercise.tests/Seed.cs @@ -30,7 +30,7 @@ public static void AddData(out Inventory inventory) var product = inventoryItem.Key; if (product.Sku.StartsWith("BGL")) { - product.AllowedMofifications = modifications; + product.AllowedModifications = modifications; } } } From db9f79ae7751e9e70ce86d1d72e73fefea6c4d04 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:18:07 +0100 Subject: [PATCH 08/11] Restructuring order class, to make receipts more versatile --- exercise.main/Order.cs | 16 ++++++++++++---- exercise.main/OrderLine.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs index 33575e18..61f406f7 100644 --- a/exercise.main/Order.cs +++ b/exercise.main/Order.cs @@ -11,8 +11,6 @@ public Order() _orderLines = new List(); } - - public override string ToString() { var sb = new StringBuilder(); @@ -20,7 +18,7 @@ public override string ToString() foreach (var orderLine in _orderLines) { sb.AppendLine($"{ - FixedLengthString(orderLine.Product.Name, 15)} " + + FixedLengthString(orderLine.Product, 15)} " + $"{FixedLengthString(orderLine.Amount.ToString(), 2)} " + $"{FormatPrice(orderLine.Price)}"); } @@ -32,12 +30,22 @@ public void Add(Product product, int amount) { _orderLines.Add(new OrderLine { - Product = product, + Product = product.Name, Amount = amount, Price = product.GetPrice() }); } + public void AddModifier(string label, int amount, double price) + { + _orderLines.Add(new OrderLine + { + Product = label, + Amount = amount, + Price = price + }); + } + private string FixedLengthString(string value, int length) { return value.PadRight(length).Substring(0, length); diff --git a/exercise.main/OrderLine.cs b/exercise.main/OrderLine.cs index 141b6e31..59137b49 100644 --- a/exercise.main/OrderLine.cs +++ b/exercise.main/OrderLine.cs @@ -2,7 +2,7 @@ namespace exercise.main; public class OrderLine { - public Product Product { get; set; } + public string Product { get; set; } public int Amount { get; set; } public double Price { get; set; } public double Discount { get; set; } From c274e16a41d2be7a4d92a8b58bcd7418f41028df Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:46:39 +0100 Subject: [PATCH 09/11] New test case regarding discounts --- exercise.tests/OrderTest.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/exercise.tests/OrderTest.cs b/exercise.tests/OrderTest.cs index ce704829..550ec289 100644 --- a/exercise.tests/OrderTest.cs +++ b/exercise.tests/OrderTest.cs @@ -1,5 +1,4 @@ using exercise.main; -using Microsoft.Extensions.DependencyInjection; namespace exercise.tests; @@ -29,4 +28,20 @@ public void TestOrder() Assert.IsNotEmpty(order.ToString()); } + + [Test] + public void TestDiscounts() + { + var _basket = new Basket(_inventory); + + _basket.SetCapacity(20); + + _basket.Add("BGLO", 14); + _basket.Add("COFB", 1); + var order = _basket.Order(); + + Console.WriteLine(order); + + Assert.IsNotEmpty(order.ToString()); + } } \ No newline at end of file From 42d7bf3a0bb9057d33de127104e22bb52cb21d68 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:34:04 +0100 Subject: [PATCH 10/11] Improvements to order handling --- exercise.main/Basket.cs | 106 +++++++++++++++++++++++++++++++----- exercise.main/BasketItem.cs | 9 +++ exercise.main/Order.cs | 19 ++++++- 3 files changed, 118 insertions(+), 16 deletions(-) diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index 6004ac2a..98f2d499 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -3,14 +3,14 @@ namespace exercise.main; public class Basket { private int _capacity; - private Dictionary _items; + private List _items; private readonly IInventory _inventory; public Basket(IInventory inventory, int capacity = 10) { _capacity = capacity; - _items = new Dictionary(); + _items = new List(); _inventory = inventory; } public void Add(string SKU, int quantity) @@ -21,23 +21,31 @@ public void Add(string SKU, int quantity) throw new Exception("Basket is full"); } - if (!_items.ContainsKey(SKU)) + var item = Get(SKU); + + if (ReferenceEquals(item, null)) { - _items.Add(SKU, quantity); + _items.Add(new BasketItem(SKU, quantity)); return; } - _items[SKU] += quantity; + item.Quantity = quantity; } public void Remove(string SKU, int quantity) { - if (!_items.ContainsKey(SKU)) + var item = Get(SKU); + // Check if nullable item is null or empty + + if (!ReferenceEquals(item, null)) { - return; + Get(SKU).Quantity -= quantity; } - - _items[SKU] -= quantity; + } + + public BasketItem? Get(string SKU) + { + return _items.FirstOrDefault(x => x.SKU == SKU); } public void SetCapacity(int capacity) @@ -56,7 +64,7 @@ private int GetNumberOfItems() foreach (var item in _items) { - numItems += item.Value; + numItems += item.Quantity; } return numItems; @@ -68,9 +76,9 @@ public double GetTotal() foreach (var item in _items) { - var price = _inventory.GetProduct(item.Key).GetPrice(); + var price = _inventory.GetProduct(item.SKU).GetPrice(); - total += price * item.Value * item.Value; + total += price * item.Quantity; } return total; @@ -79,10 +87,17 @@ public double GetTotal() public Order Order() { Order order = new Order(); + Dictionary discounts = CheckDiscounts(); foreach (var item in _items) { - order.Add(_inventory.GetProduct(item.Key), item.Value); + var product = _inventory.GetProduct(item.SKU); + order.Add(product, item.Quantity); + } + + foreach (var discount in discounts) + { + order.AddModifier(discount.Key, 0, discount.Value * -1); } return order; @@ -95,11 +110,74 @@ public override string ToString() private Dictionary CheckDiscounts() { - throw new NotImplementedException(); + var discounts = new Dictionary(); + var numBagels = 0; + var numCoffees = 0; + var discount = 0.0; + + foreach (var basketItem in _items) + { + if (basketItem.SKU.StartsWith("BGL")) + { + numBagels += basketItem.Quantity; + } + + if (basketItem.SKU.StartsWith("COF")) + { + numCoffees += basketItem.Quantity; + } + } + + // Apply the 12 for 3.99 offer + var twelves = numBagels / 12; + discount = twelves * (12 * 0.49 - 3.99); + numBagels %= 12; + // Discount is only applied when more than 0 + if (discount > 0) + { + discounts.Add("12 for 3.99", discount); + } + + // Apply the 6 for 2.49 offer + var sixes = numBagels / 6; + discount = sixes * (6 * 0.49 - 2.49); + numBagels %= 6; + if (discount > 0) + { + discounts.Add("6 for 2.49", discount); + } + + // Coffee deal. Not to be combined with other deals + var qualifyingMealDeals = Math.Min(numBagels, numCoffees); + discount = qualifyingMealDeals * 0.5; + if (discount > 0) + { + discounts.Add("Coffee and Bagel", discount); + } + + return discounts; } private bool CheckCapacity(int numNewItems) { throw new NotImplementedException(); } + + private BasketItem? Contains(string SKU) + { + return Contains(SKU, []); + } + + private BasketItem? Contains(string SKU, List modifiers) + { + modifiers.Sort(); + try + { + return _items.First(x => x.SKU == SKU && x.Modifiers.SequenceEqual(modifiers)); + } + catch + { + return null; + } + } } \ No newline at end of file diff --git a/exercise.main/BasketItem.cs b/exercise.main/BasketItem.cs index 86c979ca..9130442e 100644 --- a/exercise.main/BasketItem.cs +++ b/exercise.main/BasketItem.cs @@ -2,5 +2,14 @@ namespace exercise.main; public class BasketItem { + public string SKU { get; set; } + public int Quantity { get; set; } + public List Modifiers { get; set; } + public BasketItem(string sku, int quantity) + { + SKU = sku; + Quantity = quantity; + Modifiers = new List(); + } } \ No newline at end of file diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs index 61f406f7..50d6278b 100644 --- a/exercise.main/Order.cs +++ b/exercise.main/Order.cs @@ -5,16 +5,24 @@ namespace exercise.main; public class Order { private List _orderLines; + private DateTime _date; public Order() { _orderLines = new List(); + _date = DateTime.Now; } public override string ToString() { var sb = new StringBuilder(); + sb.AppendLine("------- Bob's Bagels -------\n"); + sb.AppendLine($" {_date} \n"); + + + sb.AppendLine("Product Amt Price"); + foreach (var orderLine in _orderLines) { sb.AppendLine($"{ @@ -23,6 +31,8 @@ public override string ToString() $"{FormatPrice(orderLine.Price)}"); } + sb.AppendLine($"\n{"Total:", 18} {FormatPrice(Total())}"); + return sb.ToString(); } @@ -46,6 +56,11 @@ public void AddModifier(string label, int amount, double price) }); } + public double Total() + { + return _orderLines.Sum(ol => ol.Price * Math.Max(1, ol.Amount)); + } + private string FixedLengthString(string value, int length) { return value.PadRight(length).Substring(0, length); @@ -55,9 +70,9 @@ private string FormatPrice(double price) { if (price < 0) { - return $"(-€{price})"; + return $"(€{price.ToString("F2")})"; } - return $" €{price}"; + return $" €{price.ToString("F2")}"; } } \ No newline at end of file From 27ceb88e9adc5f456746c41d8b0f66ae906578a4 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:37:49 +0100 Subject: [PATCH 11/11] Finished exercise --- domain-model.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/domain-model.md b/domain-model.md index 045e9d69..580c3342 100644 --- a/domain-model.md +++ b/domain-model.md @@ -2,20 +2,19 @@ ## Simplified user stories -- [ ] Must be able to add bagel to basket -- [ ] Must be able to remove bagel from basket -- [ ] Must be able to check if basket is full -- [ ] Must be able to change basket capacity -- [ ] Must be able to check if item exists in basket -- [ ] Warn when user removes non-existent item from basket -- [ ] Must be able to check total cost of items in basket -- [ ] Must be able to check cost of bagel before adding to basket -- [ ] Must be able to choose fillings for bagel -- [ ] Must be able to check cost of filling before adding to bagel order -- [ ] Must be able to add coffee to basket -- [ ] Must be able to check cost of coffee before adding to basket -- [ ] Must be able to add promotion to product -- [ ] Must be able to check stock of product +- [x] Must be able to add bagel to basket +- [x] Must be able to remove bagel from basket +- [x] Must be able to check if basket is full +- [x] Must be able to change basket capacity +- [x] Must be able to check if item exists in basket +- [x] Warn when user removes non-existent item from basket +- [x] Must be able to check total cost of items in basket +- [x] Must be able to check cost of bagel before adding to basket +- [x] Must be able to choose fillings for bagel +- [x] Must be able to check cost of filling before adding to bagel order +- [x] Must be able to add coffee to basket +- [x] Must be able to check cost of coffee before adding to basket +- [x] Must be able to add promotion to product ## Methods