From 2f34409422b76672292e6b6b2677530690c55f82 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Fri, 10 Jan 2025 16:39:46 +0100 Subject: [PATCH 01/10] Initial Domain model... --- domain-model.md | 143 ++++++++++++++++++++++++++++++++++ exercise.main/domain-model.md | 143 ++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 domain-model.md create mode 100644 exercise.main/domain-model.md diff --git a/domain-model.md b/domain-model.md new file mode 100644 index 00000000..ed934c1e --- /dev/null +++ b/domain-model.md @@ -0,0 +1,143 @@ +## User Stories [CORE] +1. As a member of the public, +So I can order a bagel before work, +I'd like to add a specific type of bagel to my basket. + +2. As a member of the public, +So I can change my order, +I'd like to remove a bagel from my basket. + +3. As a member of the public, +So that I can not overfill my small bagel basket +I'd like to know when my basket is full when I try adding an item beyond my basket capacity. + +4. As a Bob's Bagels manager, +So that I can expand my business, +I’d like to change the capacity of baskets. + +5. As a member of the public +So that I can maintain my sanity +I'd like to know if I try to remove an item that doesn't exist in my basket. + +6. As a customer, +So I know how much money I need, +I'd like to know the total cost of items in my basket. + +7. As a customer, +So I know what the damage will be, +I'd like to know the cost of a bagel before I add it to my basket. + +8. As a customer, +So I can shake things up a bit, +I'd like to be able to choose fillings for my bagel. + +9. As a customer, +So I don't over-spend, +I'd like to know the cost of each filling before I add it to my bagel order. + +10. As the manager, +So we don't get any weird requests, +I want customers to only be able to order things that we stock in our inventory. + +## User Stories [Extended: Discounts] +11. As the manager, +So we can sell stuff before it goes bad and entice customers to buy, +I want to be able to set `Special Offer` for any product that matches `buy x nr of z for y`, `buy z, get [a,..] for y`. + +12. As the manager, +So we don't lose money by allowing infinite purchases of a discounted product, +I want to be able to define how many of product `z` that should have the `Special Offer`. + +13. As a customer, +So I know that I'm paying the the discounted price for a discounted product and no more, +I'd like a discount to automatically be applied when I buy `x` nr of `z` if a product has a `buy x nr of z for y` discount. + +## User Stories [Extended: Reciepts] +14. As a customer, +So I can justify the cost of my purchases to my partner, +I'd like to get a reciepe containing my purchased products, along with the cost of each. + +15. As a Manager, +So that we have a value to request the customer to pay, +I'd like the reciepe to contain a Total for all products on the reciept. + +16. As a Manager, +So I can keep track of the stock for each product, +I want each purchase to be registered and the stock to be updated. + +17. As a Manager, +So that the customer will have a good impression of us, +I want there to be a `thank you` message to the customer. + +18. As a Manager, +So that that the customer will know where they bought the bagels, +I want our name to be printed on the reciept. + +19. As a Manager, +So I can verify that the purchase is within our return time policy if a customer tries to return a product, +I want the date and time of the purchase present on the reciept. + +20. As a Manager, +So I don't waste unnecessary amount of reciept paper, +I want identical products to be grouped together and a number to be used to represent the amount. + +21. As a Manager, +So that we have a way to identify each purchase, +I want the date and time of the purchase present on the reciept. + +22. As a Developer, +So that I don't need to buy a reciept printer, +I want to be able to print a reciep to the terminal. + +## User Stories [Extended: Discounted Reciepts] + +23. as a Customer, +so that I know that all the discounts has been accounted for, +I want to see all the discounts (per product) on the reciept. + +24. as a Manager, +so that they feel extra good about their purchase, +I want the customer to see how much they saved on discounts. + +| User Story Id | Class | Method/Property | Scenario | Output | +|---------------|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| +| 1 | Product | Abstract class | A Basket contains a list of different Products | Allows for polymorphism | +| 11 | Product | DateTime ExpirationDate | Manager wants to apply a discount to all Products with a expiration date less than 2 days | ExpirationDate is returned | +| 7, 9 | Product | Price (prop) | User wants to know the price of a product | returns value of Product | +| | | | | | +| 1 | Basket | Add | User wants to add a bagel to their basket, to purchase | Bagel product added to the basket | +| 2 | Basket | Remove | user wants to remove a bagel from their basket, not to be included in the puschase | The removed bagel is no longer part of the Products in the basket | +| 3 | Basket | Capacity (prop) | The user tries to add another bagel to their basket, but the basket is full | No new bagel will be added to the basket, basket product list remains unchanged | +| 4 | Basket | Capacity (prop) | Manager decides to allow one more item in the basket | The basket now contains a productlist that can fit one more item | +| 5 | Basket | Remove | User removes an item that doesn't exist in their basket | User recieves a warning | +| 6 | Basket | GetTotal | User has added several products to basket, they want to know how much its going to cost | returns the sum of all items | +| 10 | Basket | Add | Usere wants to add a Product or ProductComponent to their Basket, but stock is 0 | nothing is added | +| | | | | | +| 8 | ProductComponent | ProductComponent Product | User want to order a custom product, containing one or several ingredients | A Product that contains a list of ProductComponent (which are inherited from Product) | +| | | | | | +| 8 | ProductDesigner | Factory class for Products | Used to construct products | | +| 8 | ProductDesigner | Create | User want to select several fillings (ProductComponents) for their Bagle | a Bagle is returned with the requested fillings | +| | | | | | +| 10 | Product | Stock (prop) | User wanted to add Product or ProductComponent to their order, but stock is 0 | | +| 11 | Product | Discount discount | Any product may have a Discount, applied to the final price | | +| 11 | Discount | Constructor | class used to represent discounts | | +| | | | | | +| 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | +| 12 | Discount | Condition (lambda) | defines what circumstances the discount is valid for (Limited amount, ) | | +| | | | | | +| 13 | CashRegister | Constructor | all purchases is handled by the CashRegister | | +| 13 | CashRegister | RegisterBasket | User is ready to pay, all products in their basket will be summed up, any discount will be accounted for | Sets the currentBasket, ready for user to Pay | +| 13 | CashRegister | Basket CurrentBasket (prop) | The current Basket instance being processed | | +| 13, 14, 15 | CashRegister | FinalizePurchase | User provides required amont of money | returns a reciept, deletes basket | +| 17, 18, 20, 21, 22, 23, 24 | CashRegister | FinalizePurchase | the Reciept will contain Product information (product and amount of it), purchase DateTime, Store title and a `thank you` | | +| 13 | CashRegister | FinalizePurchase | User doesn't have enough money | currentBasket is set to Null | +| | | | | | +| 16 | Inventory | Constructor | Keep tracks on the inventory | | +| 19 | Inventory | History (prop) | When a purchase is made, it is kept in the history using DateTime as an ID | | +| 16 | Inventory | SetBusy(Product) | Marks an Product as taken by another Users Basket | | +| 16 | Inventory | Remove(Product) | Product is removed when User Finalizes a Purchase | Product is removed from inventory | +| 16 | Inventory | add(Product, nr) | Manager bought 100 jars of pickle | 100 jars of pickle added to the inventory | + + + + \ No newline at end of file diff --git a/exercise.main/domain-model.md b/exercise.main/domain-model.md new file mode 100644 index 00000000..ed934c1e --- /dev/null +++ b/exercise.main/domain-model.md @@ -0,0 +1,143 @@ +## User Stories [CORE] +1. As a member of the public, +So I can order a bagel before work, +I'd like to add a specific type of bagel to my basket. + +2. As a member of the public, +So I can change my order, +I'd like to remove a bagel from my basket. + +3. As a member of the public, +So that I can not overfill my small bagel basket +I'd like to know when my basket is full when I try adding an item beyond my basket capacity. + +4. As a Bob's Bagels manager, +So that I can expand my business, +I’d like to change the capacity of baskets. + +5. As a member of the public +So that I can maintain my sanity +I'd like to know if I try to remove an item that doesn't exist in my basket. + +6. As a customer, +So I know how much money I need, +I'd like to know the total cost of items in my basket. + +7. As a customer, +So I know what the damage will be, +I'd like to know the cost of a bagel before I add it to my basket. + +8. As a customer, +So I can shake things up a bit, +I'd like to be able to choose fillings for my bagel. + +9. As a customer, +So I don't over-spend, +I'd like to know the cost of each filling before I add it to my bagel order. + +10. As the manager, +So we don't get any weird requests, +I want customers to only be able to order things that we stock in our inventory. + +## User Stories [Extended: Discounts] +11. As the manager, +So we can sell stuff before it goes bad and entice customers to buy, +I want to be able to set `Special Offer` for any product that matches `buy x nr of z for y`, `buy z, get [a,..] for y`. + +12. As the manager, +So we don't lose money by allowing infinite purchases of a discounted product, +I want to be able to define how many of product `z` that should have the `Special Offer`. + +13. As a customer, +So I know that I'm paying the the discounted price for a discounted product and no more, +I'd like a discount to automatically be applied when I buy `x` nr of `z` if a product has a `buy x nr of z for y` discount. + +## User Stories [Extended: Reciepts] +14. As a customer, +So I can justify the cost of my purchases to my partner, +I'd like to get a reciepe containing my purchased products, along with the cost of each. + +15. As a Manager, +So that we have a value to request the customer to pay, +I'd like the reciepe to contain a Total for all products on the reciept. + +16. As a Manager, +So I can keep track of the stock for each product, +I want each purchase to be registered and the stock to be updated. + +17. As a Manager, +So that the customer will have a good impression of us, +I want there to be a `thank you` message to the customer. + +18. As a Manager, +So that that the customer will know where they bought the bagels, +I want our name to be printed on the reciept. + +19. As a Manager, +So I can verify that the purchase is within our return time policy if a customer tries to return a product, +I want the date and time of the purchase present on the reciept. + +20. As a Manager, +So I don't waste unnecessary amount of reciept paper, +I want identical products to be grouped together and a number to be used to represent the amount. + +21. As a Manager, +So that we have a way to identify each purchase, +I want the date and time of the purchase present on the reciept. + +22. As a Developer, +So that I don't need to buy a reciept printer, +I want to be able to print a reciep to the terminal. + +## User Stories [Extended: Discounted Reciepts] + +23. as a Customer, +so that I know that all the discounts has been accounted for, +I want to see all the discounts (per product) on the reciept. + +24. as a Manager, +so that they feel extra good about their purchase, +I want the customer to see how much they saved on discounts. + +| User Story Id | Class | Method/Property | Scenario | Output | +|---------------|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| +| 1 | Product | Abstract class | A Basket contains a list of different Products | Allows for polymorphism | +| 11 | Product | DateTime ExpirationDate | Manager wants to apply a discount to all Products with a expiration date less than 2 days | ExpirationDate is returned | +| 7, 9 | Product | Price (prop) | User wants to know the price of a product | returns value of Product | +| | | | | | +| 1 | Basket | Add | User wants to add a bagel to their basket, to purchase | Bagel product added to the basket | +| 2 | Basket | Remove | user wants to remove a bagel from their basket, not to be included in the puschase | The removed bagel is no longer part of the Products in the basket | +| 3 | Basket | Capacity (prop) | The user tries to add another bagel to their basket, but the basket is full | No new bagel will be added to the basket, basket product list remains unchanged | +| 4 | Basket | Capacity (prop) | Manager decides to allow one more item in the basket | The basket now contains a productlist that can fit one more item | +| 5 | Basket | Remove | User removes an item that doesn't exist in their basket | User recieves a warning | +| 6 | Basket | GetTotal | User has added several products to basket, they want to know how much its going to cost | returns the sum of all items | +| 10 | Basket | Add | Usere wants to add a Product or ProductComponent to their Basket, but stock is 0 | nothing is added | +| | | | | | +| 8 | ProductComponent | ProductComponent Product | User want to order a custom product, containing one or several ingredients | A Product that contains a list of ProductComponent (which are inherited from Product) | +| | | | | | +| 8 | ProductDesigner | Factory class for Products | Used to construct products | | +| 8 | ProductDesigner | Create | User want to select several fillings (ProductComponents) for their Bagle | a Bagle is returned with the requested fillings | +| | | | | | +| 10 | Product | Stock (prop) | User wanted to add Product or ProductComponent to their order, but stock is 0 | | +| 11 | Product | Discount discount | Any product may have a Discount, applied to the final price | | +| 11 | Discount | Constructor | class used to represent discounts | | +| | | | | | +| 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | +| 12 | Discount | Condition (lambda) | defines what circumstances the discount is valid for (Limited amount, ) | | +| | | | | | +| 13 | CashRegister | Constructor | all purchases is handled by the CashRegister | | +| 13 | CashRegister | RegisterBasket | User is ready to pay, all products in their basket will be summed up, any discount will be accounted for | Sets the currentBasket, ready for user to Pay | +| 13 | CashRegister | Basket CurrentBasket (prop) | The current Basket instance being processed | | +| 13, 14, 15 | CashRegister | FinalizePurchase | User provides required amont of money | returns a reciept, deletes basket | +| 17, 18, 20, 21, 22, 23, 24 | CashRegister | FinalizePurchase | the Reciept will contain Product information (product and amount of it), purchase DateTime, Store title and a `thank you` | | +| 13 | CashRegister | FinalizePurchase | User doesn't have enough money | currentBasket is set to Null | +| | | | | | +| 16 | Inventory | Constructor | Keep tracks on the inventory | | +| 19 | Inventory | History (prop) | When a purchase is made, it is kept in the history using DateTime as an ID | | +| 16 | Inventory | SetBusy(Product) | Marks an Product as taken by another Users Basket | | +| 16 | Inventory | Remove(Product) | Product is removed when User Finalizes a Purchase | Product is removed from inventory | +| 16 | Inventory | add(Product, nr) | Manager bought 100 jars of pickle | 100 jars of pickle added to the inventory | + + + + \ No newline at end of file From 7fbc046d8db3ec4a570a15de9bfe73ede5a67150 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Sun, 12 Jan 2025 21:14:41 +0100 Subject: [PATCH 02/10] WIP: Discount --- domain-model.md | 2 +- exercise.main/Basket.cs | 50 ++++ exercise.main/Product.cs | 339 +++++++++++++++++++++++++++ exercise.tests/UnitTest1.cs | 115 ++++++++- exercise.tests/exercise.tests.csproj | 4 + 5 files changed, 506 insertions(+), 4 deletions(-) create mode 100644 exercise.main/Basket.cs create mode 100644 exercise.main/Product.cs diff --git a/domain-model.md b/domain-model.md index ed934c1e..63741b04 100644 --- a/domain-model.md +++ b/domain-model.md @@ -101,7 +101,7 @@ I want the customer to see how much they saved on discounts. | User Story Id | Class | Method/Property | Scenario | Output | |---------------|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| -| 1 | Product | Abstract class | A Basket contains a list of different Products | Allows for polymorphism | +| 1 | Product | Abstract class ? | A Basket contains a list of different Products | Allows for polymorphism | | 11 | Product | DateTime ExpirationDate | Manager wants to apply a discount to all Products with a expiration date less than 2 days | ExpirationDate is returned | | 7, 9 | Product | Price (prop) | User wants to know the price of a product | returns value of Product | | | | | | | diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs new file mode 100644 index 00000000..d7d4395c --- /dev/null +++ b/exercise.main/Basket.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace exercise.main +{ + public class Basket + { + private List products = new List(); + + public Basket() {} + + + public void addProduct(BaseProduct product) + { + this.products.Add(product); + } + public int countProductTypes(string SKU) + { + return products.Where(x=>x.SKU == SKU).Count(); + + } + public List getProducts() + { + // Return a deep copy of the list... + var cpyList = new List(); + foreach (BaseProduct product in this.products) + { + cpyList.Add(product); + } + return cpyList; + } + public float? getDefaultPrice(string SKU) + { + + foreach (BaseProduct product in this.products) + { + if(product.SKU == SKU) + { + return product.ProductPrice; + } + } + return null; + + } + + } +} diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs new file mode 100644 index 00000000..bae9c5be --- /dev/null +++ b/exercise.main/Product.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace exercise.main +{ + public struct OrderData + { + public int amount; + public float individual_price; + public float discounted_price; + } + public class Order + { + public Dictionary orderDatas; + public Order(Basket basket) + { + + } + } + public class DiscountManager + { + List discountTypes = new List(); + //public void addDiscountType(params object[] args) where T : Discount + public void addDiscountType(Discount discount) + { + // TODO: Check for and remove identicals... + discountTypes.Add(discount); + } + + private List pickBestDeals(List possibleDiscounts, Basket basket) + { + // Looks for conflicting deals, removes them and favors best value deals + + List> conflictingIndexes = new List>(); + List possibleCombinations= new List(); + + // Count/collect possible conflicting deals + for (int i = 0; i < possibleDiscounts.Count; i++) + { + bool noConflict = true; + for (int j = i+1; j < possibleDiscounts.Count; j++) + { + foreach (var key in possibleDiscounts[j].SKU_amount.Keys) + { + + if(possibleDiscounts[i].SKU_amount.ContainsKey(key)) + { + conflictingIndexes.Add(new (i, j, key)); + noConflict = false; + } + } + } + if (noConflict) + { + possibleCombinations.Add(possibleDiscounts[i]); + } + } + + foreach(var c in conflictingIndexes) + { + int index_i = c.Item1; + int index_j = c.Item2; + string SKU = c.Item3; + //float? defprice = basket.getDefaultPrice(SKU); + //if (defprice == null) + //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); + + if (possibleDiscounts[index_i].possibleSavings > possibleDiscounts[index_j].possibleSavings) + { + possibleCombinations.Add(possibleDiscounts[index_i]); + } + else + { + possibleCombinations.Add(possibleDiscounts[index_j]); + + } + + + //defprice. + + } + + return possibleCombinations; + } + + //public Order calculateDiscount(Basket basket) + public void calculateDiscount(Basket basket) + { + List possibleDiscounts = new List(); + foreach (Discount discount in discountTypes) + { + if (discount.checkCondition(basket)) + { + possibleDiscounts.Add(discount.getDiscountedPrice(basket)); + } + } + + var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); + + List orderData = new List(); + + var productList = basket.getProducts(); + + // Remove Discounted products from the product list... + foreach (var discount in bestDealsDiscounts) + { + + foreach ( var di in discount.SKU_amount) + { + var sku = di.Key; + int amount = di.Value; + + int counted = 0; + List temp = new List(); + + foreach ( var product in productList) + { + if (sku != product.SKU || counted >= amount) + { + temp.Add(product); + } + else + { + counted++; + } + } + + productList = temp; + + + } + } + + Dictionary> nameAmountPrice = new Dictionary>(); + + foreach (var product in bestDealsDiscounts) + { + string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); + + nameAmountPrice[nameStr] = new (product.discountMultiple, product.finalPrice); + } + + Dictionary tempSku = new Dictionary(); + foreach (var product in productList) + { + if(!tempSku.ContainsKey(product.SKU)) + { + tempSku[product.SKU] = 0; + } + tempSku[product.SKU]++; + } + + foreach (var product in tempSku) + { + + float? defPrice = basket.getDefaultPrice(product.Key); + if (defPrice == null) + Debug.Assert(false, "defPrice cant be zero..."); + + nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); + } + + + Console.WriteLine("hej"); + + + } + } + public abstract class Discount + { + public Discount(float discountedPrice) + { + this.discountPrice = discountedPrice; + } + //private Func discountConditionFunc; + //private Action discountConditionFunc; + + //public void defineCondition(Func func ) + private float discountPrice; + + public float DiscountPrice { get => discountPrice; } + + public abstract bool checkCondition(Basket basket); + public abstract DiscountedProductCount getDiscountedPrice(Basket basket); + } + + public class Discount_XforY : Discount + { + Dictionary nrOfRequiredProducts = new Dictionary(); + + public Discount_XforY(Dictionary nrOfRequiredProductsSKU, float discountedPrice) + : base(discountedPrice) + { + this.nrOfRequiredProducts = nrOfRequiredProductsSKU; + + } + public override bool checkCondition(Basket basket) + { + foreach (var discountReq in this.nrOfRequiredProducts) + { + if (basket.countProductTypes(discountReq.Key) < discountReq.Value) + return false; + } + return true; + } + + public override DiscountedProductCount getDiscountedPrice(Basket basket) + { + Dictionary discountedProductSKU = new Dictionary(); + float totalCost_withoutDiscount = 0.0f; + float totalCost_withDiscount = 0.0f; + float discountedSavings = 0.0f; + + foreach (var discountReq in this.nrOfRequiredProducts) + { + string SKU = discountReq.Key; + int requiredProduct = discountReq.Value; + int d = (int)MathF.Floor(basket.countProductTypes(SKU) / discountReq.Value); + + float? defprice = basket.getDefaultPrice(SKU); + //if (defprice == null) + Debug.Assert(defprice != null, "expected SKU to exist in basket..."); + + totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); + + discountedProductSKU[SKU] = d; + } + + // The max possible discount is limited by the smalletst multiplier + float maxDiscountMultiplier = discountedProductSKU.ToList().Min(x => x.Value); + + totalCost_withDiscount = this.DiscountPrice * maxDiscountMultiplier; + + discountedSavings = this.DiscountPrice - totalCost_withoutDiscount; + + Dictionary nrOfDiscounted = new Dictionary(); + foreach(var discountReq in this.nrOfRequiredProducts) + { + string SKU = discountReq.Key; + int requiredProduct = discountReq.Value; + nrOfDiscounted[SKU] = (int)maxDiscountMultiplier * requiredProduct; + } + + DiscountedProductCount dp = new DiscountedProductCount + { + //SKU_amount = discountedProductSKU, + SKU_amount = nrOfDiscounted, + //discountMultiple = maxDiscountMultiplier , + discountMultiple = (int)maxDiscountMultiplier , + possibleSavings = discountedSavings, + finalPrice = totalCost_withDiscount + }; + + return dp; + } + } + + public class DiscountedProductCount + { + public Dictionary SKU_amount = new Dictionary(); + public int discountMultiple = 0; + public float possibleSavings = 0.0f; + public float finalPrice = 0.0f; + } + + + public abstract class BaseProduct + { + protected string name; + private string sku; + protected float productPrice; + protected List subProducts; + protected ProductType productType; + public BaseProduct(string SKU,string name, float defaultPrice, ProductType productType, List? subProducts = null) + { + this.sku = SKU; + this.name = name; + this.productPrice = defaultPrice; + this.subProducts = subProducts ?? new List(); + this.productType = productType; + } + public float CombinedPrice + { + get { return this.productPrice + subProducts.Sum(x => x.CombinedPrice); } + } + public float ProductPrice + { + get { return this.productPrice; } + } + + public string SKU { get => sku;} + } + + public class Product: BaseProduct where T : ProductType, new() + { + public Product(string SKU, string name, float defaultPrice,List? subProducts = null) + : base(SKU, name, defaultPrice, new T(),subProducts) + { + } + } + + + public interface ProductType + { + public float calcPrice(); + public string TypeName() + { + return GetType().Name; + } + } + public class Filling : ProductType + { + public float calcPrice() + { + throw new NotImplementedException(); + } + } + public class Coffee : ProductType + { + public float calcPrice() + { + throw new NotImplementedException(); + } + } + public class Bagel : ProductType + { + public Bagel() { } + public float calcPrice() + { + throw new NotImplementedException(); + } + } + +} diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 7bdb8968..cc97b865 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,15 +1,124 @@ +using System.Linq.Expressions; +using System.Reflection.Metadata.Ecma335; +using exercise.main; namespace exercise.tests; public class Tests { - [SetUp] - public void Setup() + + [Test] + public void CreateBasket() { + var p = new Basket(); + Assert.Pass(); } + + [Test] + public void addProductToBasket() + { + var p = new Basket(); + var b = new Product ("BGLO", "Onion", 0.49f); + var h = new Product("FILH", "Ham", 0.12f); + var c = new Product("FILC", "Cheese", 0.12f); + var l = new Product("FILE", "Egg", 0.12f); + + p.addProduct(b); + p.addProduct(h); + p.addProduct(c); + p.addProduct(l); + Assert.Pass(); + } [Test] - public void Test1() + public void createDiscountType() { + var p = new Basket(); + + var b = new Product("BGLO", "Onion", 0.49f); + var h = new Product("FILH", "Ham", 0.12f); + var c = new Product("FILC", "Cheese", 0.12f); + var l = new Product("FILE", "Egg", 0.12f); + + //for (var i = 0; i < 6; i++) + for (var i = 0; i < 24; i++) + { + p.addProduct(b); + } + p.addProduct(h); + p.addProduct(c); + p.addProduct(l); + p.addProduct(l); + + + //var d = new Discount_XforY(); + var discountReq = new Dictionary { { "BGLO" , 6} }; + var d = new Discount_XforY(discountReq, 2.49f); + + + + //Func lambda = x => { + // return x; + //}; + + //Action lambda =( ) => { + // return 3; + //}; + + //d.defineCondition(lambda); + + DiscountManager dm = new DiscountManager(); + + dm.addDiscountType(d); + + + //Order o = dm.calculateDiscount(p); + dm.calculateDiscount(p); + + + Assert.Pass(); } + + //[TestCase("Onion", 2.5f)] + //public void CreateProduct(string productName, float productPrice) + //{ + // var p = new Product(productName, productPrice); + // Assert.Pass(); + //} + + //[TestCase("Bread", 2.5f)] + //public void CreateProduct_withSubProducts(string productName, float productPrice, params Tuple[] subProducts) + //{ + + // var products = new List() + // { + // new Product("Ham", 0.5f), + // new Product("Cheese", 0.5f), + // new Product("Lettuce", 0.5f), + // }; + + // var p = new Product(productName, productPrice, products); + + + // Assert.Pass(); + //} + + //[TestCase("Bread", 2.5f)] + //public void CreateProduct_withSubProducts_getPrice(string productName, float productPrice) + //{ + + // var products = new List() + // { + // new Product("Ham", 0.5f), + // new Product("Cheese", 0.5f), + // new Product("Lettuce", 0.5f), + // }; + + // var expectedCost = products.Sum(p => p.CombinedPrice) + productPrice; + + // var p = new Product(productName, productPrice, products); + + + // Assert.That( p.CombinedPrice == expectedCost); + //} } \ No newline at end of file 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 a99e4301fd56fc49bbd9f9e196ccc9cf4601ddc3 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Mon, 13 Jan 2025 13:13:21 +0100 Subject: [PATCH 03/10] Will redo Generic ProductType... useless now --- exercise.main/Basket.cs | 14 ++++ exercise.main/Inventory.cs | 131 ++++++++++++++++++++++++++++++++ exercise.main/Product.cs | 80 ++++++++++++++------ exercise.tests/UnitTest1.cs | 145 ++++++++++++++++++++++-------------- 4 files changed, 293 insertions(+), 77 deletions(-) create mode 100644 exercise.main/Inventory.cs diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index d7d4395c..31ec219b 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -32,6 +32,20 @@ public List getProducts() } return cpyList; } + public Dictionary getAmountPerSku() + { + var productList = getProducts(); + Dictionary tempSku = new Dictionary(); + foreach (var product in productList) + { + if (!tempSku.ContainsKey(product.SKU)) + { + tempSku[product.SKU] = 0; + } + tempSku[product.SKU]++; + } + return tempSku; + } public float? getDefaultPrice(string SKU) { diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs new file mode 100644 index 00000000..bc48a4b7 --- /dev/null +++ b/exercise.main/Inventory.cs @@ -0,0 +1,131 @@ +using System.Diagnostics; +using System.IO.Pipelines; + +namespace exercise.main +{ + public class InventoryData + { + + public string name; + public string SKU; + public float price; + public int stock; + + public InventoryData(string name, string sKU, float price, int stock) + { + this.name = name; + SKU = sKU; + this.price = price; + this.stock = stock; + } + } + public class Inventory + { + Dictionary inventory = new Dictionary(); + public Inventory() + { + } + public void Add(string SKU, string name, float price, int stock ) + { + if (inventory.ContainsKey(SKU)) + { + Console.WriteLine("SKU already added to Inventory... Edit"); + Debug.Assert(inventory.ContainsKey(SKU)); + return; + } + + inventory.Add(SKU,new InventoryData(name, SKU, price, stock)); + } + public void Add(BaseProduct product, int stock) + { + if (inventory.ContainsKey(product.SKU)) + { + Console.WriteLine("SKU already added to Inventory... Edit"); + Debug.Assert(inventory.ContainsKey(product.SKU)); + return; + } + + inventory.Add(product.SKU, new InventoryData(product.ProductName, product.SKU, product.ProductPrice, stock)); + } + public void IncreaseStock(string SKU, int additionToStock ) + { + if (inventory.ContainsKey(SKU)) + { + inventory[SKU].stock += additionToStock; + } + else + { + Console.WriteLine("SKU Doesnt exist, can't increase Stock"); + Debug.Assert(inventory.ContainsKey(SKU)); + return; + } + } + public void decreaseStock(string SKU, int removeFromStock ) + { + if (inventory.ContainsKey(SKU)) + { + inventory[SKU].stock -= removeFromStock; + } + else + { + Console.WriteLine("SKU Doesnt exist, can't decrease Stock"); + Debug.Assert(inventory.ContainsKey(SKU)); + return; + } + } + public int getStock(string SKU) + { + if (inventory.ContainsKey(SKU)) + { + return inventory[SKU].stock; + } + else + { + Console.WriteLine("SKU Doesnt exist, no stock to return"); + return 0; + } + } + public void EditPrice(string SKU, float newPrice) + { + if (inventory.ContainsKey(SKU)) + { + inventory[SKU].price = newPrice; + } + else + { + Console.WriteLine("SKU Doesnt exist, can't Edit Price"); + Debug.Assert(inventory.ContainsKey(SKU)); + return; + } + + } + public float getPrice(string SKU) + { + if (inventory.ContainsKey(SKU)) + { + return inventory[SKU].price; + } + else + { + Console.WriteLine("SKU Doesnt exist, no price to return"); + Debug.Assert(inventory.ContainsKey(SKU)); + return 0; + } + + } + public string getName(string SKU) + { + if (inventory.ContainsKey(SKU)) + { + return inventory[SKU].name; + } + else + { + Console.WriteLine("SKU Doesnt exist, no name to return"); + Debug.Assert(inventory.ContainsKey(SKU)); + return ""; + } + + } + } +} \ No newline at end of file diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index bae9c5be..48d0bc32 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -10,9 +10,12 @@ namespace exercise.main { public struct OrderData { + public string name; public int amount; public float individual_price; public float discounted_price; + public float total_price; + public float saving; } public class Order { @@ -24,8 +27,14 @@ public Order(Basket basket) } public class DiscountManager { + List discountTypes = new List(); //public void addDiscountType(params object[] args) where T : Discount + private Inventory _inventory; + public DiscountManager(Inventory inventory) + { + this._inventory = inventory; + } public void addDiscountType(Discount discount) { // TODO: Check for and remove identicals... @@ -89,7 +98,7 @@ private List pickBestDeals(List } //public Order calculateDiscount(Basket basket) - public void calculateDiscount(Basket basket) + public Dictionary calculateDiscount(Basket basket) { List possibleDiscounts = new List(); foreach (Discount discount in discountTypes) @@ -136,52 +145,77 @@ public void calculateDiscount(Basket basket) } } - Dictionary> nameAmountPrice = new Dictionary>(); + //Dictionary> nameAmountPrice = new Dictionary>(); + Dictionary nameAmountPrice = new Dictionary(); foreach (var product in bestDealsDiscounts) { - string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); + //string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); + string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => this._inventory.getName(x))); - nameAmountPrice[nameStr] = new (product.discountMultiple, product.finalPrice); + nameAmountPrice[nameStr] = new OrderData + { + name = nameStr, + amount = product.discountMultiple, + individual_price = 0.0f, // TODO: fix ... + discounted_price = product.finalPrice, + total_price = product.finalPrice, + saving = product.possibleSavings, + + }; } - Dictionary tempSku = new Dictionary(); + Dictionary amontPerSku = new Dictionary(); foreach (var product in productList) { - if(!tempSku.ContainsKey(product.SKU)) + if (!amontPerSku.ContainsKey(product.SKU)) { - tempSku[product.SKU] = 0; + amontPerSku[product.SKU] = 0; } - tempSku[product.SKU]++; + amontPerSku[product.SKU]++; } - - foreach (var product in tempSku) - { - float? defPrice = basket.getDefaultPrice(product.Key); - if (defPrice == null) - Debug.Assert(false, "defPrice cant be zero..."); - - nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); - } + //var amontPerSku = basket.getAmountPerSku(); + foreach (var product in amontPerSku) + { + //float? defPrice = basket.getDefaultPrice(product.Key); + //float? defPrice = this._inventory.getPrice(product.Key); + //if (defPrice == null) + //Debug.Assert(false, "defPrice cant be zero..."); - Console.WriteLine("hej"); + float defPrice = this._inventory.getPrice(product.Key); + //nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); + nameAmountPrice[product.Key] = new OrderData + { + name = product.Key, + amount = product.Value, + //individual_price = defPrice.Value, + individual_price = defPrice, + discounted_price = 0.0f, // TODO: fix + //total_price = defPrice.Value * product.Value, + total_price = defPrice * product.Value, + saving = 0.0f, + }; + } + return nameAmountPrice; } } public abstract class Discount { - public Discount(float discountedPrice) + public Discount(float discountedPrice, Inventory inventory) { this.discountPrice = discountedPrice; + this._inventory = inventory; } //private Func discountConditionFunc; //private Action discountConditionFunc; //public void defineCondition(Func func ) private float discountPrice; + protected Inventory _inventory; public float DiscountPrice { get => discountPrice; } @@ -193,8 +227,8 @@ public class Discount_XforY : Discount { Dictionary nrOfRequiredProducts = new Dictionary(); - public Discount_XforY(Dictionary nrOfRequiredProductsSKU, float discountedPrice) - : base(discountedPrice) + public Discount_XforY(Dictionary nrOfRequiredProductsSKU, float discountedPrice, Inventory inventory) + : base(discountedPrice, inventory) { this.nrOfRequiredProducts = nrOfRequiredProductsSKU; @@ -292,6 +326,10 @@ public float ProductPrice { get { return this.productPrice; } } + public string ProductName + { + get { return this.name; } + } public string SKU { get => sku;} } diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index cc97b865..b28b02c9 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; using System.Reflection.Metadata.Ecma335; using exercise.main; +using NUnit.Framework.Constraints; namespace exercise.tests; public class Tests @@ -34,91 +35,123 @@ public void createDiscountType() { var p = new Basket(); + var b = new Product("BGLO", "Onion", 0.49f); var h = new Product("FILH", "Ham", 0.12f); var c = new Product("FILC", "Cheese", 0.12f); var l = new Product("FILE", "Egg", 0.12f); - //for (var i = 0; i < 6; i++) - for (var i = 0; i < 24; i++) - { - p.addProduct(b); - } + Inventory inventory = new Inventory(); + inventory.Add(b,50); + inventory.Add(h,50); + inventory.Add(c,50); + inventory.Add(l,50); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO" , nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + // Add deal to DiscountManager + DiscountManager dm = new DiscountManager(inventory); + dm.addDiscountType(d); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + + p.addProduct(h); + totalPrice += h.ProductPrice; p.addProduct(c); + totalPrice += c.ProductPrice; p.addProduct(l); + totalPrice += l.ProductPrice; p.addProduct(l); + totalPrice += l.ProductPrice; + // Add discounted Products, calculate sum based on the discount + int nrOf_OnionBagels = 24; + for (var i = 0; i < nrOf_OnionBagels; i++) + { + p.addProduct(b); + } - //var d = new Discount_XforY(); - var discountReq = new Dictionary { { "BGLO" , 6} }; - var d = new Discount_XforY(discountReq, 2.49f); - + totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + // calculate Rest + totalPrice += ((float)nrOf_OnionBagels % nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; - //Func lambda = x => { - // return x; - //}; - //Action lambda =( ) => { - // return 3; - //}; + //Order o = dm.calculateDiscount(p); + var orderDataDict = dm.calculateDiscount(p); - //d.defineCondition(lambda); + var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); - DiscountManager dm = new DiscountManager(); + Assert.That(totalPrice, Is.EqualTo(returnedTotal)); - dm.addDiscountType(d); + } + [Test] + public void createInventory() + { + Inventory inventory = new Inventory(); + Assert.Pass(); - //Order o = dm.calculateDiscount(p); - dm.calculateDiscount(p); + } + [Test] + public void inventory_add() + { + Inventory inventory = new Inventory(); + inventory.Add("BGLO", "Onion", 0.49f, 10); + Assert.That(inventory.getStock("BGLO"), Is.EqualTo(10)); + Assert.That(inventory.getName("BGLO"), Is.EqualTo("Onion")); + Assert.That(inventory.getPrice("BGLO"), Is.EqualTo(0.49f)); - Assert.Pass(); } - //[TestCase("Onion", 2.5f)] - //public void CreateProduct(string productName, float productPrice) - //{ - // var p = new Product(productName, productPrice); - // Assert.Pass(); - //} + //[TestCase("Onion", 2.5f)] + //public void CreateProduct(string productName, float productPrice) + //{ + // var p = new Product(productName, productPrice); + // Assert.Pass(); + //} - //[TestCase("Bread", 2.5f)] - //public void CreateProduct_withSubProducts(string productName, float productPrice, params Tuple[] subProducts) - //{ + //[TestCase("Bread", 2.5f)] + //public void CreateProduct_withSubProducts(string productName, float productPrice, params Tuple[] subProducts) + //{ - // var products = new List() - // { - // new Product("Ham", 0.5f), - // new Product("Cheese", 0.5f), - // new Product("Lettuce", 0.5f), - // }; + // var products = new List() + // { + // new Product("Ham", 0.5f), + // new Product("Cheese", 0.5f), + // new Product("Lettuce", 0.5f), + // }; - // var p = new Product(productName, productPrice, products); + // var p = new Product(productName, productPrice, products); - // Assert.Pass(); - //} - - //[TestCase("Bread", 2.5f)] - //public void CreateProduct_withSubProducts_getPrice(string productName, float productPrice) - //{ - - // var products = new List() - // { - // new Product("Ham", 0.5f), - // new Product("Cheese", 0.5f), - // new Product("Lettuce", 0.5f), - // }; - - // var expectedCost = products.Sum(p => p.CombinedPrice) + productPrice; + // Assert.Pass(); + //} + + //[TestCase("Bread", 2.5f)] + //public void CreateProduct_withSubProducts_getPrice(string productName, float productPrice) + //{ + + // var products = new List() + // { + // new Product("Ham", 0.5f), + // new Product("Cheese", 0.5f), + // new Product("Lettuce", 0.5f), + // }; + + // var expectedCost = products.Sum(p => p.CombinedPrice) + productPrice; - // var p = new Product(productName, productPrice, products); + // var p = new Product(productName, productPrice, products); - // Assert.That( p.CombinedPrice == expectedCost); - //} -} \ No newline at end of file + // Assert.That( p.CombinedPrice == expectedCost); + //} + } \ No newline at end of file From 5a220a5048caa985fd6970a91f16d7b3fcbe8d40 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Mon, 13 Jan 2025 13:50:41 +0100 Subject: [PATCH 04/10] RFA: Removed unused generics... --- exercise.main/Basket.cs | 31 +++++++++++-- exercise.main/Product.cs | 53 ++++++---------------- exercise.tests/UnitTest1.cs | 87 ++++++++++++++++++++++--------------- 3 files changed, 94 insertions(+), 77 deletions(-) diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index 31ec219b..b36c9981 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,13 +10,37 @@ namespace exercise.main public class Basket { private List products = new List(); + private Inventory _inventory; - public Basket() {} + public Basket(Inventory inventory) + { + this._inventory = inventory; + } - public void addProduct(BaseProduct product) + //public void addProduct(BaseProduct product) + public void addProduct(string productSku, int amount = 1) { - this.products.Add(product); + if (amount < 1) + { + Debug.Assert(amount < 1, "Amount to add must be positive"); + } + + int productStock = this._inventory.getStock(productSku); + if (productStock < amount) + { + Debug.Assert(productStock <= 0, $"There's not enough {productSku} in stock..."); + } + for (int i = 0; i < amount; i++) + { + Product p = new Product( + productSku, + _inventory.getName(productSku), + _inventory.getPrice(productSku) + ); + + this.products.Add(p); + } } public int countProductTypes(string SKU) { diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 48d0bc32..0afca94f 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -256,11 +256,14 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) int requiredProduct = discountReq.Value; int d = (int)MathF.Floor(basket.countProductTypes(SKU) / discountReq.Value); - float? defprice = basket.getDefaultPrice(SKU); - //if (defprice == null) - Debug.Assert(defprice != null, "expected SKU to exist in basket..."); + //float? defprice = basket.getDefaultPrice(SKU); + ////if (defprice == null) + //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); - totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); + var defprice = _inventory.getPrice(SKU); + + //totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); + totalCost_withoutDiscount += discountReq.Value * d * defprice; discountedProductSKU[SKU] = d; } @@ -309,14 +312,15 @@ public abstract class BaseProduct private string sku; protected float productPrice; protected List subProducts; - protected ProductType productType; - public BaseProduct(string SKU,string name, float defaultPrice, ProductType productType, List? subProducts = null) + //protected ProductType productType; + //public BaseProduct(string SKU,string name, float defaultPrice, ProductType productType, List? subProducts = null) + public BaseProduct(string SKU,string name, float defaultPrice, List? subProducts = null) { this.sku = SKU; this.name = name; this.productPrice = defaultPrice; this.subProducts = subProducts ?? new List(); - this.productType = productType; + //this.productType = productType; } public float CombinedPrice { @@ -334,44 +338,15 @@ public string ProductName public string SKU { get => sku;} } - public class Product: BaseProduct where T : ProductType, new() + public class Product: BaseProduct { public Product(string SKU, string name, float defaultPrice,List? subProducts = null) - : base(SKU, name, defaultPrice, new T(),subProducts) + : base(SKU, name, defaultPrice, subProducts) { } } - public interface ProductType - { - public float calcPrice(); - public string TypeName() - { - return GetType().Name; - } - } - public class Filling : ProductType - { - public float calcPrice() - { - throw new NotImplementedException(); - } - } - public class Coffee : ProductType - { - public float calcPrice() - { - throw new NotImplementedException(); - } - } - public class Bagel : ProductType - { - public Bagel() { } - public float calcPrice() - { - throw new NotImplementedException(); - } - } + } diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index b28b02c9..0b206f1c 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -10,42 +10,57 @@ public class Tests [Test] public void CreateBasket() { - var p = new Basket(); + Inventory inventory = new Inventory(); + var p = new Basket(inventory); Assert.Pass(); } [Test] public void addProductToBasket() { - var p = new Basket(); - - var b = new Product ("BGLO", "Onion", 0.49f); - var h = new Product("FILH", "Ham", 0.12f); - var c = new Product("FILC", "Cheese", 0.12f); - var l = new Product("FILE", "Egg", 0.12f); - - p.addProduct(b); - p.addProduct(h); - p.addProduct(c); - p.addProduct(l); + Inventory inventory = new Inventory(); + var p = new Basket(inventory); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + //var b = new Product("BGLO", "Onion", 0.49f); + //var h = new Product("FILH", "Ham", 0.12f); + //var c = new Product("FILC", "Cheese", 0.12f); + //var l = new Product("FILE", "Egg", 0.12f); + + //p.addProduct(b); + //p.addProduct(h); + //p.addProduct(c); + //p.addProduct(l); + p.addProduct("BGLO"); + p.addProduct("FILH"); + p.addProduct("FILC"); + p.addProduct("FILE"); Assert.Pass(); } [Test] public void createDiscountType() { - var p = new Basket(); + Inventory inventory = new Inventory(); + var p = new Basket(inventory); - var b = new Product("BGLO", "Onion", 0.49f); - var h = new Product("FILH", "Ham", 0.12f); - var c = new Product("FILC", "Cheese", 0.12f); - var l = new Product("FILE", "Egg", 0.12f); - - Inventory inventory = new Inventory(); - inventory.Add(b,50); - inventory.Add(h,50); - inventory.Add(c,50); - inventory.Add(l,50); + //var b = new Product("BGLO", "Onion", 0.49f); + //var h = new Product("FILH", "Ham", 0.12f); + //var c = new Product("FILC", "Cheese", 0.12f); + //var l = new Product("FILE", "Egg", 0.12f); + + //inventory.Add(b,50); + //inventory.Add(h,50); + //inventory.Add(c,50); + //inventory.Add(l,50); + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); // Create Discount deal, 6 BGLOO, for 2.49f int nrOfBagelsForDiscount = 6; @@ -61,21 +76,23 @@ public void createDiscountType() float totalPrice = 0.0f; - p.addProduct(h); - totalPrice += h.ProductPrice; - p.addProduct(c); - totalPrice += c.ProductPrice; - p.addProduct(l); - totalPrice += l.ProductPrice; - p.addProduct(l); - totalPrice += l.ProductPrice; + p.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + p.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + p.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + p.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); // Add discounted Products, calculate sum based on the discount int nrOf_OnionBagels = 24; - for (var i = 0; i < nrOf_OnionBagels; i++) - { - p.addProduct(b); - } + p.addProduct("BGLO", nrOf_OnionBagels); + //for (var i = 0; i < nrOf_OnionBagels; i++) + //{ + // //p.addProduct(b); + // p.addProduct("BGLO"); + //} totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; From 5ab8c69d06fa48063e317aa2001e117b90d6a308 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Mon, 13 Jan 2025 14:30:44 +0100 Subject: [PATCH 05/10] WIP: Refactoring classes into files --- exercise.main/Basket.cs | 38 +++++-- exercise.main/DiscountManager.cs | 185 +++++++++++++++++++++++++++++++ exercise.main/Product.cs | 178 ----------------------------- exercise.tests/UnitTest1.cs | 70 +++++++----- 4 files changed, 254 insertions(+), 217 deletions(-) create mode 100644 exercise.main/DiscountManager.cs diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index b36c9981..a1e4e7bf 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -42,6 +42,22 @@ public void addProduct(string productSku, int amount = 1) this.products.Add(p); } } + public void removeProduct(string productSku, int amount = 1) + { + if (amount < 1) + { + Debug.Assert(amount < 1, "Amount to remove must be positive"); + } + + int amountProductInBasket = this.countProductTypes(productSku); + int nrToRemove = Math.Min(amountProductInBasket, amount); + var itemsToRemoveList = this.products.Where(x => x.SKU == productSku).ToList(); + for (int i = 0; i < nrToRemove; i++) + { + this.products.Remove(itemsToRemoveList[i]); + } + Console.WriteLine("he"); + } public int countProductTypes(string SKU) { return products.Where(x=>x.SKU == SKU).Count(); @@ -71,19 +87,19 @@ public Dictionary getAmountPerSku() } return tempSku; } - public float? getDefaultPrice(string SKU) - { + //public float? getDefaultPrice(string SKU) + //{ - foreach (BaseProduct product in this.products) - { - if(product.SKU == SKU) - { - return product.ProductPrice; - } - } - return null; + // foreach (BaseProduct product in this.products) + // { + // if(product.SKU == SKU) + // { + // return product.ProductPrice; + // } + // } + // return null; - } + //} } } diff --git a/exercise.main/DiscountManager.cs b/exercise.main/DiscountManager.cs new file mode 100644 index 00000000..522ceeaa --- /dev/null +++ b/exercise.main/DiscountManager.cs @@ -0,0 +1,185 @@ +namespace exercise.main +{ + public class DiscountManager + { + + List discountTypes = new List(); + //public void addDiscountType(params object[] args) where T : Discount + private Inventory _inventory; + public DiscountManager(Inventory inventory) + { + this._inventory = inventory; + } + public void addDiscountType(Discount discount) + { + // TODO: Check for and remove identicals... + discountTypes.Add(discount); + } + + private List pickBestDeals(List possibleDiscounts, Basket basket) + { + // Looks for conflicting deals, removes them and favors best value deals + + List> conflictingIndexes = new List>(); + List possibleCombinations= new List(); + + // Count/collect possible conflicting deals + for (int i = 0; i < possibleDiscounts.Count; i++) + { + bool noConflict = true; + for (int j = i+1; j < possibleDiscounts.Count; j++) + { + foreach (var key in possibleDiscounts[j].SKU_amount.Keys) + { + + if(possibleDiscounts[i].SKU_amount.ContainsKey(key)) + { + conflictingIndexes.Add(new (i, j, key)); + noConflict = false; + } + } + } + if (noConflict) + { + possibleCombinations.Add(possibleDiscounts[i]); + } + } + + foreach(var c in conflictingIndexes) + { + int index_i = c.Item1; + int index_j = c.Item2; + string SKU = c.Item3; + //float? defprice = basket.getDefaultPrice(SKU); + //if (defprice == null) + //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); + + if (possibleDiscounts[index_i].possibleSavings > possibleDiscounts[index_j].possibleSavings) + { + possibleCombinations.Add(possibleDiscounts[index_i]); + } + else + { + possibleCombinations.Add(possibleDiscounts[index_j]); + + } + + + //defprice. + + } + + return possibleCombinations; + } + + //public Order calculateDiscount(Basket basket) + public Dictionary calculateDiscount(Basket basket) + { + List possibleDiscounts = new List(); + foreach (Discount discount in discountTypes) + { + if (discount.checkCondition(basket)) + { + possibleDiscounts.Add(discount.getDiscountedPrice(basket)); + } + } + + var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); + + List orderData = new List(); + + var productList = basket.getProducts(); + + // Remove Discounted products from the product list... + foreach (var discount in bestDealsDiscounts) + { + + foreach ( var di in discount.SKU_amount) + { + var sku = di.Key; + int amount = di.Value; + + int counted = 0; + List temp = new List(); + + foreach ( var product in productList) + { + if (sku != product.SKU || counted >= amount) + { + temp.Add(product); + } + else + { + counted++; + } + } + + productList = temp; + + + } + } + + //Dictionary> nameAmountPrice = new Dictionary>(); + Dictionary nameAmountPrice = new Dictionary(); + + foreach (var product in bestDealsDiscounts) + { + //string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); + string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => this._inventory.getName(x))); + + nameAmountPrice[nameStr] = new OrderData + { + name = nameStr, + amount = product.discountMultiple, + individual_price = 0.0f, // TODO: fix ... + discounted_price = product.finalPrice, + total_price = product.finalPrice, + saving = product.possibleSavings, + + }; + } + + Dictionary amontPerSku = new Dictionary(); + foreach (var product in productList) + { + if (!amontPerSku.ContainsKey(product.SKU)) + { + amontPerSku[product.SKU] = 0; + } + amontPerSku[product.SKU]++; + } + + //var amontPerSku = basket.getAmountPerSku(); + foreach (var product in amontPerSku) + { + + //float? defPrice = basket.getDefaultPrice(product.Key); + //float? defPrice = this._inventory.getPrice(product.Key); + //if (defPrice == null) + //Debug.Assert(false, "defPrice cant be zero..."); + + float defPrice = this._inventory.getPrice(product.Key); + + //nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); + nameAmountPrice[product.Key] = new OrderData + { + name = product.Key, + amount = product.Value, + //individual_price = defPrice.Value, + individual_price = defPrice, + discounted_price = 0.0f, // TODO: fix + //total_price = defPrice.Value * product.Value, + total_price = defPrice * product.Value, + saving = 0.0f, + }; + } + + return nameAmountPrice; + } + } + + + + +} diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 0afca94f..aac41434 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -25,184 +25,6 @@ public Order(Basket basket) } } - public class DiscountManager - { - - List discountTypes = new List(); - //public void addDiscountType(params object[] args) where T : Discount - private Inventory _inventory; - public DiscountManager(Inventory inventory) - { - this._inventory = inventory; - } - public void addDiscountType(Discount discount) - { - // TODO: Check for and remove identicals... - discountTypes.Add(discount); - } - - private List pickBestDeals(List possibleDiscounts, Basket basket) - { - // Looks for conflicting deals, removes them and favors best value deals - - List> conflictingIndexes = new List>(); - List possibleCombinations= new List(); - - // Count/collect possible conflicting deals - for (int i = 0; i < possibleDiscounts.Count; i++) - { - bool noConflict = true; - for (int j = i+1; j < possibleDiscounts.Count; j++) - { - foreach (var key in possibleDiscounts[j].SKU_amount.Keys) - { - - if(possibleDiscounts[i].SKU_amount.ContainsKey(key)) - { - conflictingIndexes.Add(new (i, j, key)); - noConflict = false; - } - } - } - if (noConflict) - { - possibleCombinations.Add(possibleDiscounts[i]); - } - } - - foreach(var c in conflictingIndexes) - { - int index_i = c.Item1; - int index_j = c.Item2; - string SKU = c.Item3; - //float? defprice = basket.getDefaultPrice(SKU); - //if (defprice == null) - //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); - - if (possibleDiscounts[index_i].possibleSavings > possibleDiscounts[index_j].possibleSavings) - { - possibleCombinations.Add(possibleDiscounts[index_i]); - } - else - { - possibleCombinations.Add(possibleDiscounts[index_j]); - - } - - - //defprice. - - } - - return possibleCombinations; - } - - //public Order calculateDiscount(Basket basket) - public Dictionary calculateDiscount(Basket basket) - { - List possibleDiscounts = new List(); - foreach (Discount discount in discountTypes) - { - if (discount.checkCondition(basket)) - { - possibleDiscounts.Add(discount.getDiscountedPrice(basket)); - } - } - - var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); - - List orderData = new List(); - - var productList = basket.getProducts(); - - // Remove Discounted products from the product list... - foreach (var discount in bestDealsDiscounts) - { - - foreach ( var di in discount.SKU_amount) - { - var sku = di.Key; - int amount = di.Value; - - int counted = 0; - List temp = new List(); - - foreach ( var product in productList) - { - if (sku != product.SKU || counted >= amount) - { - temp.Add(product); - } - else - { - counted++; - } - } - - productList = temp; - - - } - } - - //Dictionary> nameAmountPrice = new Dictionary>(); - Dictionary nameAmountPrice = new Dictionary(); - - foreach (var product in bestDealsDiscounts) - { - //string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); - string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => this._inventory.getName(x))); - - nameAmountPrice[nameStr] = new OrderData - { - name = nameStr, - amount = product.discountMultiple, - individual_price = 0.0f, // TODO: fix ... - discounted_price = product.finalPrice, - total_price = product.finalPrice, - saving = product.possibleSavings, - - }; - } - - Dictionary amontPerSku = new Dictionary(); - foreach (var product in productList) - { - if (!amontPerSku.ContainsKey(product.SKU)) - { - amontPerSku[product.SKU] = 0; - } - amontPerSku[product.SKU]++; - } - - //var amontPerSku = basket.getAmountPerSku(); - foreach (var product in amontPerSku) - { - - //float? defPrice = basket.getDefaultPrice(product.Key); - //float? defPrice = this._inventory.getPrice(product.Key); - //if (defPrice == null) - //Debug.Assert(false, "defPrice cant be zero..."); - - float defPrice = this._inventory.getPrice(product.Key); - - //nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); - nameAmountPrice[product.Key] = new OrderData - { - name = product.Key, - amount = product.Value, - //individual_price = defPrice.Value, - individual_price = defPrice, - discounted_price = 0.0f, // TODO: fix - //total_price = defPrice.Value * product.Value, - total_price = defPrice * product.Value, - saving = 0.0f, - }; - } - - return nameAmountPrice; - } - } public abstract class Discount { public Discount(float discountedPrice, Inventory inventory) diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 0b206f1c..b2ad388d 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -19,44 +19,63 @@ public void CreateBasket() public void addProductToBasket() { Inventory inventory = new Inventory(); - var p = new Basket(inventory); + var b = new Basket(inventory); inventory.Add("BGLO", "Onion", 0.49f, 100); inventory.Add("FILH", "Ham", 0.12f, 100); inventory.Add("FILC", "Cheese", 0.12f, 100); inventory.Add("FILE", "Egg", 0.12f, 100); - //var b = new Product("BGLO", "Onion", 0.49f); - //var h = new Product("FILH", "Ham", 0.12f); - //var c = new Product("FILC", "Cheese", 0.12f); - //var l = new Product("FILE", "Egg", 0.12f); + b.addProduct("BGLO"); + b.addProduct("FILH"); + b.addProduct("FILC"); + b.addProduct("FILE"); - //p.addProduct(b); - //p.addProduct(h); - //p.addProduct(c); - //p.addProduct(l); - p.addProduct("BGLO"); - p.addProduct("FILH"); - p.addProduct("FILC"); - p.addProduct("FILE"); + var productsInBasket = b.getAmountPerSku(); + Assert.That(productsInBasket.ContainsKey("BGLO")); + Assert.That(productsInBasket.ContainsKey("FILH")); + Assert.That(productsInBasket.ContainsKey("FILC")); + Assert.That(productsInBasket.ContainsKey("FILE")); Assert.Pass(); } + + [Test] + public void removeProductToBasket() + { + Inventory inventory = new Inventory(); + var b = new Basket(inventory); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO",30); + b.addProduct("FILH",25); + b.addProduct("BGLS", 25); + b.addProduct("FILC",20); + b.addProduct("FILE",10); + + b.removeProduct("FILC", 20); + b.removeProduct("BGLS", 5); + b.removeProduct("FILH", 100); + + var productsInBasket = b.getAmountPerSku(); + Assert.That(productsInBasket["BGLO"] == 30); + Assert.That(productsInBasket.ContainsKey("FILH") == false); + Assert.That(productsInBasket["BGLS"] == 20); + Assert.That(productsInBasket.ContainsKey("FILC") == false); + Assert.That(productsInBasket["FILE"] == 10); + + + } [Test] public void createDiscountType() { Inventory inventory = new Inventory(); var p = new Basket(inventory); - - //var b = new Product("BGLO", "Onion", 0.49f); - //var h = new Product("FILH", "Ham", 0.12f); - //var c = new Product("FILC", "Cheese", 0.12f); - //var l = new Product("FILE", "Egg", 0.12f); - - //inventory.Add(b,50); - //inventory.Add(h,50); - //inventory.Add(c,50); - //inventory.Add(l,50); inventory.Add("BGLO", "Onion", 0.49f, 50); inventory.Add("FILH", "Ham", 0.12f, 50); inventory.Add("FILC", "Cheese", 0.12f, 50); @@ -88,11 +107,6 @@ public void createDiscountType() // Add discounted Products, calculate sum based on the discount int nrOf_OnionBagels = 24; p.addProduct("BGLO", nrOf_OnionBagels); - //for (var i = 0; i < nrOf_OnionBagels; i++) - //{ - // //p.addProduct(b); - // p.addProduct("BGLO"); - //} totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; From 7e0b7a875e187d940e456865fca227bda7c83644 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Mon, 13 Jan 2025 14:35:37 +0100 Subject: [PATCH 06/10] RFA: moved most classes into their own files... --- exercise.main/Discount/Discount.cs | 26 ++++ .../{ => Discount}/DiscountManager.cs | 56 ++++----- exercise.main/Discount/Discount_XforY.cs | 80 ++++++++++++ .../Discount/DiscountedProductCount.cs | 14 +++ exercise.main/Order.cs | 15 +++ exercise.main/OrderData.cs | 16 +++ exercise.main/Product.cs | 118 ------------------ exercise.tests/UnitTest1.cs | 1 + 8 files changed, 180 insertions(+), 146 deletions(-) create mode 100644 exercise.main/Discount/Discount.cs rename exercise.main/{ => Discount}/DiscountManager.cs (82%) create mode 100644 exercise.main/Discount/Discount_XforY.cs create mode 100644 exercise.main/Discount/DiscountedProductCount.cs create mode 100644 exercise.main/Order.cs create mode 100644 exercise.main/OrderData.cs diff --git a/exercise.main/Discount/Discount.cs b/exercise.main/Discount/Discount.cs new file mode 100644 index 00000000..73517a82 --- /dev/null +++ b/exercise.main/Discount/Discount.cs @@ -0,0 +1,26 @@ +namespace exercise.main.Discount +{ + public abstract class Discount + { + public Discount(float discountedPrice, Inventory inventory) + { + discountPrice = discountedPrice; + _inventory = inventory; + } + //private Func discountConditionFunc; + //private Action discountConditionFunc; + + //public void defineCondition(Func func ) + private float discountPrice; + protected Inventory _inventory; + + public float DiscountPrice { get => discountPrice; } + + public abstract bool checkCondition(Basket basket); + public abstract DiscountedProductCount getDiscountedPrice(Basket basket); + } + + + + +} diff --git a/exercise.main/DiscountManager.cs b/exercise.main/Discount/DiscountManager.cs similarity index 82% rename from exercise.main/DiscountManager.cs rename to exercise.main/Discount/DiscountManager.cs index 522ceeaa..97cffb84 100644 --- a/exercise.main/DiscountManager.cs +++ b/exercise.main/Discount/DiscountManager.cs @@ -1,16 +1,16 @@ -namespace exercise.main +namespace exercise.main.Discount { - public class DiscountManager + public class DiscountManager { - + List discountTypes = new List(); //public void addDiscountType(params object[] args) where T : Discount - private Inventory _inventory; + private Inventory _inventory; public DiscountManager(Inventory inventory) { - this._inventory = inventory; + _inventory = inventory; } - public void addDiscountType(Discount discount) + public void addDiscountType(Discount discount) { // TODO: Check for and remove identicals... discountTypes.Add(discount); @@ -21,20 +21,20 @@ private List pickBestDeals(List // Looks for conflicting deals, removes them and favors best value deals List> conflictingIndexes = new List>(); - List possibleCombinations= new List(); + List possibleCombinations = new List(); // Count/collect possible conflicting deals for (int i = 0; i < possibleDiscounts.Count; i++) { bool noConflict = true; - for (int j = i+1; j < possibleDiscounts.Count; j++) + for (int j = i + 1; j < possibleDiscounts.Count; j++) { foreach (var key in possibleDiscounts[j].SKU_amount.Keys) { - if(possibleDiscounts[i].SKU_amount.ContainsKey(key)) + if (possibleDiscounts[i].SKU_amount.ContainsKey(key)) { - conflictingIndexes.Add(new (i, j, key)); + conflictingIndexes.Add(new(i, j, key)); noConflict = false; } } @@ -45,7 +45,7 @@ private List pickBestDeals(List } } - foreach(var c in conflictingIndexes) + foreach (var c in conflictingIndexes) { int index_i = c.Item1; int index_j = c.Item2; @@ -63,7 +63,7 @@ private List pickBestDeals(List possibleCombinations.Add(possibleDiscounts[index_j]); } - + //defprice. @@ -93,16 +93,16 @@ public Dictionary calculateDiscount(Basket basket) // Remove Discounted products from the product list... foreach (var discount in bestDealsDiscounts) { - - foreach ( var di in discount.SKU_amount) + + foreach (var di in discount.SKU_amount) { - var sku = di.Key; + var sku = di.Key; int amount = di.Value; int counted = 0; List temp = new List(); - - foreach ( var product in productList) + + foreach (var product in productList) { if (sku != product.SKU || counted >= amount) { @@ -113,30 +113,30 @@ public Dictionary calculateDiscount(Basket basket) counted++; } } - - productList = temp; - + productList = temp; + + } } //Dictionary> nameAmountPrice = new Dictionary>(); Dictionary nameAmountPrice = new Dictionary(); - + foreach (var product in bestDealsDiscounts) { //string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); - string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => this._inventory.getName(x))); - + string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => _inventory.getName(x))); + nameAmountPrice[nameStr] = new OrderData { name = nameStr, - amount = product.discountMultiple, + amount = product.discountMultiple, individual_price = 0.0f, // TODO: fix ... discounted_price = product.finalPrice, total_price = product.finalPrice, saving = product.possibleSavings, - + }; } @@ -157,9 +157,9 @@ public Dictionary calculateDiscount(Basket basket) //float? defPrice = basket.getDefaultPrice(product.Key); //float? defPrice = this._inventory.getPrice(product.Key); //if (defPrice == null) - //Debug.Assert(false, "defPrice cant be zero..."); + //Debug.Assert(false, "defPrice cant be zero..."); - float defPrice = this._inventory.getPrice(product.Key); + float defPrice = _inventory.getPrice(product.Key); //nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); nameAmountPrice[product.Key] = new OrderData @@ -172,7 +172,7 @@ public Dictionary calculateDiscount(Basket basket) //total_price = defPrice.Value * product.Value, total_price = defPrice * product.Value, saving = 0.0f, - }; + }; } return nameAmountPrice; diff --git a/exercise.main/Discount/Discount_XforY.cs b/exercise.main/Discount/Discount_XforY.cs new file mode 100644 index 00000000..213cfb5a --- /dev/null +++ b/exercise.main/Discount/Discount_XforY.cs @@ -0,0 +1,80 @@ +namespace exercise.main.Discount +{ + public class Discount_XforY : Discount + { + Dictionary nrOfRequiredProducts = new Dictionary(); + + public Discount_XforY(Dictionary nrOfRequiredProductsSKU, float discountedPrice, Inventory inventory) + : base(discountedPrice, inventory) + { + nrOfRequiredProducts = nrOfRequiredProductsSKU; + + } + public override bool checkCondition(Basket basket) + { + foreach (var discountReq in nrOfRequiredProducts) + { + if (basket.countProductTypes(discountReq.Key) < discountReq.Value) + return false; + } + return true; + } + + public override DiscountedProductCount getDiscountedPrice(Basket basket) + { + Dictionary discountedProductSKU = new Dictionary(); + float totalCost_withoutDiscount = 0.0f; + float totalCost_withDiscount = 0.0f; + float discountedSavings = 0.0f; + + foreach (var discountReq in nrOfRequiredProducts) + { + string SKU = discountReq.Key; + int requiredProduct = discountReq.Value; + int d = (int)MathF.Floor(basket.countProductTypes(SKU) / discountReq.Value); + + //float? defprice = basket.getDefaultPrice(SKU); + ////if (defprice == null) + //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); + + var defprice = _inventory.getPrice(SKU); + + //totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); + totalCost_withoutDiscount += discountReq.Value * d * defprice; + + discountedProductSKU[SKU] = d; + } + + // The max possible discount is limited by the smalletst multiplier + float maxDiscountMultiplier = discountedProductSKU.ToList().Min(x => x.Value); + + totalCost_withDiscount = this.DiscountPrice * maxDiscountMultiplier; + + discountedSavings = this.DiscountPrice - totalCost_withoutDiscount; + + Dictionary nrOfDiscounted = new Dictionary(); + foreach (var discountReq in nrOfRequiredProducts) + { + string SKU = discountReq.Key; + int requiredProduct = discountReq.Value; + nrOfDiscounted[SKU] = (int)maxDiscountMultiplier * requiredProduct; + } + + DiscountedProductCount dp = new DiscountedProductCount + { + //SKU_amount = discountedProductSKU, + SKU_amount = nrOfDiscounted, + //discountMultiple = maxDiscountMultiplier , + discountMultiple = (int)maxDiscountMultiplier, + possibleSavings = discountedSavings, + finalPrice = totalCost_withDiscount + }; + + return dp; + } + } + + + + +} diff --git a/exercise.main/Discount/DiscountedProductCount.cs b/exercise.main/Discount/DiscountedProductCount.cs new file mode 100644 index 00000000..09796e0d --- /dev/null +++ b/exercise.main/Discount/DiscountedProductCount.cs @@ -0,0 +1,14 @@ +namespace exercise.main.Discount +{ + public class DiscountedProductCount + { + public Dictionary SKU_amount = new Dictionary(); + public int discountMultiple = 0; + public float possibleSavings = 0.0f; + public float finalPrice = 0.0f; + } + + + + +} diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs new file mode 100644 index 00000000..077747e4 --- /dev/null +++ b/exercise.main/Order.cs @@ -0,0 +1,15 @@ +namespace exercise.main +{ + public class Order + { + public Dictionary orderDatas; + public Order(Basket basket) + { + + } + } + + + + +} diff --git a/exercise.main/OrderData.cs b/exercise.main/OrderData.cs new file mode 100644 index 00000000..0fce844d --- /dev/null +++ b/exercise.main/OrderData.cs @@ -0,0 +1,16 @@ +namespace exercise.main +{ + public struct OrderData + { + public string name; + public int amount; + public float individual_price; + public float discounted_price; + public float total_price; + public float saving; + } + + + + +} diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index aac41434..84799933 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -8,124 +8,6 @@ namespace exercise.main { - public struct OrderData - { - public string name; - public int amount; - public float individual_price; - public float discounted_price; - public float total_price; - public float saving; - } - public class Order - { - public Dictionary orderDatas; - public Order(Basket basket) - { - - } - } - public abstract class Discount - { - public Discount(float discountedPrice, Inventory inventory) - { - this.discountPrice = discountedPrice; - this._inventory = inventory; - } - //private Func discountConditionFunc; - //private Action discountConditionFunc; - - //public void defineCondition(Func func ) - private float discountPrice; - protected Inventory _inventory; - - public float DiscountPrice { get => discountPrice; } - - public abstract bool checkCondition(Basket basket); - public abstract DiscountedProductCount getDiscountedPrice(Basket basket); - } - - public class Discount_XforY : Discount - { - Dictionary nrOfRequiredProducts = new Dictionary(); - - public Discount_XforY(Dictionary nrOfRequiredProductsSKU, float discountedPrice, Inventory inventory) - : base(discountedPrice, inventory) - { - this.nrOfRequiredProducts = nrOfRequiredProductsSKU; - - } - public override bool checkCondition(Basket basket) - { - foreach (var discountReq in this.nrOfRequiredProducts) - { - if (basket.countProductTypes(discountReq.Key) < discountReq.Value) - return false; - } - return true; - } - - public override DiscountedProductCount getDiscountedPrice(Basket basket) - { - Dictionary discountedProductSKU = new Dictionary(); - float totalCost_withoutDiscount = 0.0f; - float totalCost_withDiscount = 0.0f; - float discountedSavings = 0.0f; - - foreach (var discountReq in this.nrOfRequiredProducts) - { - string SKU = discountReq.Key; - int requiredProduct = discountReq.Value; - int d = (int)MathF.Floor(basket.countProductTypes(SKU) / discountReq.Value); - - //float? defprice = basket.getDefaultPrice(SKU); - ////if (defprice == null) - //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); - - var defprice = _inventory.getPrice(SKU); - - //totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); - totalCost_withoutDiscount += discountReq.Value * d * defprice; - - discountedProductSKU[SKU] = d; - } - - // The max possible discount is limited by the smalletst multiplier - float maxDiscountMultiplier = discountedProductSKU.ToList().Min(x => x.Value); - - totalCost_withDiscount = this.DiscountPrice * maxDiscountMultiplier; - - discountedSavings = this.DiscountPrice - totalCost_withoutDiscount; - - Dictionary nrOfDiscounted = new Dictionary(); - foreach(var discountReq in this.nrOfRequiredProducts) - { - string SKU = discountReq.Key; - int requiredProduct = discountReq.Value; - nrOfDiscounted[SKU] = (int)maxDiscountMultiplier * requiredProduct; - } - - DiscountedProductCount dp = new DiscountedProductCount - { - //SKU_amount = discountedProductSKU, - SKU_amount = nrOfDiscounted, - //discountMultiple = maxDiscountMultiplier , - discountMultiple = (int)maxDiscountMultiplier , - possibleSavings = discountedSavings, - finalPrice = totalCost_withDiscount - }; - - return dp; - } - } - - public class DiscountedProductCount - { - public Dictionary SKU_amount = new Dictionary(); - public int discountMultiple = 0; - public float possibleSavings = 0.0f; - public float finalPrice = 0.0f; - } public abstract class BaseProduct diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index b2ad388d..7b4a8fcc 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; using System.Reflection.Metadata.Ecma335; using exercise.main; +using exercise.main.Discount; using NUnit.Framework.Constraints; namespace exercise.tests; From 4d30cb526b15109287def5d0d8b99be26027c393 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Mon, 13 Jan 2025 18:25:25 +0100 Subject: [PATCH 07/10] Add: program, and more... --- domain-model.md | 6 +- exercise.main/Basket.cs | 43 ++++-- .../Discount/{Discount.cs => DiscountBase.cs} | 5 +- exercise.main/Discount/DiscountManager.cs | 17 ++- exercise.main/Discount/Discount_XforY.cs | 11 +- .../Discount/DiscountedProductCount.cs | 2 + exercise.main/Inventory.cs | 27 +++- exercise.main/OrderData.cs | 8 +- exercise.main/Product.cs | 128 ++++++++++++++++++ exercise.main/Program.cs | 45 +++++- exercise.tests/UnitTest1.cs | 22 +++ 11 files changed, 282 insertions(+), 32 deletions(-) rename exercise.main/Discount/{Discount.cs => DiscountBase.cs} (79%) diff --git a/domain-model.md b/domain-model.md index 63741b04..d669a69a 100644 --- a/domain-model.md +++ b/domain-model.md @@ -101,10 +101,12 @@ I want the customer to see how much they saved on discounts. | User Story Id | Class | Method/Property | Scenario | Output | |---------------|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| -| 1 | Product | Abstract class ? | A Basket contains a list of different Products | Allows for polymorphism | +| 1 | Product | Abstract class ? | A Basket contains a list of different Products | Allows for polymorphism | | 11 | Product | DateTime ExpirationDate | Manager wants to apply a discount to all Products with a expiration date less than 2 days | ExpirationDate is returned | | 7, 9 | Product | Price (prop) | User wants to know the price of a product | returns value of Product | | | | | | | +| 7, 9 | StoreFront | Interface + impl. | Present product and deal information | User interface that shows products, guide user into buying | +| | | | | | | 1 | Basket | Add | User wants to add a bagel to their basket, to purchase | Bagel product added to the basket | | 2 | Basket | Remove | user wants to remove a bagel from their basket, not to be included in the puschase | The removed bagel is no longer part of the Products in the basket | | 3 | Basket | Capacity (prop) | The user tries to add another bagel to their basket, but the basket is full | No new bagel will be added to the basket, basket product list remains unchanged | @@ -122,7 +124,7 @@ I want the customer to see how much they saved on discounts. | 11 | Product | Discount discount | Any product may have a Discount, applied to the final price | | | 11 | Discount | Constructor | class used to represent discounts | | | | | | | | -| 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | +| 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | | 12 | Discount | Condition (lambda) | defines what circumstances the discount is valid for (Limited amount, ) | | | | | | | | | 13 | CashRegister | Constructor | all purchases is handled by the CashRegister | | diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index a1e4e7bf..bc0b747f 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using exercise.main.Discount; namespace exercise.main { @@ -41,6 +42,7 @@ public void addProduct(string productSku, int amount = 1) this.products.Add(p); } + this._inventory.decreaseStock(productSku, amount); } public void removeProduct(string productSku, int amount = 1) { @@ -56,7 +58,7 @@ public void removeProduct(string productSku, int amount = 1) { this.products.Remove(itemsToRemoveList[i]); } - Console.WriteLine("he"); + this._inventory.IncreaseStock(productSku, nrToRemove); } public int countProductTypes(string SKU) { @@ -87,19 +89,32 @@ public Dictionary getAmountPerSku() } return tempSku; } - //public float? getDefaultPrice(string SKU) - //{ - - // foreach (BaseProduct product in this.products) - // { - // if(product.SKU == SKU) - // { - // return product.ProductPrice; - // } - // } - // return null; - - //} + public bool isNotEmpty() + { + return this.products.Count > 0; + } + + public string stringify(DiscountManager dm) + { + var cacledBasket = dm.calculateDiscount(this); + string ret = string.Format("{0,0}{1,25}{2,25}\n", "Name", "Amount", "Cost"); + + foreach (var x in cacledBasket.ToList()) + { + if (x.Value.UsedDiscount == null) + { + ret += "\n"+ string.Format("{0,0}{1,25}{2,25}", _inventory.getName(x.Value.name), x.Value.amount, x.Value.total_price); + } + else + { + ret += "\n"+ string.Format("{0,0}{1,25}{2,25}", x.Value.UsedDiscount.stringify(), x.Value.amount, x.Value.total_price); + } + } + + ret += $"\n\n Total: {cacledBasket.Sum(x => x.Value.total_price)}"; + + return ret; + } } } diff --git a/exercise.main/Discount/Discount.cs b/exercise.main/Discount/DiscountBase.cs similarity index 79% rename from exercise.main/Discount/Discount.cs rename to exercise.main/Discount/DiscountBase.cs index 73517a82..2a3cd3ba 100644 --- a/exercise.main/Discount/Discount.cs +++ b/exercise.main/Discount/DiscountBase.cs @@ -1,8 +1,8 @@ namespace exercise.main.Discount { - public abstract class Discount + public abstract class DiscountBase { - public Discount(float discountedPrice, Inventory inventory) + public DiscountBase(float discountedPrice, Inventory inventory) { discountPrice = discountedPrice; _inventory = inventory; @@ -18,6 +18,7 @@ public Discount(float discountedPrice, Inventory inventory) public abstract bool checkCondition(Basket basket); public abstract DiscountedProductCount getDiscountedPrice(Basket basket); + public abstract string stringify(); } diff --git a/exercise.main/Discount/DiscountManager.cs b/exercise.main/Discount/DiscountManager.cs index 97cffb84..97090b42 100644 --- a/exercise.main/Discount/DiscountManager.cs +++ b/exercise.main/Discount/DiscountManager.cs @@ -1,16 +1,18 @@ -namespace exercise.main.Discount +using System.Linq; + +namespace exercise.main.Discount { public class DiscountManager { - List discountTypes = new List(); + List discountTypes = new List(); //public void addDiscountType(params object[] args) where T : Discount private Inventory _inventory; public DiscountManager(Inventory inventory) { _inventory = inventory; } - public void addDiscountType(Discount discount) + public void addDiscountType(DiscountBase discount) { // TODO: Check for and remove identicals... discountTypes.Add(discount); @@ -76,7 +78,7 @@ private List pickBestDeals(List public Dictionary calculateDiscount(Basket basket) { List possibleDiscounts = new List(); - foreach (Discount discount in discountTypes) + foreach (DiscountBase discount in discountTypes) { if (discount.checkCondition(basket)) { @@ -136,6 +138,7 @@ public Dictionary calculateDiscount(Basket basket) discounted_price = product.finalPrice, total_price = product.finalPrice, saving = product.possibleSavings, + UsedDiscount = product.discount }; } @@ -177,6 +180,12 @@ public Dictionary calculateDiscount(Basket basket) return nameAmountPrice; } + + public string stringify() + { + + return string.Join("\n", discountTypes.ToList().Select(x => x.stringify()).ToList()); + } } diff --git a/exercise.main/Discount/Discount_XforY.cs b/exercise.main/Discount/Discount_XforY.cs index 213cfb5a..d42a0472 100644 --- a/exercise.main/Discount/Discount_XforY.cs +++ b/exercise.main/Discount/Discount_XforY.cs @@ -1,6 +1,6 @@ namespace exercise.main.Discount { - public class Discount_XforY : Discount + public class Discount_XforY : DiscountBase { Dictionary nrOfRequiredProducts = new Dictionary(); @@ -67,11 +67,18 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) //discountMultiple = maxDiscountMultiplier , discountMultiple = (int)maxDiscountMultiplier, possibleSavings = discountedSavings, - finalPrice = totalCost_withDiscount + finalPrice = totalCost_withDiscount, + discount = this, }; return dp; } + + public override string stringify() + { + + return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; + } } diff --git a/exercise.main/Discount/DiscountedProductCount.cs b/exercise.main/Discount/DiscountedProductCount.cs index 09796e0d..fc31eda3 100644 --- a/exercise.main/Discount/DiscountedProductCount.cs +++ b/exercise.main/Discount/DiscountedProductCount.cs @@ -6,6 +6,8 @@ public class DiscountedProductCount public int discountMultiple = 0; public float possibleSavings = 0.0f; public float finalPrice = 0.0f; + public DiscountBase discount = null; + } diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs index bc48a4b7..a17ed928 100644 --- a/exercise.main/Inventory.cs +++ b/exercise.main/Inventory.cs @@ -1,15 +1,16 @@ using System.Diagnostics; using System.IO.Pipelines; +using System.Xml.Linq; namespace exercise.main { public class InventoryData { - public string name; - public string SKU; - public float price; - public int stock; + public string name = ""; + public string SKU = ""; + public float price = 0.0f; + public int stock = 0; public InventoryData(string name, string sKU, float price, int stock) { @@ -18,13 +19,31 @@ public InventoryData(string name, string sKU, float price, int stock) this.price = price; this.stock = stock; } + public string stringify() + { + //return string.Format("{0,0:10}{1,0:10}{2,0:30}{3,0:40}", SKU, name, price, stock); + return String.Format("{0,0}{1,10}{2,10}{3,10}", SKU, name, price, stock); + } } + + public class Inventory { Dictionary inventory = new Dictionary(); public Inventory() { } + public string stringify() + { + + //return "Inventory:\n"+ string.Join("\n", inventory.ToList().Select(x=>x.Value.toString())); + string st = String.Format("{0,0}{1,10}{2,10}{3,10}\n", "SKU", "Product", "Price", "Stock"); ; + foreach (var item in inventory.ToList()) + { + st += item.Value.stringify() + "\n"; + } + return st; + } public void Add(string SKU, string name, float price, int stock ) { if (inventory.ContainsKey(SKU)) diff --git a/exercise.main/OrderData.cs b/exercise.main/OrderData.cs index 0fce844d..48155288 100644 --- a/exercise.main/OrderData.cs +++ b/exercise.main/OrderData.cs @@ -1,4 +1,5 @@ -namespace exercise.main +using exercise.main.Discount; +namespace exercise.main { public struct OrderData { @@ -7,7 +8,10 @@ public struct OrderData public float individual_price; public float discounted_price; public float total_price; - public float saving; + public float saving; + private DiscountBase _usedDiscount; + + public DiscountBase UsedDiscount { get => _usedDiscount; set => _usedDiscount = value; } } diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 84799933..da5527ee 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -5,10 +5,138 @@ using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; +using exercise.main.Discount; namespace exercise.main { + public interface IStoreFront + { + + Inventory inventory{ get; set; } + DiscountManager discountManager{ get; set; } + Basket basket { get; set; } + void presentProducts(); + void presentDeals(); + void addToBasket(string skuu, int amount = 1); + void removeFromBasket(string sku, int amount = 1); + void showBasket(); + void run(); + void exit(); + } + + public class TerminalStoreFront : IStoreFront + { + public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager discountManager) + { + this.inventory = inventory; + this.basket = basket; + this.discountManager = discountManager; + + } + public delegate void basketOpFunc(string sku, int amount); + + public Inventory inventory {get; set;} + public Basket basket { get; set; } + public DiscountManager discountManager { get; set; } + + public void addToBasket(string sku, int amount = 1 ) + { + basket.addProduct(sku, amount); + } + + public void presentDeals() + { + Console.WriteLine("Deals: ---------------------------------"); + Console.Write(discountManager.stringify()); + Console.WriteLine("----------------------------------------\n"); + } + + public void presentProducts() + { + Console.WriteLine("Menu: ----------------------------------"); + Console.Write(inventory.stringify()); + Console.WriteLine("----------------------------------------"); + } + + public void removeFromBasket(string sku, int amount = 1) + { + basket.removeProduct(sku, amount); + } + + public void showBasket() + { + Console.WriteLine("\nBasket: --------------------------------"); + Console.WriteLine(basket.stringify(discountManager)); + Console.WriteLine("----------------------------------------"); + } + public void exit() + { + throw new NotImplementedException(); + } + public void run() + { + + basketOpFunc addTo = new basketOpFunc(addToBasket); + basketOpFunc remFrom = new basketOpFunc(removeFromBasket); + + string exitCommand = "/q"; + string userInput = ""; + while (exitCommand != userInput) + { + presentDeals(); + presentProducts(); + if (basket.isNotEmpty()) + { + showBasket(); + } + userInput = Console.ReadLine(); + Console.Clear(); + + var args = userInput.Split(" "); + if (args.Length > 0) + { + + CheckPrefromBasketOp(args, "/add", addTo); + CheckPrefromBasketOp(args, "/rem", remFrom); + + } + } + + } + void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) + { + if (args[0].ToLower() == commandWord) + { + if (args.Length >= 3) + { + int parsed = 0; + if (int.TryParse(args[2], out parsed)) + { + f(args[1].ToUpper(), parsed); + } + } + else + { + f(args[1].ToUpper(), 1); + } + } + } + } + public class StoreFrontExecutor + { + IStoreFront storeFront; + public StoreFrontExecutor(IStoreFront storeFront) + { + this.storeFront = storeFront; + } + + public void run() + { + storeFront.run(); + } + + } public abstract class BaseProduct { diff --git a/exercise.main/Program.cs b/exercise.main/Program.cs index 3751555c..39cb956a 100644 --- a/exercise.main/Program.cs +++ b/exercise.main/Program.cs @@ -1,2 +1,43 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); + + + +using exercise.main; +using exercise.main.Discount; + +Inventory inventory = new Inventory(); +Basket basket = new Basket(inventory); +DiscountManager discountManager = new DiscountManager(inventory); + +inventory.Add("BGLO", "Onion", 0.49f, 100); +inventory.Add("BGLP", "Plain", 0.39f, 100); +inventory.Add("BGLE", "Everything", 0.39f, 100); +inventory.Add("BGLS", "Sesame", 0.49f, 100); +inventory.Add("COFB", "Black", 0.49f, 100); +inventory.Add("FILH", "Ham", 0.12f, 100); +inventory.Add("FILC", "Cheese", 0.12f, 100); +inventory.Add("FILE", "Egg", 0.12f, 100); + +int nrOfBagelsForDiscount = 6; +float discountedPrice_6_for_2_49 = 2.49f; +var discountReq = new Dictionary { { "BGLO", nrOfBagelsForDiscount } }; +var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); +discountManager.addDiscountType(d); + +var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; +var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); +discountManager.addDiscountType(d2); + +var discountReq_3 = new Dictionary { { "BGLP", 12 }}; +var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); +discountManager.addDiscountType(d3); + +var discountReq_4 = new Dictionary { { "BGLE", 12 }}; +var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); +discountManager.addDiscountType(d4); + + + + +var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager)); + +storeFrontExecutior.run(); \ No newline at end of file diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 7b4a8fcc..cc15bace 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -130,6 +130,28 @@ public void createInventory() Inventory inventory = new Inventory(); Assert.Pass(); + } + [Test] + public void createTerminalStoreFront() + { + + Inventory inventory = new Inventory(); + Basket basket = new Basket(inventory); + DiscountManager discountManager = new DiscountManager(inventory); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + + var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager)); + + storeFrontExecutior.run(); + + Assert.Pass(); + } [Test] public void inventory_add() From b6d7a52fd35f73187502ca03d70410ce5545913c Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Tue, 14 Jan 2025 15:23:56 +0100 Subject: [PATCH 08/10] Fixes, features, ... --- domain-model.md | 26 +- exercise.main/Basket.cs | 94 ++++- exercise.main/Discount/DiscountManager.cs | 44 ++- exercise.main/Discount/Discount_XforY.cs | 2 +- exercise.main/Inventory.cs | 39 +- exercise.main/OrderData.cs | 1 + exercise.main/Product.cs | 54 ++- exercise.main/Program.cs | 7 +- exercise.tests/UnitTest1.cs | 449 +++++++++++++++++++++- 9 files changed, 638 insertions(+), 78 deletions(-) diff --git a/domain-model.md b/domain-model.md index d669a69a..e8cd88b8 100644 --- a/domain-model.md +++ b/domain-model.md @@ -44,9 +44,9 @@ I want customers to only be able to order things that we stock in our inventory. So we can sell stuff before it goes bad and entice customers to buy, I want to be able to set `Special Offer` for any product that matches `buy x nr of z for y`, `buy z, get [a,..] for y`. -12. As the manager, + 13. As a customer, So I know that I'm paying the the discounted price for a discounted product and no more, @@ -63,7 +63,7 @@ I'd like the reciepe to contain a Total for all products on the reciept. 16. As a Manager, So I can keep track of the stock for each product, -I want each purchase to be registered and the stock to be updated. +I want each purchase to be registered and the stock to be updated. 17. As a Manager, So that the customer will have a good impression of us, @@ -113,19 +113,20 @@ I want the customer to see how much they saved on discounts. | 4 | Basket | Capacity (prop) | Manager decides to allow one more item in the basket | The basket now contains a productlist that can fit one more item | | 5 | Basket | Remove | User removes an item that doesn't exist in their basket | User recieves a warning | | 6 | Basket | GetTotal | User has added several products to basket, they want to know how much its going to cost | returns the sum of all items | -| 10 | Basket | Add | Usere wants to add a Product or ProductComponent to their Basket, but stock is 0 | nothing is added | +| 10 | Basket | Add | User wants to add a Product to their Basket, but stock is 0 | nothing is added | +| 8 | Basket | Add | User want to select several fillings for their Bagle | Fillings are addded to the order | | | | | | | -| 8 | ProductComponent | ProductComponent Product | User want to order a custom product, containing one or several ingredients | A Product that contains a list of ProductComponent (which are inherited from Product) | + | | | | | | -| 8 | ProductDesigner | Factory class for Products | Used to construct products | | -| 8 | ProductDesigner | Create | User want to select several fillings (ProductComponents) for their Bagle | a Bagle is returned with the requested fillings | + | | | | | | -| 10 | Product | Stock (prop) | User wanted to add Product or ProductComponent to their order, but stock is 0 | | -| 11 | Product | Discount discount | Any product may have a Discount, applied to the final price | | -| 11 | Discount | Constructor | class used to represent discounts | | +| 11 | DiscountManager | Discount discount | Any product may have a Discount, applied to the final price | | | | | | | | +| 11 | Discount | Constructor | class used to represent discounts | Allows to create different types of Discounts, example: based on expiry date | | 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | -| 12 | Discount | Condition (lambda) | defines what circumstances the discount is valid for (Limited amount, ) | | +| | | | | | + | | | | | | | 13 | CashRegister | Constructor | all purchases is handled by the CashRegister | | | 13 | CashRegister | RegisterBasket | User is ready to pay, all products in their basket will be summed up, any discount will be accounted for | Sets the currentBasket, ready for user to Pay | @@ -135,7 +136,8 @@ I want the customer to see how much they saved on discounts. | 13 | CashRegister | FinalizePurchase | User doesn't have enough money | currentBasket is set to Null | | | | | | | | 16 | Inventory | Constructor | Keep tracks on the inventory | | -| 19 | Inventory | History (prop) | When a purchase is made, it is kept in the history using DateTime as an ID | | + +| 10 | Inventory | Stock (prop) | User wanted to add Product to their order, but stock is 0 | Nothing is added | | 16 | Inventory | SetBusy(Product) | Marks an Product as taken by another Users Basket | | | 16 | Inventory | Remove(Product) | Product is removed when User Finalizes a Purchase | Product is removed from inventory | | 16 | Inventory | add(Product, nr) | Manager bought 100 jars of pickle | 100 jars of pickle added to the inventory | diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index bc0b747f..c77de41c 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -10,66 +10,104 @@ namespace exercise.main { public class Basket { - private List products = new List(); + private List products = new List(); private Inventory _inventory; - public Basket(Inventory inventory) + + private int _capacity; + private int Capacity { get => _capacity; } + private int NrOfItems { get => products.Count; } + private DiscountManager _DM; + private DiscountManager DM { get => _DM; } + private List _warnings = new List(); + public List Warnings { + get { + var temp = _warnings.ToList(); + _warnings.RemoveRange(0, _warnings.Count); + return temp; + } + } + public Basket(Inventory inventory, DiscountManager dm ,int capacity = 15) { this._inventory = inventory; + this._capacity = capacity; + this._DM = dm; + } + + public void setCapacity(int newCapacity) + { + this._capacity = newCapacity; + if (this.Capacity < products.Count) + { + var tempProducts = products.ToList().GetRange(0,this.Capacity); + products = tempProducts; + } } - //public void addProduct(BaseProduct product) public void addProduct(string productSku, int amount = 1) { if (amount < 1) { - Debug.Assert(amount < 1, "Amount to add must be positive"); + _warnings.Add("Amount to add must be positive"); + return; } int productStock = this._inventory.getStock(productSku); if (productStock < amount) { - Debug.Assert(productStock <= 0, $"There's not enough {productSku} in stock..."); + _warnings.Add($"Can't add {amount} {productSku}, as the there's {productStock} in stock "); + return; } - for (int i = 0; i < amount; i++) + + if (NrOfItems + amount > Capacity ) { - Product p = new Product( - productSku, - _inventory.getName(productSku), - _inventory.getPrice(productSku) - ); + _warnings.Add($"Can't add {amount} {productSku}, will be more than Baskets Capacity of {Capacity} items"); + return; + } - this.products.Add(p); + + for (int i = 0; i < amount; i++) + { + this.products.Add(_inventory.createProductType(productSku)); } this._inventory.decreaseStock(productSku, amount); } - public void removeProduct(string productSku, int amount = 1) + public bool removeProduct(string productSku, int amount = 1) { if (amount < 1) { - Debug.Assert(amount < 1, "Amount to remove must be positive"); + _warnings.Add($"Amount to remove must be positive"); + return false; + } int amountProductInBasket = this.countProductTypes(productSku); int nrToRemove = Math.Min(amountProductInBasket, amount); var itemsToRemoveList = this.products.Where(x => x.SKU == productSku).ToList(); + if (itemsToRemoveList.Count == 0) + { + _warnings.Add($"Can't remove {productSku}, as its not in the basket"); + return false; + } for (int i = 0; i < nrToRemove; i++) { this.products.Remove(itemsToRemoveList[i]); } + this._inventory.IncreaseStock(productSku, nrToRemove); + return true; } public int countProductTypes(string SKU) { return products.Where(x=>x.SKU == SKU).Count(); } - public List getProducts() + public List getProducts() { // Return a deep copy of the list... - var cpyList = new List(); - foreach (BaseProduct product in this.products) + var cpyList = new List(); + foreach (Product product in this.products) { cpyList.Add(product); } @@ -94,24 +132,38 @@ public bool isNotEmpty() return this.products.Count > 0; } + public float getTotal() + { + var orderDataDict = DM.calculateDiscount(this); + + return orderDataDict.Values.Sum(x => x.total_price); + + } + + public float getUndiscountedTotal() + { + return this.products.Sum(x => x.ProductPrice); + + } + public string stringify(DiscountManager dm) { var cacledBasket = dm.calculateDiscount(this); - string ret = string.Format("{0,0}{1,25}{2,25}\n", "Name", "Amount", "Cost"); + string ret = string.Format("{0,0}{1,25}{2,25}{3,25}\n", "Type", "Name", "Amount", "Cost"); foreach (var x in cacledBasket.ToList()) { if (x.Value.UsedDiscount == null) { - ret += "\n"+ string.Format("{0,0}{1,25}{2,25}", _inventory.getName(x.Value.name), x.Value.amount, x.Value.total_price); + ret += "\n"+ string.Format("{0,0}{1,25}{2,25}{3,25}", _inventory.getProductType(x.Value.name) ,_inventory.getName(x.Value.name), x.Value.amount, x.Value.total_price); } else { - ret += "\n"+ string.Format("{0,0}{1,25}{2,25}", x.Value.UsedDiscount.stringify(), x.Value.amount, x.Value.total_price); + ret += "\n"+ string.Format("{0,0}{1,25}{2,25}{3,25}", "Deal", x.Value.UsedDiscount.stringify(), x.Value.amount, x.Value.total_price); } } - ret += $"\n\n Total: {cacledBasket.Sum(x => x.Value.total_price)}"; + ret += $"\n\n Total: {getTotal()}"; return ret; } diff --git a/exercise.main/Discount/DiscountManager.cs b/exercise.main/Discount/DiscountManager.cs index 97090b42..f019d526 100644 --- a/exercise.main/Discount/DiscountManager.cs +++ b/exercise.main/Discount/DiscountManager.cs @@ -23,20 +23,34 @@ private List pickBestDeals(List // Looks for conflicting deals, removes them and favors best value deals List> conflictingIndexes = new List>(); + Dictionary> handleDuplications = new(); List possibleCombinations = new List(); // Count/collect possible conflicting deals for (int i = 0; i < possibleDiscounts.Count; i++) { bool noConflict = true; - for (int j = i + 1; j < possibleDiscounts.Count; j++) + //for (int j = i + 1; j < possibleDiscounts.Count; j++) + for (int j = 0; j < possibleDiscounts.Count; j++) { + if (j == i) + continue; foreach (var key in possibleDiscounts[j].SKU_amount.Keys) { if (possibleDiscounts[i].SKU_amount.ContainsKey(key)) { - conflictingIndexes.Add(new(i, j, key)); + if (!handleDuplications.ContainsKey(i)) + handleDuplications[i] = new(); + if (!handleDuplications.ContainsKey(j)) + handleDuplications[j] = new(); + + //if (!handleDuplications[i].Contains(j)) + if (!handleDuplications[j].Contains(i)) + { + conflictingIndexes.Add(new(i, j, key)); + handleDuplications[i].Add(j); + } noConflict = false; } } @@ -47,35 +61,31 @@ private List pickBestDeals(List } } + var bestDeals = new List(possibleCombinations); + float total = basket.getUndiscountedTotal(); foreach (var c in conflictingIndexes) { int index_i = c.Item1; int index_j = c.Item2; string SKU = c.Item3; - //float? defprice = basket.getDefaultPrice(SKU); - //if (defprice == null) - //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); - if (possibleDiscounts[index_i].possibleSavings > possibleDiscounts[index_j].possibleSavings) + if (total + possibleDiscounts[index_i].possibleSavings > total + possibleDiscounts[index_j].possibleSavings) { - possibleCombinations.Add(possibleDiscounts[index_i]); + bestDeals.Add(possibleDiscounts[index_i]); } else { - possibleCombinations.Add(possibleDiscounts[index_j]); + bestDeals.Add(possibleDiscounts[index_j]); } - - //defprice. - } - return possibleCombinations; + return bestDeals; } //public Order calculateDiscount(Basket basket) - public Dictionary calculateDiscount(Basket basket) + private List getPossibleDiscounts(Basket basket) { List possibleDiscounts = new List(); foreach (DiscountBase discount in discountTypes) @@ -85,7 +95,11 @@ public Dictionary calculateDiscount(Basket basket) possibleDiscounts.Add(discount.getDiscountedPrice(basket)); } } - + return possibleDiscounts; + } + public Dictionary calculateDiscount(Basket basket) + { + var possibleDiscounts = getPossibleDiscounts(basket); var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); List orderData = new List(); @@ -102,7 +116,7 @@ public Dictionary calculateDiscount(Basket basket) int amount = di.Value; int counted = 0; - List temp = new List(); + List temp = new List(); foreach (var product in productList) { diff --git a/exercise.main/Discount/Discount_XforY.cs b/exercise.main/Discount/Discount_XforY.cs index d42a0472..c4ec4637 100644 --- a/exercise.main/Discount/Discount_XforY.cs +++ b/exercise.main/Discount/Discount_XforY.cs @@ -77,7 +77,7 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) public override string stringify() { - return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; + return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; } } diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs index a17ed928..46c435ec 100644 --- a/exercise.main/Inventory.cs +++ b/exercise.main/Inventory.cs @@ -19,10 +19,10 @@ public InventoryData(string name, string sKU, float price, int stock) this.price = price; this.stock = stock; } - public string stringify() + public string stringify(Inventory inventory) { //return string.Format("{0,0:10}{1,0:10}{2,0:30}{3,0:40}", SKU, name, price, stock); - return String.Format("{0,0}{1,10}{2,10}{3,10}", SKU, name, price, stock); + return String.Format("{0,0}{1,15}{2,15}{3,10}{4,10}", SKU, inventory.getProductType(SKU), name, price, stock); } } @@ -33,14 +33,43 @@ public class Inventory public Inventory() { } + + public Product createProductType(string SKU) + { + return new Product(SKU, this.getName(SKU), this.getPrice(SKU)); + + } + public string getProductType(string SKU) + { + // Would be nice if manager could register this themselves, similar too discounts + if (SKU.Length == 0) + return "unknown"; + + string type = SKU[0..1].ToUpper(); + switch (type) + { + case "B": + return "Bagel"; + break; + + case "C": + return "Coffee"; + break; + + case "F": + return "Filling"; + break; + } + return "unknown"; + } public string stringify() { //return "Inventory:\n"+ string.Join("\n", inventory.ToList().Select(x=>x.Value.toString())); - string st = String.Format("{0,0}{1,10}{2,10}{3,10}\n", "SKU", "Product", "Price", "Stock"); ; + string st = String.Format("{0,0}{1,15}{2,15}{3,10}{4,10}\n", "SKU", "Product Type", "Variant", "Price", "Stock"); ; foreach (var item in inventory.ToList()) { - st += item.Value.stringify() + "\n"; + st += item.Value.stringify(this) + "\n"; } return st; } @@ -55,7 +84,7 @@ public void Add(string SKU, string name, float price, int stock ) inventory.Add(SKU,new InventoryData(name, SKU, price, stock)); } - public void Add(BaseProduct product, int stock) + public void Add(Product product, int stock) { if (inventory.ContainsKey(product.SKU)) { diff --git a/exercise.main/OrderData.cs b/exercise.main/OrderData.cs index 48155288..1ed8b468 100644 --- a/exercise.main/OrderData.cs +++ b/exercise.main/OrderData.cs @@ -4,6 +4,7 @@ namespace exercise.main public struct OrderData { public string name; + public string productType; public int amount; public float individual_price; public float discounted_price; diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index da5527ee..3f865c5a 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -20,6 +20,7 @@ public interface IStoreFront void presentDeals(); void addToBasket(string skuu, int amount = 1); void removeFromBasket(string sku, int amount = 1); + void changeCap(int amount); void showBasket(); void run(); void exit(); @@ -35,6 +36,7 @@ public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager di } public delegate void basketOpFunc(string sku, int amount); + public delegate void baskeSetFunc(int amount); public Inventory inventory {get; set;} public Basket basket { get; set; } @@ -79,6 +81,7 @@ public void run() basketOpFunc addTo = new basketOpFunc(addToBasket); basketOpFunc remFrom = new basketOpFunc(removeFromBasket); + baskeSetFunc setCapF = new baskeSetFunc(changeCap); string exitCommand = "/q"; string userInput = ""; @@ -90,6 +93,11 @@ public void run() { showBasket(); } + var currentWarnings = basket.Warnings; + Console.WriteLine(string.Join("\n", currentWarnings)); + + Console.WriteLine("\nInstructions: \n\t/q : quit\n\t/add SKU NR: add items\n\t/rem SKU NR: remove items\n\t/cap NR : change basket capacity"); + userInput = Console.ReadLine(); Console.Clear(); @@ -99,6 +107,7 @@ public void run() CheckPrefromBasketOp(args, "/add", addTo); CheckPrefromBasketOp(args, "/rem", remFrom); + changeBasketSetting(args, "/cap", setCapF); } } @@ -122,6 +131,26 @@ void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) } } } + void changeBasketSetting(string[] args, string commandWord,baskeSetFunc f) + { + if (args[0].ToLower() == commandWord) + { + if (args.Length >= 2) + { + int parsed = 0; + if (int.TryParse(args[1], out parsed)) + { + f( parsed); + } + } + + } + } + + public void changeCap(int amount ) + { + basket.setCapacity(amount); + } } public class StoreFrontExecutor { @@ -138,20 +167,20 @@ public void run() } - public abstract class BaseProduct + public class Product { protected string name; private string sku; protected float productPrice; - protected List subProducts; + protected List subProducts; //protected ProductType productType; //public BaseProduct(string SKU,string name, float defaultPrice, ProductType productType, List? subProducts = null) - public BaseProduct(string SKU,string name, float defaultPrice, List? subProducts = null) + public Product(string SKU,string name, float defaultPrice, List? subProducts = null) { this.sku = SKU; this.name = name; this.productPrice = defaultPrice; - this.subProducts = subProducts ?? new List(); + this.subProducts = subProducts ?? new List(); //this.productType = productType; } public float CombinedPrice @@ -170,13 +199,16 @@ public string ProductName public string SKU { get => sku;} } - public class Product: BaseProduct - { - public Product(string SKU, string name, float defaultPrice,List? subProducts = null) - : base(SKU, name, defaultPrice, subProducts) - { - } - } + //public class Product: BaseProduct + //{ + // // This isn't necessary, + // public Product(string SKU, string name, float defaultPrice,List? subProducts = null) + // : base(SKU, name, defaultPrice, subProducts) + // { + // } + //} + + diff --git a/exercise.main/Program.cs b/exercise.main/Program.cs index 39cb956a..bc8223f8 100644 --- a/exercise.main/Program.cs +++ b/exercise.main/Program.cs @@ -5,14 +5,15 @@ using exercise.main.Discount; Inventory inventory = new Inventory(); -Basket basket = new Basket(inventory); + DiscountManager discountManager = new DiscountManager(inventory); +Basket basket = new Basket(inventory, discountManager); inventory.Add("BGLO", "Onion", 0.49f, 100); inventory.Add("BGLP", "Plain", 0.39f, 100); inventory.Add("BGLE", "Everything", 0.39f, 100); inventory.Add("BGLS", "Sesame", 0.49f, 100); -inventory.Add("COFB", "Black", 0.49f, 100); +inventory.Add("COFB", "Black", 0.99f, 100); inventory.Add("FILH", "Ham", 0.12f, 100); inventory.Add("FILC", "Cheese", 0.12f, 100); inventory.Add("FILE", "Egg", 0.12f, 100); @@ -31,7 +32,7 @@ var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); discountManager.addDiscountType(d3); -var discountReq_4 = new Dictionary { { "BGLE", 12 }}; +var discountReq_4 = new Dictionary { { "BGLE", 6 }}; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); discountManager.addDiscountType(d4); diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index cc15bace..3d65916a 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -3,6 +3,8 @@ using exercise.main; using exercise.main.Discount; using NUnit.Framework.Constraints; +using System.Reflection; + namespace exercise.tests; public class Tests @@ -12,7 +14,8 @@ public class Tests public void CreateBasket() { Inventory inventory = new Inventory(); - var p = new Basket(inventory); + DiscountManager dm = new DiscountManager(inventory); + var p = new Basket(inventory, dm); Assert.Pass(); } @@ -20,7 +23,8 @@ public void CreateBasket() public void addProductToBasket() { Inventory inventory = new Inventory(); - var b = new Basket(inventory); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm); inventory.Add("BGLO", "Onion", 0.49f, 100); inventory.Add("FILH", "Ham", 0.12f, 100); @@ -39,12 +43,83 @@ public void addProductToBasket() Assert.That(productsInBasket.ContainsKey("FILE")); Assert.Pass(); } + [Test] + public void basket_capacityLimitsAdding() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 3); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO"); + b.addProduct("FILH"); + b.addProduct("FILC"); + b.addProduct("FILE"); // Should not be added + + var productsInBasket = b.getAmountPerSku(); + Assert.That(!productsInBasket.ContainsKey("FILE")); // Should not contain + } + [Test] + public void basket_changeCapacity_decrease() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 3); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO"); + b.addProduct("FILH"); + b.addProduct("FILC"); + b.addProduct("FILE"); // Should not be added + + var productsInBasket = b.getAmountPerSku(); + Assert.That(!productsInBasket.ContainsKey("FILE")); // Should not contain + + b.setCapacity(2); // Should remove FILC + productsInBasket = b.getAmountPerSku(); + Assert.That(!productsInBasket.ContainsKey("FILC")); // Should not contain + + }[Test] + public void basket_changeCapacity_increase() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 3); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO"); + b.addProduct("FILH"); + b.addProduct("FILC"); + b.addProduct("FILE"); // Should not be added + + var productsInBasket = b.getAmountPerSku(); + Assert.That(!productsInBasket.ContainsKey("FILE")); // Should not contain + + b.setCapacity(4); // Should remove FILC + b.addProduct("FILE"); // Should not be added + productsInBasket = b.getAmountPerSku(); + Assert.That(productsInBasket.ContainsKey("FILE")); // Should contain + + } [Test] - public void removeProductToBasket() + public void removeProductFromBasket() { Inventory inventory = new Inventory(); - var b = new Basket(inventory); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 300); inventory.Add("BGLO", "Onion", 0.49f, 100); inventory.Add("BGLS", "Sesame", 0.49f, 100); @@ -58,9 +133,9 @@ public void removeProductToBasket() b.addProduct("FILC",20); b.addProduct("FILE",10); - b.removeProduct("FILC", 20); - b.removeProduct("BGLS", 5); - b.removeProduct("FILH", 100); + Assert.That(b.removeProduct("FILC", 20) == true); + Assert.That(b.removeProduct("BGLS", 5) == true); + Assert.That(b.removeProduct("FILH", 100) == true); var productsInBasket = b.getAmountPerSku(); Assert.That(productsInBasket["BGLO"] == 30); @@ -69,15 +144,97 @@ public void removeProductToBasket() Assert.That(productsInBasket.ContainsKey("FILC") == false); Assert.That(productsInBasket["FILE"] == 10); + } + [Test] + public void removeProductThatDoesNotExist_fromBasket() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO",30); + b.addProduct("FILH",25); + b.addProduct("BGLS", 25); + b.addProduct("FILC",20); + + Assert.That(b.removeProduct("FILE", 1) == false); } + + [Test] + public void getTotalFromBasket() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO",1); + b.addProduct("FILH",1); + b.addProduct("FILC",1); + + + Assert.That(b.getTotal() == 0.49f + 0.12f + 0.12f); + + } + [Test] + public void addToBasket_itemWithZeroInStock() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 0); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("BGLO",1); + + var prods = b.getProducts(); + + Assert.That(prods.Count == 0); + } + [Test] + public void addToBasket_ItemDoesNotExistInInventory() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 0); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + + b.addProduct("COFB",1); + + var prods = b.getProducts(); + + Assert.That(prods.Count == 0); + } [Test] public void createDiscountType() { Inventory inventory = new Inventory(); - var p = new Basket(inventory); + DiscountManager dm = new DiscountManager(inventory); + var p = new Basket(inventory, dm, 50); inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); inventory.Add("FILH", "Ham", 0.12f, 50); inventory.Add("FILC", "Cheese", 0.12f, 50); inventory.Add("FILE", "Egg", 0.12f, 50); @@ -89,13 +246,140 @@ public void createDiscountType() var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); // Add deal to DiscountManager + //DiscountManager dm = new DiscountManager(inventory); + dm.addDiscountType(d); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + + + p.addProduct("COFB"); + totalPrice += inventory.getPrice("COFB"); + p.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + p.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + p.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + p.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + + // Add discounted Products, calculate sum based on the discount + int nrOf_OnionBagels = 24; + p.addProduct("BGLO", nrOf_OnionBagels); + + totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // calculate Rest + totalPrice += ((float)nrOf_OnionBagels % nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + + //Order o = dm.calculateDiscount(p); + var orderDataDict = dm.calculateDiscount(p); + + var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); + + Assert.That(totalPrice, Is.EqualTo(returnedTotal)); + + } + [Test] + public void multipleDiscountsAreAppliedCorrectly_onlyBuyOneDeal() + { + Inventory inventory = new Inventory(); DiscountManager dm = new DiscountManager(inventory); + var p = new Basket(inventory, dm, 50); + + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO" , nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + //DiscountManager dm = new DiscountManager(inventory); dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); // Add non-discounted Products, calculate the total float totalPrice = 0.0f; + // Add discounted Products, calculate sum based on the discount + int nrOf_OnionBagels = 24; + p.addProduct("BGLO", nrOf_OnionBagels); + + totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // calculate Rest + totalPrice += ((float)nrOf_OnionBagels % nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + + //Order o = dm.calculateDiscount(p); + var orderDataDict = dm.calculateDiscount(p); + + var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); + + Assert.That(totalPrice, Is.EqualTo(returnedTotal)); + + } + [Test] + public void multipleDiscountsAreAppliedCorrectly_buyDealAndOther() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var p = new Basket(inventory, dm, 50); + + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO" , nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + //DiscountManager dm = new DiscountManager(inventory); + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + + + //p.addProduct("COFB"); + //totalPrice += inventory.getPrice("COFB"); p.addProduct("FILH"); totalPrice += inventory.getPrice("FILH"); p.addProduct("FILC"); @@ -122,6 +406,150 @@ public void createDiscountType() Assert.That(totalPrice, Is.EqualTo(returnedTotal)); + } + + [Test] + public void discountManager_pickTheOneDealAvailable() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 100); + + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO" , nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + //DiscountManager dm = new DiscountManager(inventory); + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + + + // Add discounted Products, calculate sum based on the discount + int nrOf_OnionBagels = 6; + b.addProduct("BGLO", nrOf_OnionBagels); + + totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // calculate Rest + totalPrice += ((float)nrOf_OnionBagels % nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // Expect to pick 6 bagels for 2.49, over coffe+bagel for 1.25 + + Type dm_type = dm.GetType(); + + MethodInfo possibleDiscounts_method = dm_type.GetMethod("getPossibleDiscounts", BindingFlags.NonPublic | BindingFlags.Instance); + MethodInfo pickBestDeals_method = dm_type.GetMethod("pickBestDeals", BindingFlags.NonPublic | BindingFlags.Instance); + + var possibleDiscounts = possibleDiscounts_method.Invoke(dm, [b]); + + List bestDeals = pickBestDeals_method.Invoke(dm, [possibleDiscounts, b]) as List; + + Assert.That(bestDeals.Count == 1); + + + } + [TestCase(0)] + [TestCase(1)] + [TestCase(24)] + public void discountManagerPicksBestDeals(int nrOfCoffeeToAdd) + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 100); + + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO" , nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + //DiscountManager dm = new DiscountManager(inventory); + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + + if (nrOfCoffeeToAdd != 0) + { + b.addProduct("COFB", nrOfCoffeeToAdd); + totalPrice += inventory.getPrice("COFB"); + } + b.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + b.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + + // Add discounted Products, calculate sum based on the discount + int nrOf_OnionBagels = 24; + b.addProduct("BGLO", nrOf_OnionBagels); + + totalPrice += MathF.Floor((float)nrOf_OnionBagels / nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // calculate Rest + totalPrice += ((float)nrOf_OnionBagels % nrOfBagelsForDiscount) * discountedPrice_6_for_2_49; + + // Expect to pick 6 bagels for 2.49, over coffe+bagel for 1.25 + + Type dm_type = dm.GetType(); + + MethodInfo possibleDiscounts_method = dm_type.GetMethod("getPossibleDiscounts", BindingFlags.NonPublic | BindingFlags.Instance); + MethodInfo pickBestDeals_method = dm_type.GetMethod("pickBestDeals", BindingFlags.NonPublic | BindingFlags.Instance); + + var possibleDiscounts = possibleDiscounts_method.Invoke(dm, [b]); + + List bestDeals = pickBestDeals_method.Invoke(dm, [possibleDiscounts, b]) as List; + + Assert.That(bestDeals.Count == 1); + Assert.That(bestDeals[0].discount == d); + + + } [Test] public void createInventory() @@ -136,7 +564,8 @@ public void createTerminalStoreFront() { Inventory inventory = new Inventory(); - Basket basket = new Basket(inventory); + DiscountManager dm = new DiscountManager(inventory); + Basket basket = new Basket(inventory, dm); DiscountManager discountManager = new DiscountManager(inventory); inventory.Add("BGLO", "Onion", 0.49f, 100); @@ -148,7 +577,7 @@ public void createTerminalStoreFront() var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager)); - storeFrontExecutior.run(); + //storeFrontExecutior.run(); // Will get stuck,... not part of the test Assert.Pass(); From 403ed63670c200a73177b5699b081aabb6d5a68b Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Tue, 14 Jan 2025 16:41:03 +0100 Subject: [PATCH 09/10] Refactoring, comments --- domain-model.md | 14 +- exercise.main/Basket.cs | 7 +- exercise.main/CashRegister.cs | 8 + exercise.main/Discount/DiscountBase.cs | 3 - exercise.main/Discount/DiscountManager.cs | 125 ++++++++-------- exercise.main/Discount/Discount_XforY.cs | 10 +- exercise.main/IStoreFront.cs | 21 +++ exercise.main/OrderData.cs | 2 +- exercise.main/Product.cs | 175 ---------------------- exercise.main/StoreFrontExecutor.cs | 18 +++ exercise.main/TerminalStoreFront.cs | 132 ++++++++++++++++ exercise.tests/UnitTest1.cs | 78 ++++++---- 12 files changed, 305 insertions(+), 288 deletions(-) create mode 100644 exercise.main/CashRegister.cs create mode 100644 exercise.main/IStoreFront.cs create mode 100644 exercise.main/StoreFrontExecutor.cs create mode 100644 exercise.main/TerminalStoreFront.cs diff --git a/domain-model.md b/domain-model.md index e8cd88b8..d5a9f88c 100644 --- a/domain-model.md +++ b/domain-model.md @@ -109,9 +109,10 @@ I want the customer to see how much they saved on discounts. | | | | | | | 1 | Basket | Add | User wants to add a bagel to their basket, to purchase | Bagel product added to the basket | | 2 | Basket | Remove | user wants to remove a bagel from their basket, not to be included in the puschase | The removed bagel is no longer part of the Products in the basket | +| 5 | Basket | Remove | User removes an item that doesn't exist in their basket | User recieves a warning | +| 16 | Basket | Remove | User removes several items from their inventory | Products will return to the Inventory stock | | 3 | Basket | Capacity (prop) | The user tries to add another bagel to their basket, but the basket is full | No new bagel will be added to the basket, basket product list remains unchanged | | 4 | Basket | Capacity (prop) | Manager decides to allow one more item in the basket | The basket now contains a productlist that can fit one more item | -| 5 | Basket | Remove | User removes an item that doesn't exist in their basket | User recieves a warning | | 6 | Basket | GetTotal | User has added several products to basket, they want to know how much its going to cost | returns the sum of all items | | 10 | Basket | Add | User wants to add a Product to their Basket, but stock is 0 | nothing is added | | 8 | Basket | Add | User want to select several fillings for their Bagle | Fillings are addded to the order | @@ -121,10 +122,12 @@ I want the customer to see how much they saved on discounts. | | | | | | -| 11 | DiscountManager | Discount discount | Any product may have a Discount, applied to the final price | | +| 11 | DiscountManager | discountTypes (list) | Different Discount types can defined and added to the DiscountManager | Each discount type is considered when calculating the total to pay | +| 13 | DiscountManager | calculateDiscount | applies and calculate the best possible discount (a product can only be part of one discount) | The most valuable discounts will always be applied at checkout | | | | | | | | 11 | Discount | Constructor | class used to represent discounts | Allows to create different types of Discounts, example: based on expiry date | | 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | +| 11, 24 | Discount_XforY | Discount impl | Manager wants to create discounts for `buy x nr of z for y`, `buy z, get [a,..] for y` for multiple products | Created discount types can now be registered to DiscountManager to be used by customers | | | | | | | | | | | | | @@ -137,9 +140,10 @@ I want the customer to see how much they saved on discounts. | | | | | | | 16 | Inventory | Constructor | Keep tracks on the inventory | | -| 10 | Inventory | Stock (prop) | User wanted to add Product to their order, but stock is 0 | Nothing is added | -| 16 | Inventory | SetBusy(Product) | Marks an Product as taken by another Users Basket | | -| 16 | Inventory | Remove(Product) | Product is removed when User Finalizes a Purchase | Product is removed from inventory | +| 10 | Inventory | Stock (Dictionary) | User wanted to add Product to their order, but stock is 0 | Nothing is added | + +| 16 | Inventory | Remove(Product) | Product is removed when placed in users basket | Product is removed from inventory | +| 16 | Inventory | Remove(Product) | Product a removed when User Finalizes a Purchase | Product is removed from inventory | | 16 | Inventory | add(Product, nr) | Manager bought 100 jars of pickle | 100 jars of pickle added to the inventory | diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index c77de41c..2f353b2f 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -149,17 +149,18 @@ public float getUndiscountedTotal() public string stringify(DiscountManager dm) { var cacledBasket = dm.calculateDiscount(this); - string ret = string.Format("{0,0}{1,25}{2,25}{3,25}\n", "Type", "Name", "Amount", "Cost"); + string ret = string.Format("{0,7} {1,-25}{2,-10}{3,-10}\n", "Type", "Name", "Amount", "Cost"); foreach (var x in cacledBasket.ToList()) { if (x.Value.UsedDiscount == null) { - ret += "\n"+ string.Format("{0,0}{1,25}{2,25}{3,25}", _inventory.getProductType(x.Value.name) ,_inventory.getName(x.Value.name), x.Value.amount, x.Value.total_price); + ret += "\n"+ string.Format("{0,7} {1,-25}{2,-10}{3,-10}", _inventory.getProductType(x.Value.name) ,_inventory.getName(x.Value.name), x.Value.amount, x.Value.total_price); } else { - ret += "\n"+ string.Format("{0,0}{1,25}{2,25}{3,25}", "Deal", x.Value.UsedDiscount.stringify(), x.Value.amount, x.Value.total_price); + ret += "\n"+ string.Format("{0,7} {1,-25}", "Deal", x.Value.UsedDiscount.stringify()); + ret += "\n"+ string.Format("{0,7} {1,-25}{2, -10}{3, -10}","", "", x.Value.amount, x.Value.total_price); } } diff --git a/exercise.main/CashRegister.cs b/exercise.main/CashRegister.cs new file mode 100644 index 00000000..61860c70 --- /dev/null +++ b/exercise.main/CashRegister.cs @@ -0,0 +1,8 @@ +using exercise.main; + +namespace exercise.tests +{ + public class CashRegister + { + } +} \ No newline at end of file diff --git a/exercise.main/Discount/DiscountBase.cs b/exercise.main/Discount/DiscountBase.cs index 2a3cd3ba..8fb75551 100644 --- a/exercise.main/Discount/DiscountBase.cs +++ b/exercise.main/Discount/DiscountBase.cs @@ -7,10 +7,7 @@ public DiscountBase(float discountedPrice, Inventory inventory) discountPrice = discountedPrice; _inventory = inventory; } - //private Func discountConditionFunc; - //private Action discountConditionFunc; - //public void defineCondition(Func func ) private float discountPrice; protected Inventory _inventory; diff --git a/exercise.main/Discount/DiscountManager.cs b/exercise.main/Discount/DiscountManager.cs index f019d526..201fe2e7 100644 --- a/exercise.main/Discount/DiscountManager.cs +++ b/exercise.main/Discount/DiscountManager.cs @@ -6,7 +6,6 @@ public class DiscountManager { List discountTypes = new List(); - //public void addDiscountType(params object[] args) where T : Discount private Inventory _inventory; public DiscountManager(Inventory inventory) { @@ -14,14 +13,13 @@ public DiscountManager(Inventory inventory) } public void addDiscountType(DiscountBase discount) { - // TODO: Check for and remove identicals... + // Note: should check for and remove identicals... discountTypes.Add(discount); } private List pickBestDeals(List possibleDiscounts, Basket basket) { // Looks for conflicting deals, removes them and favors best value deals - List> conflictingIndexes = new List>(); Dictionary> handleDuplications = new(); List possibleCombinations = new List(); @@ -30,7 +28,7 @@ private List pickBestDeals(List for (int i = 0; i < possibleDiscounts.Count; i++) { bool noConflict = true; - //for (int j = i + 1; j < possibleDiscounts.Count; j++) + // for each discount, check if there exist another discount for the same products, filter out for (int j = 0; j < possibleDiscounts.Count; j++) { if (j == i) @@ -40,12 +38,13 @@ private List pickBestDeals(List if (possibleDiscounts[i].SKU_amount.ContainsKey(key)) { + // adds a list if dictionary doesn't have one for current index if (!handleDuplications.ContainsKey(i)) handleDuplications[i] = new(); if (!handleDuplications.ContainsKey(j)) handleDuplications[j] = new(); - //if (!handleDuplications[i].Contains(j)) + // Avoid adding the same deal twice... if (!handleDuplications[j].Contains(i)) { conflictingIndexes.Add(new(i, j, key)); @@ -61,8 +60,11 @@ private List pickBestDeals(List } } + // Add the non-conflicting possibleCombinations to bestDeal var bestDeals = new List(possibleCombinations); float total = basket.getUndiscountedTotal(); + + // Compare the conflicting deals, select the most valuable foreach (var c in conflictingIndexes) { int index_i = c.Item1; @@ -78,13 +80,11 @@ private List pickBestDeals(List bestDeals.Add(possibleDiscounts[index_j]); } - } return bestDeals; } - //public Order calculateDiscount(Basket basket) private List getPossibleDiscounts(Basket basket) { List possibleDiscounts = new List(); @@ -107,48 +107,57 @@ public Dictionary calculateDiscount(Basket basket) var productList = basket.getProducts(); // Remove Discounted products from the product list... - foreach (var discount in bestDealsDiscounts) - { + productList = filterAwayDiscountedProducts(bestDealsDiscounts, productList); - foreach (var di in discount.SKU_amount) - { - var sku = di.Key; - int amount = di.Value; + Dictionary nameAmountPrice = new Dictionary(); - int counted = 0; - List temp = new List(); + createProductDataDictionary(bestDealsDiscounts, nameAmountPrice); + Dictionary amontPerSku = GetAmountPerSkuDictionary(productList); - foreach (var product in productList) - { - if (sku != product.SKU || counted >= amount) - { - temp.Add(product); - } - else - { - counted++; - } - } + foreach (var product in amontPerSku) + { + float defPrice = _inventory.getPrice(product.Key); - productList = temp; + nameAmountPrice[product.Key] = new OrderData + { + name = product.Key, + amount = product.Value, + //individual_price = defPrice, + discounted_price = 0.0f, // TODO: fix + total_price = defPrice * product.Value, + saving = 0.0f, + }; + } + return nameAmountPrice; + } + private static Dictionary GetAmountPerSkuDictionary(List productList) + { + Dictionary amontPerSku = new Dictionary(); + foreach (var product in productList) + { + if (!amontPerSku.ContainsKey(product.SKU)) + { + amontPerSku[product.SKU] = 0; } + amontPerSku[product.SKU]++; } - //Dictionary> nameAmountPrice = new Dictionary>(); - Dictionary nameAmountPrice = new Dictionary(); + return amontPerSku; + } + private void createProductDataDictionary(List bestDealsDiscounts, Dictionary nameAmountPrice) + { foreach (var product in bestDealsDiscounts) { - //string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys); string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => _inventory.getName(x))); nameAmountPrice[nameStr] = new OrderData { name = nameStr, amount = product.discountMultiple, - individual_price = 0.0f, // TODO: fix ... + //individual_price = 0.0f, // TODO: fix ... discounted_price = product.finalPrice, total_price = product.finalPrice, saving = product.possibleSavings, @@ -156,43 +165,35 @@ public Dictionary calculateDiscount(Basket basket) }; } + } - Dictionary amontPerSku = new Dictionary(); - foreach (var product in productList) + private static List filterAwayDiscountedProducts(List bestDealsDiscounts, List productList) + { + foreach (var discount in bestDealsDiscounts) { - if (!amontPerSku.ContainsKey(product.SKU)) + foreach (var di in discount.SKU_amount) { - amontPerSku[product.SKU] = 0; - } - amontPerSku[product.SKU]++; - } - - //var amontPerSku = basket.getAmountPerSku(); - foreach (var product in amontPerSku) - { - - //float? defPrice = basket.getDefaultPrice(product.Key); - //float? defPrice = this._inventory.getPrice(product.Key); - //if (defPrice == null) - //Debug.Assert(false, "defPrice cant be zero..."); + var sku = di.Key; + int amount = di.Value; - float defPrice = _inventory.getPrice(product.Key); + int counted = 0; + List temp = new List(); - //nameAmountPrice[product.Key] = new (product.Value, defPrice.Value); - nameAmountPrice[product.Key] = new OrderData - { - name = product.Key, - amount = product.Value, - //individual_price = defPrice.Value, - individual_price = defPrice, - discounted_price = 0.0f, // TODO: fix - //total_price = defPrice.Value * product.Value, - total_price = defPrice * product.Value, - saving = 0.0f, - }; + foreach (var product in productList) + { + if (sku != product.SKU || counted >= amount) + { + temp.Add(product); + } + else + { + counted++; + } + } + productList = temp; + } } - - return nameAmountPrice; + return productList; } public string stringify() diff --git a/exercise.main/Discount/Discount_XforY.cs b/exercise.main/Discount/Discount_XforY.cs index c4ec4637..6bfec2d6 100644 --- a/exercise.main/Discount/Discount_XforY.cs +++ b/exercise.main/Discount/Discount_XforY.cs @@ -33,13 +33,8 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) int requiredProduct = discountReq.Value; int d = (int)MathF.Floor(basket.countProductTypes(SKU) / discountReq.Value); - //float? defprice = basket.getDefaultPrice(SKU); - ////if (defprice == null) - //Debug.Assert(defprice != null, "expected SKU to exist in basket..."); - var defprice = _inventory.getPrice(SKU); - //totalCost_withoutDiscount += discountReq.Value * d * (defprice ?? 1.0f); totalCost_withoutDiscount += discountReq.Value * d * defprice; discountedProductSKU[SKU] = d; @@ -62,9 +57,7 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) DiscountedProductCount dp = new DiscountedProductCount { - //SKU_amount = discountedProductSKU, SKU_amount = nrOfDiscounted, - //discountMultiple = maxDiscountMultiplier , discountMultiple = (int)maxDiscountMultiplier, possibleSavings = discountedSavings, finalPrice = totalCost_withDiscount, @@ -77,7 +70,8 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) public override string stringify() { - return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; + //return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; + return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds"; } } diff --git a/exercise.main/IStoreFront.cs b/exercise.main/IStoreFront.cs new file mode 100644 index 00000000..d106f163 --- /dev/null +++ b/exercise.main/IStoreFront.cs @@ -0,0 +1,21 @@ +using exercise.main.Discount; + +namespace exercise.main +{ + public interface IStoreFront + { + + Inventory inventory{ get; set; } + DiscountManager discountManager{ get; set; } + Basket basket { get; set; } + void presentProducts(); + void presentDeals(); + void addToBasket(string skuu, int amount = 1); + void removeFromBasket(string sku, int amount = 1); + void changeCap(int amount); + void showBasket(); + void run(); + void exit(); + } + +} diff --git a/exercise.main/OrderData.cs b/exercise.main/OrderData.cs index 1ed8b468..bfe856be 100644 --- a/exercise.main/OrderData.cs +++ b/exercise.main/OrderData.cs @@ -6,7 +6,7 @@ public struct OrderData public string name; public string productType; public int amount; - public float individual_price; + //public float individual_price; public float discounted_price; public float total_price; public float saving; diff --git a/exercise.main/Product.cs b/exercise.main/Product.cs index 3f865c5a..d90b1b1c 100644 --- a/exercise.main/Product.cs +++ b/exercise.main/Product.cs @@ -5,183 +5,22 @@ using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; -using exercise.main.Discount; namespace exercise.main { - public interface IStoreFront - { - - Inventory inventory{ get; set; } - DiscountManager discountManager{ get; set; } - Basket basket { get; set; } - void presentProducts(); - void presentDeals(); - void addToBasket(string skuu, int amount = 1); - void removeFromBasket(string sku, int amount = 1); - void changeCap(int amount); - void showBasket(); - void run(); - void exit(); - } - - public class TerminalStoreFront : IStoreFront - { - public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager discountManager) - { - this.inventory = inventory; - this.basket = basket; - this.discountManager = discountManager; - - } - public delegate void basketOpFunc(string sku, int amount); - public delegate void baskeSetFunc(int amount); - - public Inventory inventory {get; set;} - public Basket basket { get; set; } - public DiscountManager discountManager { get; set; } - - public void addToBasket(string sku, int amount = 1 ) - { - basket.addProduct(sku, amount); - } - - public void presentDeals() - { - Console.WriteLine("Deals: ---------------------------------"); - Console.Write(discountManager.stringify()); - Console.WriteLine("----------------------------------------\n"); - } - - public void presentProducts() - { - Console.WriteLine("Menu: ----------------------------------"); - Console.Write(inventory.stringify()); - Console.WriteLine("----------------------------------------"); - } - - public void removeFromBasket(string sku, int amount = 1) - { - basket.removeProduct(sku, amount); - } - - public void showBasket() - { - Console.WriteLine("\nBasket: --------------------------------"); - Console.WriteLine(basket.stringify(discountManager)); - Console.WriteLine("----------------------------------------"); - } - public void exit() - { - throw new NotImplementedException(); - } - public void run() - { - - basketOpFunc addTo = new basketOpFunc(addToBasket); - basketOpFunc remFrom = new basketOpFunc(removeFromBasket); - baskeSetFunc setCapF = new baskeSetFunc(changeCap); - - string exitCommand = "/q"; - string userInput = ""; - while (exitCommand != userInput) - { - presentDeals(); - presentProducts(); - if (basket.isNotEmpty()) - { - showBasket(); - } - var currentWarnings = basket.Warnings; - Console.WriteLine(string.Join("\n", currentWarnings)); - - Console.WriteLine("\nInstructions: \n\t/q : quit\n\t/add SKU NR: add items\n\t/rem SKU NR: remove items\n\t/cap NR : change basket capacity"); - - userInput = Console.ReadLine(); - Console.Clear(); - - var args = userInput.Split(" "); - if (args.Length > 0) - { - - CheckPrefromBasketOp(args, "/add", addTo); - CheckPrefromBasketOp(args, "/rem", remFrom); - changeBasketSetting(args, "/cap", setCapF); - - } - } - - } - void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) - { - if (args[0].ToLower() == commandWord) - { - if (args.Length >= 3) - { - int parsed = 0; - if (int.TryParse(args[2], out parsed)) - { - f(args[1].ToUpper(), parsed); - } - } - else - { - f(args[1].ToUpper(), 1); - } - } - } - void changeBasketSetting(string[] args, string commandWord,baskeSetFunc f) - { - if (args[0].ToLower() == commandWord) - { - if (args.Length >= 2) - { - int parsed = 0; - if (int.TryParse(args[1], out parsed)) - { - f( parsed); - } - } - - } - } - - public void changeCap(int amount ) - { - basket.setCapacity(amount); - } - } - public class StoreFrontExecutor - { - IStoreFront storeFront; - public StoreFrontExecutor(IStoreFront storeFront) - { - this.storeFront = storeFront; - } - - public void run() - { - storeFront.run(); - } - - } - public class Product { protected string name; private string sku; protected float productPrice; protected List subProducts; - //protected ProductType productType; - //public BaseProduct(string SKU,string name, float defaultPrice, ProductType productType, List? subProducts = null) public Product(string SKU,string name, float defaultPrice, List? subProducts = null) { this.sku = SKU; this.name = name; this.productPrice = defaultPrice; this.subProducts = subProducts ?? new List(); - //this.productType = productType; } public float CombinedPrice { @@ -199,18 +38,4 @@ public string ProductName public string SKU { get => sku;} } - //public class Product: BaseProduct - //{ - // // This isn't necessary, - // public Product(string SKU, string name, float defaultPrice,List? subProducts = null) - // : base(SKU, name, defaultPrice, subProducts) - // { - // } - //} - - - - - - } diff --git a/exercise.main/StoreFrontExecutor.cs b/exercise.main/StoreFrontExecutor.cs new file mode 100644 index 00000000..b0660b57 --- /dev/null +++ b/exercise.main/StoreFrontExecutor.cs @@ -0,0 +1,18 @@ +namespace exercise.main +{ + public class StoreFrontExecutor + { + IStoreFront storeFront; + public StoreFrontExecutor(IStoreFront storeFront) + { + this.storeFront = storeFront; + } + + public void run() + { + storeFront.run(); + } + + } + +} diff --git a/exercise.main/TerminalStoreFront.cs b/exercise.main/TerminalStoreFront.cs new file mode 100644 index 00000000..31d6756f --- /dev/null +++ b/exercise.main/TerminalStoreFront.cs @@ -0,0 +1,132 @@ +using exercise.main.Discount; + +namespace exercise.main +{ + public class TerminalStoreFront : IStoreFront + { + public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager discountManager) + { + this.inventory = inventory; + this.basket = basket; + this.discountManager = discountManager; + + } + public delegate void basketOpFunc(string sku, int amount); + public delegate void baskeSetFunc(int amount); + + public Inventory inventory {get; set;} + public Basket basket { get; set; } + public DiscountManager discountManager { get; set; } + + public void addToBasket(string sku, int amount = 1 ) + { + basket.addProduct(sku, amount); + } + + public void presentDeals() + { + Console.WriteLine("Deals: -----------------------------------------------"); + Console.Write(discountManager.stringify()); + Console.WriteLine("\n------------------------------------------------------\n"); + } + + public void presentProducts() + { + Console.WriteLine("Menu: ------------------------------------------------"); + Console.Write(inventory.stringify()); + Console.WriteLine("------------------------------------------------------"); + } + + public void removeFromBasket(string sku, int amount = 1) + { + basket.removeProduct(sku, amount); + } + + public void showBasket() + { + Console.WriteLine("\nBasket: ----------------------------------------------"); + Console.WriteLine(basket.stringify(discountManager)); + Console.WriteLine("------------------------------------------------------"); + } + public void exit() + { + throw new NotImplementedException(); + } + public void run() + { + + basketOpFunc addTo = new basketOpFunc(addToBasket); + basketOpFunc remFrom = new basketOpFunc(removeFromBasket); + baskeSetFunc setCapF = new baskeSetFunc(changeCap); + + string exitCommand = "/q"; + string userInput = ""; + while (exitCommand != userInput) + { + presentDeals(); + presentProducts(); + if (basket.isNotEmpty()) + { + showBasket(); + } + var currentWarnings = basket.Warnings; + Console.WriteLine(string.Join("\n", currentWarnings)); + + Console.WriteLine("\nInstructions: \n\t/q : quit\n\t/add SKU NR: add items\n\t/rem SKU NR: remove items\n\t/cap NR : change basket capacity"); + + userInput = Console.ReadLine(); + Console.Clear(); + + var args = userInput.Split(" "); + if (args.Length > 0) + { + + CheckPrefromBasketOp(args, "/add", addTo); + CheckPrefromBasketOp(args, "/rem", remFrom); + changeBasketSetting(args, "/cap", setCapF); + + } + } + + } + void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) + { + if (args[0].ToLower() == commandWord) + { + if (args.Length >= 3) + { + int parsed = 0; + if (int.TryParse(args[2], out parsed)) + { + f(args[1].ToUpper(), parsed); + } + } + else + { + f(args[1].ToUpper(), 1); + } + } + } + void changeBasketSetting(string[] args, string commandWord,baskeSetFunc f) + { + if (args[0].ToLower() == commandWord) + { + if (args.Length >= 2) + { + int parsed = 0; + if (int.TryParse(args[1], out parsed)) + { + f( parsed); + } + } + + } + } + + public void changeCap(int amount ) + { + basket.setCapacity(amount); + } + } + +} diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 3d65916a..2551c07a 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -594,47 +594,63 @@ public void inventory_add() Assert.That(inventory.getPrice("BGLO"), Is.EqualTo(0.49f)); } + [Test] + public void create_cashRegister() + { - //[TestCase("Onion", 2.5f)] - //public void CreateProduct(string productName, float productPrice) - //{ - // var p = new Product(productName, productPrice); - // Assert.Pass(); - //} + CashRegister inventory = new CashRegister(); - //[TestCase("Bread", 2.5f)] - //public void CreateProduct_withSubProducts(string productName, float productPrice, params Tuple[] subProducts) - //{ + Assert.Pass(); + } + [Test] + public void cashRegister_registerBasket() + { - // var products = new List() - // { - // new Product("Ham", 0.5f), - // new Product("Cheese", 0.5f), - // new Product("Lettuce", 0.5f), - // }; + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 100); + var cashReg = new CashRegister(); - // var p = new Product(productName, productPrice, products); + inventory.Add("BGLO", "Onion", 0.49f, 50); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("FILH", "Ham", 0.12f, 50); + inventory.Add("FILC", "Cheese", 0.12f, 50); + inventory.Add("FILE", "Egg", 0.12f, 50); + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO", nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); - // Assert.Pass(); - //} + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); - //[TestCase("Bread", 2.5f)] - //public void CreateProduct_withSubProducts_getPrice(string productName, float productPrice) - //{ + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - // var products = new List() - // { - // new Product("Ham", 0.5f), - // new Product("Cheese", 0.5f), - // new Product("Lettuce", 0.5f), - // }; + var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); - // var expectedCost = products.Sum(p => p.CombinedPrice) + productPrice; + // Add deal to DiscountManager + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); - // var p = new Product(productName, productPrice, products); + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + b.addProduct("BGLO"); + totalPrice += inventory.getPrice("BGLO"); + b.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + b.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); ; + } - // Assert.That( p.CombinedPrice == expectedCost); - //} } \ No newline at end of file From cfe874ec394e0f665237d9a6dc82b5099a8f1db1 Mon Sep 17 00:00:00 2001 From: Lowe Raivio Date: Tue, 14 Jan 2025 20:35:51 +0100 Subject: [PATCH 10/10] Done --- domain-model.md | 13 +- exercise.main/Basket.cs | 4 +- exercise.main/CashRegister.cs | 56 +++++- exercise.main/Discount/DiscountManager.cs | 22 +-- exercise.main/Discount/Discount_XforY.cs | 4 +- exercise.main/Inventory.cs | 2 - exercise.main/Order.cs | 81 ++++++++- exercise.main/OrderData.cs | 1 - exercise.main/Program.cs | 29 ++- exercise.main/StoreFrontExecutor.cs | 2 - exercise.main/TerminalStoreFront.cs | 95 +++++++--- exercise.tests/UnitTest1.cs | 212 ++++++++++++++++++++-- 12 files changed, 437 insertions(+), 84 deletions(-) diff --git a/domain-model.md b/domain-model.md index d5a9f88c..bc7a941d 100644 --- a/domain-model.md +++ b/domain-model.md @@ -117,10 +117,7 @@ I want the customer to see how much they saved on discounts. | 10 | Basket | Add | User wants to add a Product to their Basket, but stock is 0 | nothing is added | | 8 | Basket | Add | User want to select several fillings for their Bagle | Fillings are addded to the order | | | | | | | - | | | | | | - | | | | | | | 11 | DiscountManager | discountTypes (list) | Different Discount types can defined and added to the DiscountManager | Each discount type is considered when calculating the total to pay | | 13 | DiscountManager | calculateDiscount | applies and calculate the best possible discount (a product can only be part of one discount) | The most valuable discounts will always be applied at checkout | @@ -129,7 +126,6 @@ I want the customer to see how much they saved on discounts. | 11, 24 | Discount | GetDiscountedPrice(...) | user is paying and the discounted price needs to be applied | User ends up paying the reduced price | | 11, 24 | Discount_XforY | Discount impl | Manager wants to create discounts for `buy x nr of z for y`, `buy z, get [a,..] for y` for multiple products | Created discount types can now be registered to DiscountManager to be used by customers | | | | | | | - | | | | | | | 13 | CashRegister | Constructor | all purchases is handled by the CashRegister | | | 13 | CashRegister | RegisterBasket | User is ready to pay, all products in their basket will be summed up, any discount will be accounted for | Sets the currentBasket, ready for user to Pay | @@ -139,13 +135,18 @@ I want the customer to see how much they saved on discounts. | 13 | CashRegister | FinalizePurchase | User doesn't have enough money | currentBasket is set to Null | | | | | | | | 16 | Inventory | Constructor | Keep tracks on the inventory | | - | 10 | Inventory | Stock (Dictionary) | User wanted to add Product to their order, but stock is 0 | Nothing is added | - | 16 | Inventory | Remove(Product) | Product is removed when placed in users basket | Product is removed from inventory | | 16 | Inventory | Remove(Product) | Product a removed when User Finalizes a Purchase | Product is removed from inventory | | 16 | Inventory | add(Product, nr) | Manager bought 100 jars of pickle | 100 jars of pickle added to the inventory | + + + + + + \ No newline at end of file diff --git a/exercise.main/Basket.cs b/exercise.main/Basket.cs index 2f353b2f..450f01d7 100644 --- a/exercise.main/Basket.cs +++ b/exercise.main/Basket.cs @@ -134,7 +134,7 @@ public bool isNotEmpty() public float getTotal() { - var orderDataDict = DM.calculateDiscount(this); + var orderDataDict = DM.calculateTotalWithDiscount(this); return orderDataDict.Values.Sum(x => x.total_price); @@ -148,7 +148,7 @@ public float getUndiscountedTotal() public string stringify(DiscountManager dm) { - var cacledBasket = dm.calculateDiscount(this); + var cacledBasket = dm.calculateTotalWithDiscount(this); string ret = string.Format("{0,7} {1,-25}{2,-10}{3,-10}\n", "Type", "Name", "Amount", "Cost"); foreach (var x in cacledBasket.ToList()) diff --git a/exercise.main/CashRegister.cs b/exercise.main/CashRegister.cs index 61860c70..9c320aa7 100644 --- a/exercise.main/CashRegister.cs +++ b/exercise.main/CashRegister.cs @@ -1,8 +1,62 @@ using exercise.main; +using exercise.main.Discount; namespace exercise.tests { - public class CashRegister + public class CashRegister { + + private Inventory _inventory ; + private DiscountManager _dm; + private Order currentOrder; + private Basket currentBasket; + public CashRegister(Inventory inventory,DiscountManager dm) + { + _inventory = inventory; + _dm= dm; + currentOrder = null; + currentBasket = null; + } + public void registerBasket(Basket basket) + { + + var cacledBasket = this._dm.calculateTotalWithDiscount(basket); + currentBasket = basket; + + string ret = $"Pay:\n\t Total: {basket.getTotal()}"; + + currentOrder = new Order(cacledBasket); + + Console.WriteLine(ret); + } + private string createReciept() + { + return currentOrder.createReciept(_inventory, currentBasket); + } + public string finalizePurchase(bool successfulPayment) + { + // Function is called to confirm the payment + // If payment failed, successPayment will be false + // This is just to demonstrate... it's not applicable in real life situation... + + string returnMsg = ""; + + if(successfulPayment) + { + // return Reciept + returnMsg = createReciept(); + } + else + { + // return error message + returnMsg = "You failed paying for your bagels..."; + } + + this.currentOrder = null; + this.currentBasket = null; + + return returnMsg; + //basket. + } } } \ No newline at end of file diff --git a/exercise.main/Discount/DiscountManager.cs b/exercise.main/Discount/DiscountManager.cs index 201fe2e7..aa7106ce 100644 --- a/exercise.main/Discount/DiscountManager.cs +++ b/exercise.main/Discount/DiscountManager.cs @@ -54,10 +54,10 @@ private List pickBestDeals(List } } } + if (noConflict) - { possibleCombinations.Add(possibleDiscounts[i]); - } + } // Add the non-conflicting possibleCombinations to bestDeal @@ -72,14 +72,10 @@ private List pickBestDeals(List string SKU = c.Item3; if (total + possibleDiscounts[index_i].possibleSavings > total + possibleDiscounts[index_j].possibleSavings) - { bestDeals.Add(possibleDiscounts[index_i]); - } else - { bestDeals.Add(possibleDiscounts[index_j]); - } } return bestDeals; @@ -91,13 +87,12 @@ private List getPossibleDiscounts(Basket basket) foreach (DiscountBase discount in discountTypes) { if (discount.checkCondition(basket)) - { possibleDiscounts.Add(discount.getDiscountedPrice(basket)); - } + } return possibleDiscounts; } - public Dictionary calculateDiscount(Basket basket) + public Dictionary calculateTotalWithDiscount(Basket basket) { var possibleDiscounts = getPossibleDiscounts(basket); var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); @@ -122,8 +117,7 @@ public Dictionary calculateDiscount(Basket basket) { name = product.Key, amount = product.Value, - //individual_price = defPrice, - discounted_price = 0.0f, // TODO: fix + discounted_price = 0.0f, total_price = defPrice * product.Value, saving = 0.0f, }; @@ -138,9 +132,8 @@ private static Dictionary GetAmountPerSkuDictionary(List p foreach (var product in productList) { if (!amontPerSku.ContainsKey(product.SKU)) - { amontPerSku[product.SKU] = 0; - } + amontPerSku[product.SKU]++; } @@ -157,13 +150,14 @@ private void createProductDataDictionary(List bestDealsD { name = nameStr, amount = product.discountMultiple, - //individual_price = 0.0f, // TODO: fix ... discounted_price = product.finalPrice, total_price = product.finalPrice, saving = product.possibleSavings, UsedDiscount = product.discount }; + + Console.Write(""); } } diff --git a/exercise.main/Discount/Discount_XforY.cs b/exercise.main/Discount/Discount_XforY.cs index 6bfec2d6..0d9c82a8 100644 --- a/exercise.main/Discount/Discount_XforY.cs +++ b/exercise.main/Discount/Discount_XforY.cs @@ -45,7 +45,8 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) totalCost_withDiscount = this.DiscountPrice * maxDiscountMultiplier; - discountedSavings = this.DiscountPrice - totalCost_withoutDiscount; + //discountedSavings = this.DiscountPrice - totalCost_withoutDiscount; + discountedSavings = totalCost_withDiscount - totalCost_withoutDiscount; Dictionary nrOfDiscounted = new Dictionary(); foreach (var discountReq in nrOfRequiredProducts) @@ -70,7 +71,6 @@ public override DiscountedProductCount getDiscountedPrice(Basket basket) public override string stringify() { - //return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds\n"; return $"{string.Join(", ", nrOfRequiredProducts.ToList().Select(x => $"{x.Value} {_inventory.getProductType(x.Key)} {this._inventory.getName(x.Key)} ({x.Key})"))} for {this.DiscountPrice} Pounds"; } } diff --git a/exercise.main/Inventory.cs b/exercise.main/Inventory.cs index 46c435ec..4b37c55d 100644 --- a/exercise.main/Inventory.cs +++ b/exercise.main/Inventory.cs @@ -21,7 +21,6 @@ public InventoryData(string name, string sKU, float price, int stock) } public string stringify(Inventory inventory) { - //return string.Format("{0,0:10}{1,0:10}{2,0:30}{3,0:40}", SKU, name, price, stock); return String.Format("{0,0}{1,15}{2,15}{3,10}{4,10}", SKU, inventory.getProductType(SKU), name, price, stock); } } @@ -65,7 +64,6 @@ public string getProductType(string SKU) public string stringify() { - //return "Inventory:\n"+ string.Join("\n", inventory.ToList().Select(x=>x.Value.toString())); string st = String.Format("{0,0}{1,15}{2,15}{3,10}{4,10}\n", "SKU", "Product Type", "Variant", "Price", "Stock"); ; foreach (var item in inventory.ToList()) { diff --git a/exercise.main/Order.cs b/exercise.main/Order.cs index 077747e4..31183544 100644 --- a/exercise.main/Order.cs +++ b/exercise.main/Order.cs @@ -1,11 +1,86 @@ -namespace exercise.main +using System.Globalization; + +namespace exercise.main { public class Order { - public Dictionary orderDatas; - public Order(Basket basket) + private Dictionary _orderDatas; + public Order(Dictionary orderDatas) { + _orderDatas = orderDatas; + } + + private string centeredText(string text, int width) + { + int calcedPadd = (width - text.Length) / 2; + string padding = $"0,{calcedPadd}"; + return string.Format( + $"{{{padding}}}{text}",""); + + } + + private string maxLenNewLine(string text, int maxSize) + { + int nrOfNewLines = text.Length / maxSize; + string newText = ""; + for (int i = 0;i < text.Length; i++) + { + if (i % maxSize == 0) + newText += "\n"; + + newText += text[i]; + + } + return newText; + } + public string createReciept(Inventory inventory, Basket basket) + { + string line = "----------------------------"; + int width = line.Length; + + string title = centeredText("~~~ Bob's Bagels ~~~", width); + + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB", false); + + string pline = centeredText(line, width); + + string ret = $"{title}\n\n{centeredText(DateTime.Now.ToString(), width)}\n\n{pline}"; + + foreach (var x in _orderDatas.ToList()) + { + if (x.Value.UsedDiscount == null) + { + ret += "\n" + string.Format( + "{0,-20} {1,5} {2,8:c}", + inventory.getProductType(x.Value.name)+" "+ inventory.getName(x.Value.name), + x.Value.amount, + "£"+x.Value.total_price + ); + } + else + { + + int dealWidth = 22; + string dealText = maxLenNewLine(x.Value.UsedDiscount.stringify(), dealWidth); + int dealpad = dealText.Split("\n").Last().Length; + + ret += "\n" + string.Format($"{{0,-20}} ", + dealText + ); + ret += string.Format($"{{0,{(-20 + dealpad) }}}{{1,5}}{{2,8:c}}", + "", + x.Value.amount, + x.Value.total_price + )+ "\n"; + + ret += string.Format($"{{0,-20}}{{1,5}}({{2,8:c}})", "", "", MathF.Round(x.Value.saving, 2)); + + } + } + ret += string.Format("\n\n{0}\n\nTotal: {1,20:c}", pline, basket.getTotal()); + ret += string.Format("\n\n{0}\n{1}", centeredText("Thank you", width), centeredText("for your order!",width)); + return ret; } } diff --git a/exercise.main/OrderData.cs b/exercise.main/OrderData.cs index bfe856be..0cf6045b 100644 --- a/exercise.main/OrderData.cs +++ b/exercise.main/OrderData.cs @@ -6,7 +6,6 @@ public struct OrderData public string name; public string productType; public int amount; - //public float individual_price; public float discounted_price; public float total_price; public float saving; diff --git a/exercise.main/Program.cs b/exercise.main/Program.cs index bc8223f8..04f30848 100644 --- a/exercise.main/Program.cs +++ b/exercise.main/Program.cs @@ -3,20 +3,31 @@ using exercise.main; using exercise.main.Discount; +using exercise.tests; Inventory inventory = new Inventory(); DiscountManager discountManager = new DiscountManager(inventory); +CashRegister cashRegister = new CashRegister(inventory, discountManager); + Basket basket = new Basket(inventory, discountManager); -inventory.Add("BGLO", "Onion", 0.49f, 100); -inventory.Add("BGLP", "Plain", 0.39f, 100); -inventory.Add("BGLE", "Everything", 0.39f, 100); -inventory.Add("BGLS", "Sesame", 0.49f, 100); -inventory.Add("COFB", "Black", 0.99f, 100); -inventory.Add("FILH", "Ham", 0.12f, 100); -inventory.Add("FILC", "Cheese", 0.12f, 100); -inventory.Add("FILE", "Egg", 0.12f, 100); + +inventory.Add("BGLO", "Onion", 0.49f, 100); +inventory.Add("BGLP", "Plain", 0.39f, 100); +inventory.Add("BGLE", "Everything", 0.49f, 100); +inventory.Add("BGLS", "Sesame", 0.49f, 100); +inventory.Add("COFB", "Black", 0.99f, 100); +inventory.Add("COFW", "White", 1.19f, 100); +inventory.Add("COFC", "Capuccino", 1.29f, 100); +inventory.Add("COFL", "Latte", 1.29f, 100); +inventory.Add("FILB", "Bacon", 0.12f, 100); +inventory.Add("FILE", "Egg", 0.12f, 100); +inventory.Add("FILC", "Cheese", 0.12f, 100); +inventory.Add("FILX", "Cream Cheese", 0.12f, 100); +inventory.Add("FILS", "Smoked Salmon", 0.12f, 100); +inventory.Add("FILH", "Ham", 0.12f, 100); + int nrOfBagelsForDiscount = 6; float discountedPrice_6_for_2_49 = 2.49f; @@ -39,6 +50,6 @@ -var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager)); +var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager, cashRegister)); storeFrontExecutior.run(); \ No newline at end of file diff --git a/exercise.main/StoreFrontExecutor.cs b/exercise.main/StoreFrontExecutor.cs index b0660b57..59283424 100644 --- a/exercise.main/StoreFrontExecutor.cs +++ b/exercise.main/StoreFrontExecutor.cs @@ -12,7 +12,5 @@ public void run() { storeFront.run(); } - } - } diff --git a/exercise.main/TerminalStoreFront.cs b/exercise.main/TerminalStoreFront.cs index 31d6756f..a84a797c 100644 --- a/exercise.main/TerminalStoreFront.cs +++ b/exercise.main/TerminalStoreFront.cs @@ -1,23 +1,37 @@ using exercise.main.Discount; +using exercise.tests; namespace exercise.main { public class TerminalStoreFront : IStoreFront { - public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager discountManager) + public TerminalStoreFront(Inventory inventory, Basket basket, DiscountManager discountManager, CashRegister cashRegister) { this.inventory = inventory; this.basket = basket; this.discountManager = discountManager; + this.cashRegister= cashRegister; } public delegate void basketOpFunc(string sku, int amount); public delegate void baskeSetFunc(int amount); + public delegate void baskePayFunc(); public Inventory inventory {get; set;} public Basket basket { get; set; } public DiscountManager discountManager { get; set; } + public CashRegister cashRegister { get; set; } + private List _messages = new List(); + private List Messages + { + get + { + var temp = _messages.ToList(); + _messages.Clear(); + return temp; + } + } public void addToBasket(string sku, int amount = 1 ) { basket.addProduct(sku, amount); @@ -52,12 +66,30 @@ public void exit() { throw new NotImplementedException(); } + public void pay() + { + cashRegister.registerBasket(basket); + Console.WriteLine("Type y to confirm"); + string userInput = Console.ReadLine().ToLower(); + if (userInput.Length == 1 && userInput[0] == 'y') + { + _messages.Add("Thanks, here is your reciept:\n"); + _messages.Add("------------------------------------------------------\n"); + _messages.Add(cashRegister.finalizePurchase(true)); + _messages.Add("\n------------------------------------------------------\n"); + } + else + { + _messages.Add(cashRegister.finalizePurchase(false)); + } + } public void run() { basketOpFunc addTo = new basketOpFunc(addToBasket); basketOpFunc remFrom = new basketOpFunc(removeFromBasket); baskeSetFunc setCapF = new baskeSetFunc(changeCap); + baskePayFunc payF = new baskePayFunc(pay); string exitCommand = "/q"; string userInput = ""; @@ -65,14 +97,16 @@ public void run() { presentDeals(); presentProducts(); + if (basket.isNotEmpty()) - { showBasket(); - } + var currentWarnings = basket.Warnings; Console.WriteLine(string.Join("\n", currentWarnings)); + Console.WriteLine(string.Join("\n", Messages)); + - Console.WriteLine("\nInstructions: \n\t/q : quit\n\t/add SKU NR: add items\n\t/rem SKU NR: remove items\n\t/cap NR : change basket capacity"); + Console.WriteLine("\nInstructions: \n\t/q : quit\n\t/add SKU NR: add items\n\t/rem SKU NR: remove items\n\t/cap NR : change basket capacity\n\t/pay : finalize order"); userInput = Console.ReadLine(); Console.Clear(); @@ -84,44 +118,49 @@ public void run() CheckPrefromBasketOp(args, "/add", addTo); CheckPrefromBasketOp(args, "/rem", remFrom); changeBasketSetting(args, "/cap", setCapF); + execBasketFunc(args, "/pay", payF); } } - } void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) { - if (args[0].ToLower() == commandWord) + if (args[0].ToLower() != commandWord) + return; + + if (args.Length >= 3) { - if (args.Length >= 3) - { - int parsed = 0; - if (int.TryParse(args[2], out parsed)) - { - f(args[1].ToUpper(), parsed); - } - } - else - { - f(args[1].ToUpper(), 1); - } + int parsed = 0; + if (int.TryParse(args[2], out parsed)) + f(args[1].ToUpper(), parsed); + + } + else + { + f(args[1].ToUpper(), 1); } + } void changeBasketSetting(string[] args, string commandWord,baskeSetFunc f) { - if (args[0].ToLower() == commandWord) + if (args[0].ToLower() != commandWord) + return; + + if (args.Length >= 2) { - if (args.Length >= 2) - { - int parsed = 0; - if (int.TryParse(args[1], out parsed)) - { - f( parsed); - } - } - + int parsed = 0; + if (int.TryParse(args[1], out parsed)) + f( parsed); + } } + void execBasketFunc(string[] args, string commandWord, baskePayFunc f) + { + if (args[0].ToLower() != commandWord) + return; + + f(); + } public void changeCap(int amount ) { diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 2551c07a..b90f96b5 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -275,7 +275,7 @@ public void createDiscountType() //Order o = dm.calculateDiscount(p); - var orderDataDict = dm.calculateDiscount(p); + var orderDataDict = dm.calculateTotalWithDiscount(p); var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); @@ -307,7 +307,7 @@ public void multipleDiscountsAreAppliedCorrectly_onlyBuyOneDeal() var discountReq_3 = new Dictionary { { "BGLP", 12 } }; var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); // Add deal to DiscountManager @@ -332,7 +332,7 @@ public void multipleDiscountsAreAppliedCorrectly_onlyBuyOneDeal() //Order o = dm.calculateDiscount(p); - var orderDataDict = dm.calculateDiscount(p); + var orderDataDict = dm.calculateTotalWithDiscount(p); var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); @@ -364,7 +364,7 @@ public void multipleDiscountsAreAppliedCorrectly_buyDealAndOther() var discountReq_3 = new Dictionary { { "BGLP", 12 } }; var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); // Add deal to DiscountManager @@ -400,7 +400,7 @@ public void multipleDiscountsAreAppliedCorrectly_buyDealAndOther() //Order o = dm.calculateDiscount(p); - var orderDataDict = dm.calculateDiscount(p); + var orderDataDict = dm.calculateTotalWithDiscount(p); var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); @@ -433,7 +433,7 @@ public void discountManager_pickTheOneDealAvailable() var discountReq_3 = new Dictionary { { "BGLP", 12 } }; var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); // Add deal to DiscountManager @@ -498,7 +498,7 @@ public void discountManagerPicksBestDeals(int nrOfCoffeeToAdd) var discountReq_3 = new Dictionary { { "BGLP", 12 } }; var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); // Add deal to DiscountManager @@ -567,6 +567,7 @@ public void createTerminalStoreFront() DiscountManager dm = new DiscountManager(inventory); Basket basket = new Basket(inventory, dm); DiscountManager discountManager = new DiscountManager(inventory); + CashRegister cashRegister = new CashRegister(inventory,discountManager); inventory.Add("BGLO", "Onion", 0.49f, 100); inventory.Add("BGLS", "Sesame", 0.49f, 100); @@ -575,7 +576,7 @@ public void createTerminalStoreFront() inventory.Add("FILE", "Egg", 0.12f, 100); - var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager)); + var storeFrontExecutior = new StoreFrontExecutor(new TerminalStoreFront(inventory, basket, discountManager, cashRegister)); //storeFrontExecutior.run(); // Will get stuck,... not part of the test @@ -597,8 +598,9 @@ public void inventory_add() [Test] public void create_cashRegister() { - - CashRegister inventory = new CashRegister(); + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + CashRegister cashRegister = new CashRegister(inventory,dm); Assert.Pass(); } @@ -609,7 +611,7 @@ public void cashRegister_registerBasket() Inventory inventory = new Inventory(); DiscountManager dm = new DiscountManager(inventory); var b = new Basket(inventory, dm, 100); - var cashReg = new CashRegister(); + var cashReg = new CashRegister(inventory, dm); inventory.Add("BGLO", "Onion", 0.49f, 50); inventory.Add("COFB", "Black", 0.99f, 100); @@ -629,7 +631,7 @@ public void cashRegister_registerBasket() var discountReq_3 = new Dictionary { { "BGLP", 12 } }; var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); - var discountReq_4 = new Dictionary { { "BGLE", 12 } }; + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); // Add deal to DiscountManager @@ -649,8 +651,190 @@ public void cashRegister_registerBasket() b.addProduct("FILE"); totalPrice += inventory.getPrice("FILE"); b.addProduct("FILE"); - totalPrice += inventory.getPrice("FILE"); ; + totalPrice += inventory.getPrice("FILE"); + + + + cashReg.registerBasket(b); + + Type cashRegType = cashReg.GetType(); + + FieldInfo currentOrder_field = cashRegType.GetField("currentOrder", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo currentBasket_field = cashRegType.GetField("currentBasket", BindingFlags.NonPublic | BindingFlags.Instance); + Order registered_order = (Order)currentOrder_field.GetValue(cashReg); + Basket registered_basket = (Basket)currentBasket_field.GetValue(cashReg); + + Assert.That(registered_order != null); + Assert.That(registered_basket != null); + + } + + [Test] + public void cashRegister_registerBasket_thenPaySuccessfully() + { + + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 100); + var cashReg = new CashRegister(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLP", "Plain", 0.39f, 100); + inventory.Add("BGLE", "Everything", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("COFW", "White", 1.19f, 100); + inventory.Add("COFC", "Capuccino", 1.29f, 100); + inventory.Add("COFL", "Latte", 1.29f, 100); + inventory.Add("FILB", "Bacon", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILX", "Cream Cheese", 0.12f, 100); + inventory.Add("FILS", "Smoked Salmon", 0.12f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO", nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + b.addProduct("BGLS",3); + b.addProduct("BGLP",25); + b.addProduct("BGLO",6); + b.addProduct("BGLE",12); + b.addProduct("cofb",2); + totalPrice += inventory.getPrice("BGLO"); + b.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + b.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + + + + cashReg.registerBasket(b); + + string reciept = cashReg.finalizePurchase(true); + + Assert.That(reciept.Length != 0); + Assert.That(reciept != "You failed paying for your bagels..."); + + + + Type cashRegType = cashReg.GetType(); + + FieldInfo currentOrder_field = cashRegType.GetField("currentOrder", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo currentBasket_field = cashRegType.GetField("currentBasket", BindingFlags.NonPublic | BindingFlags.Instance); + Order registered_order = (Order)currentOrder_field.GetValue(cashReg); + Basket registered_basket = (Basket)currentBasket_field.GetValue(cashReg); + + Assert.That(registered_order == null); + Assert.That(registered_basket == null); + + + } + [Test] + public void cashRegister_registerBasket_thenPayFailure() + { + + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var b = new Basket(inventory, dm, 100); + var cashReg = new CashRegister(inventory, dm); + + inventory.Add("BGLO", "Onion", 0.49f, 100); + inventory.Add("BGLP", "Plain", 0.39f, 100); + inventory.Add("BGLE", "Everything", 0.49f, 100); + inventory.Add("BGLS", "Sesame", 0.49f, 100); + inventory.Add("COFB", "Black", 0.99f, 100); + inventory.Add("COFW", "White", 1.19f, 100); + inventory.Add("COFC", "Capuccino", 1.29f, 100); + inventory.Add("COFL", "Latte", 1.29f, 100); + inventory.Add("FILB", "Bacon", 0.12f, 100); + inventory.Add("FILE", "Egg", 0.12f, 100); + inventory.Add("FILC", "Cheese", 0.12f, 100); + inventory.Add("FILX", "Cream Cheese", 0.12f, 100); + inventory.Add("FILS", "Smoked Salmon", 0.12f, 100); + inventory.Add("FILH", "Ham", 0.12f, 100); + + // Create Discount deal, 6 BGLOO, for 2.49f + int nrOfBagelsForDiscount = 6; + float discountedPrice_6_for_2_49 = 2.49f; + var discountReq = new Dictionary { { "BGLO", nrOfBagelsForDiscount } }; + var d = new Discount_XforY(discountReq, discountedPrice_6_for_2_49, inventory); + + var discountReq_2 = new Dictionary { { "BGLO", 1 }, { "COFB", 1 } }; + var d2 = new Discount_XforY(discountReq_2, 1.25f, inventory); + + var discountReq_3 = new Dictionary { { "BGLP", 12 } }; + var d3 = new Discount_XforY(discountReq_3, 3.99f, inventory); + + var discountReq_4 = new Dictionary { { "BGLE", 6 } }; + var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); + + // Add deal to DiscountManager + dm.addDiscountType(d); + dm.addDiscountType(d2); + dm.addDiscountType(d3); + dm.addDiscountType(d4); + + // Add non-discounted Products, calculate the total + float totalPrice = 0.0f; + b.addProduct("BGLO"); + totalPrice += inventory.getPrice("BGLO"); + b.addProduct("FILH"); + totalPrice += inventory.getPrice("FILH"); + b.addProduct("FILC"); + totalPrice += inventory.getPrice("FILC"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + b.addProduct("FILE"); + totalPrice += inventory.getPrice("FILE"); + + + + cashReg.registerBasket(b); + + string reciept = cashReg.finalizePurchase(false); + + Assert.That(reciept.Length != 0); + Assert.That(reciept == "You failed paying for your bagels..."); + + + + Type cashRegType = cashReg.GetType(); + + FieldInfo currentOrder_field = cashRegType.GetField("currentOrder", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo currentBasket_field = cashRegType.GetField("currentBasket", BindingFlags.NonPublic | BindingFlags.Instance); + Order registered_order = (Order)currentOrder_field.GetValue(cashReg); + Basket registered_basket = (Basket)currentBasket_field.GetValue(cashReg); + + Assert.That(registered_order == null); + Assert.That(registered_basket == null); + + } - } \ No newline at end of file +} \ No newline at end of file