diff --git a/domain-model.md b/domain-model.md new file mode 100644 index 00000000..bc7a941d --- /dev/null +++ b/domain-model.md @@ -0,0 +1,152 @@ +## 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`. + + + +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 | +| | | | | | +| 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 | +| 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 | +| 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 | +| | | | | | +| | | | | | +| | | | | | +| 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 | +| | | | | | +| | | | | | +| 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 | | +| 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 new file mode 100644 index 00000000..450f01d7 --- /dev/null +++ b/exercise.main/Basket.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using exercise.main.Discount; + +namespace exercise.main +{ + public class Basket + { + private List products = new List(); + private 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(string productSku, int amount = 1) + { + if (amount < 1) + { + _warnings.Add("Amount to add must be positive"); + return; + } + + int productStock = this._inventory.getStock(productSku); + if (productStock < amount) + { + _warnings.Add($"Can't add {amount} {productSku}, as the there's {productStock} in stock "); + return; + } + + if (NrOfItems + amount > Capacity ) + { + _warnings.Add($"Can't add {amount} {productSku}, will be more than Baskets Capacity of {Capacity} items"); + return; + } + + + for (int i = 0; i < amount; i++) + { + this.products.Add(_inventory.createProductType(productSku)); + } + this._inventory.decreaseStock(productSku, amount); + } + public bool removeProduct(string productSku, int amount = 1) + { + if (amount < 1) + { + _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() + { + // Return a deep copy of the list... + var cpyList = new List(); + foreach (Product product in this.products) + { + cpyList.Add(product); + } + 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 bool isNotEmpty() + { + return this.products.Count > 0; + } + + public float getTotal() + { + var orderDataDict = DM.calculateTotalWithDiscount(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.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()) + { + if (x.Value.UsedDiscount == null) + { + 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,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); + } + } + + ret += $"\n\n Total: {getTotal()}"; + + return ret; + } + + } +} diff --git a/exercise.main/CashRegister.cs b/exercise.main/CashRegister.cs new file mode 100644 index 00000000..9c320aa7 --- /dev/null +++ b/exercise.main/CashRegister.cs @@ -0,0 +1,62 @@ +using exercise.main; +using exercise.main.Discount; + +namespace exercise.tests +{ + 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/DiscountBase.cs b/exercise.main/Discount/DiscountBase.cs new file mode 100644 index 00000000..8fb75551 --- /dev/null +++ b/exercise.main/Discount/DiscountBase.cs @@ -0,0 +1,24 @@ +namespace exercise.main.Discount +{ + public abstract class DiscountBase + { + public DiscountBase(float discountedPrice, Inventory inventory) + { + discountPrice = discountedPrice; + _inventory = inventory; + } + + private float discountPrice; + protected Inventory _inventory; + + public float DiscountPrice { get => discountPrice; } + + 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 new file mode 100644 index 00000000..aa7106ce --- /dev/null +++ b/exercise.main/Discount/DiscountManager.cs @@ -0,0 +1,203 @@ +using System.Linq; + +namespace exercise.main.Discount +{ + public class DiscountManager + { + + List discountTypes = new List(); + private Inventory _inventory; + public DiscountManager(Inventory inventory) + { + _inventory = inventory; + } + public void addDiscountType(DiscountBase discount) + { + // 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(); + + // Count/collect possible conflicting deals + for (int i = 0; i < possibleDiscounts.Count; i++) + { + bool noConflict = true; + // 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) + continue; + foreach (var key in possibleDiscounts[j].SKU_amount.Keys) + { + + 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(); + + // Avoid adding the same deal twice... + if (!handleDuplications[j].Contains(i)) + { + conflictingIndexes.Add(new(i, j, key)); + handleDuplications[i].Add(j); + } + noConflict = false; + } + } + } + + if (noConflict) + possibleCombinations.Add(possibleDiscounts[i]); + + } + + // 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; + int index_j = c.Item2; + 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; + } + + private List getPossibleDiscounts(Basket basket) + { + List possibleDiscounts = new List(); + foreach (DiscountBase discount in discountTypes) + { + if (discount.checkCondition(basket)) + possibleDiscounts.Add(discount.getDiscountedPrice(basket)); + + } + return possibleDiscounts; + } + public Dictionary calculateTotalWithDiscount(Basket basket) + { + var possibleDiscounts = getPossibleDiscounts(basket); + var bestDealsDiscounts = pickBestDeals(possibleDiscounts, basket); + + List orderData = new List(); + + var productList = basket.getProducts(); + + // Remove Discounted products from the product list... + productList = filterAwayDiscountedProducts(bestDealsDiscounts, productList); + + Dictionary nameAmountPrice = new Dictionary(); + + createProductDataDictionary(bestDealsDiscounts, nameAmountPrice); + Dictionary amontPerSku = GetAmountPerSkuDictionary(productList); + + foreach (var product in amontPerSku) + { + float defPrice = _inventory.getPrice(product.Key); + + nameAmountPrice[product.Key] = new OrderData + { + name = product.Key, + amount = product.Value, + discounted_price = 0.0f, + 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]++; + } + + return amontPerSku; + } + + private void createProductDataDictionary(List bestDealsDiscounts, Dictionary nameAmountPrice) + { + foreach (var product in bestDealsDiscounts) + { + string nameStr = "deal: " + string.Join(" + ", product.SKU_amount.Keys.Select(x => _inventory.getName(x))); + + nameAmountPrice[nameStr] = new OrderData + { + name = nameStr, + amount = product.discountMultiple, + discounted_price = product.finalPrice, + total_price = product.finalPrice, + saving = product.possibleSavings, + UsedDiscount = product.discount + + }; + + Console.Write(""); + } + } + + private static List filterAwayDiscountedProducts(List bestDealsDiscounts, List productList) + { + 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; + } + } + return productList; + } + + 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 new file mode 100644 index 00000000..0d9c82a8 --- /dev/null +++ b/exercise.main/Discount/Discount_XforY.cs @@ -0,0 +1,81 @@ +namespace exercise.main.Discount +{ + public class Discount_XforY : DiscountBase + { + 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); + + var defprice = _inventory.getPrice(SKU); + + 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; + discountedSavings = totalCost_withDiscount - 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 = nrOfDiscounted, + discountMultiple = (int)maxDiscountMultiplier, + possibleSavings = discountedSavings, + finalPrice = totalCost_withDiscount, + discount = this, + }; + + return dp; + } + + 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"; + } + } + + + + +} diff --git a/exercise.main/Discount/DiscountedProductCount.cs b/exercise.main/Discount/DiscountedProductCount.cs new file mode 100644 index 00000000..fc31eda3 --- /dev/null +++ b/exercise.main/Discount/DiscountedProductCount.cs @@ -0,0 +1,16 @@ +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; + public DiscountBase discount = null; + + } + + + + +} 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/Inventory.cs b/exercise.main/Inventory.cs new file mode 100644 index 00000000..4b37c55d --- /dev/null +++ b/exercise.main/Inventory.cs @@ -0,0 +1,177 @@ +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 = 0.0f; + public int stock = 0; + + public InventoryData(string name, string sKU, float price, int stock) + { + this.name = name; + SKU = sKU; + this.price = price; + this.stock = stock; + } + public string stringify(Inventory inventory) + { + return String.Format("{0,0}{1,15}{2,15}{3,10}{4,10}", SKU, inventory.getProductType(SKU), name, price, stock); + } + } + + + public class Inventory + { + Dictionary inventory = new Dictionary(); + 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() + { + + 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(this) + "\n"; + } + return st; + } + 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(Product 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/Order.cs b/exercise.main/Order.cs new file mode 100644 index 00000000..31183544 --- /dev/null +++ b/exercise.main/Order.cs @@ -0,0 +1,90 @@ +using System.Globalization; + +namespace exercise.main +{ + public class Order + { + 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 new file mode 100644 index 00000000..0cf6045b --- /dev/null +++ b/exercise.main/OrderData.cs @@ -0,0 +1,20 @@ +using exercise.main.Discount; +namespace exercise.main +{ + public struct OrderData + { + public string name; + public string productType; + public int amount; + public float discounted_price; + public float total_price; + 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 new file mode 100644 index 00000000..d90b1b1c --- /dev/null +++ b/exercise.main/Product.cs @@ -0,0 +1,41 @@ +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 class Product + { + protected string name; + private string sku; + protected float productPrice; + protected List subProducts; + 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(); + } + public float CombinedPrice + { + get { return this.productPrice + subProducts.Sum(x => x.CombinedPrice); } + } + public float ProductPrice + { + get { return this.productPrice; } + } + public string ProductName + { + get { return this.name; } + } + + public string SKU { get => sku;} + } + +} diff --git a/exercise.main/Program.cs b/exercise.main/Program.cs index 3751555c..04f30848 100644 --- a/exercise.main/Program.cs +++ b/exercise.main/Program.cs @@ -1,2 +1,55 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); + + + +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.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; +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", 6 }}; +var d4 = new Discount_XforY(discountReq_4, 2.49f, inventory); +discountManager.addDiscountType(d4); + + + + +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 new file mode 100644 index 00000000..59283424 --- /dev/null +++ b/exercise.main/StoreFrontExecutor.cs @@ -0,0 +1,16 @@ +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..a84a797c --- /dev/null +++ b/exercise.main/TerminalStoreFront.cs @@ -0,0 +1,171 @@ +using exercise.main.Discount; +using exercise.tests; + +namespace exercise.main +{ + public class TerminalStoreFront : IStoreFront + { + 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); + } + + 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 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 = ""; + while (exitCommand != userInput) + { + 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\n\t/pay : finalize order"); + + 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); + execBasketFunc(args, "/pay", payF); + + } + } + } + void CheckPrefromBasketOp(string[] args, string commandWord,basketOpFunc f) + { + if (args[0].ToLower() != commandWord) + return; + + 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) + return; + + if (args.Length >= 2) + { + 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 ) + { + basket.setCapacity(amount); + } + } + +} 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 diff --git a/exercise.tests/UnitTest1.cs b/exercise.tests/UnitTest1.cs index 7bdb8968..b90f96b5 100644 --- a/exercise.tests/UnitTest1.cs +++ b/exercise.tests/UnitTest1.cs @@ -1,15 +1,840 @@ +using System.Linq.Expressions; +using System.Reflection.Metadata.Ecma335; +using exercise.main; +using exercise.main.Discount; +using NUnit.Framework.Constraints; +using System.Reflection; + namespace exercise.tests; public class Tests { - [SetUp] - public void Setup() + + [Test] + public void CreateBasket() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + var p = new Basket(inventory, dm); + Assert.Pass(); + } + + [Test] + public void addProductToBasket() + { + Inventory inventory = new 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); + 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"); + + 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 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 removeProductFromBasket() + { + Inventory inventory = new 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); + 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); + + 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); + 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 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(); + 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); + + // 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.calculateTotalWithDiscount(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", 6 } }; + 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.calculateTotalWithDiscount(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", 6 } }; + 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"); + 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.calculateTotalWithDiscount(p); + + var returnedTotal= orderDataDict.Values.Sum(x => x.total_price); + + 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", 6 } }; + 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", 6 } }; + 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() + { + + Inventory inventory = new Inventory(); + Assert.Pass(); + + } + [Test] + public void createTerminalStoreFront() + { + + Inventory inventory = new Inventory(); + 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); + 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, cashRegister)); + + //storeFrontExecutior.run(); // Will get stuck,... not part of the test + + Assert.Pass(); + + } [Test] - public void Test1() + 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)); + + } + [Test] + public void create_cashRegister() + { + Inventory inventory = new Inventory(); + DiscountManager dm = new DiscountManager(inventory); + CashRegister cashRegister = new CashRegister(inventory,dm); + Assert.Pass(); } + [Test] + public void cashRegister_registerBasket() + { + + 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, 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", 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); + + 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 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 @@ + + + +