diff --git a/class_diagram.png b/class_diagram.png new file mode 100644 index 000000000..cd5c7fb1e Binary files /dev/null and b/class_diagram.png differ diff --git a/class_diagram_extension.png b/class_diagram_extension.png new file mode 100644 index 000000000..3dbca706b Binary files /dev/null and b/class_diagram_extension.png differ diff --git a/domain-model-extension.md b/domain-model-extension.md new file mode 100644 index 000000000..552a59867 --- /dev/null +++ b/domain-model-extension.md @@ -0,0 +1,103 @@ +# Bob's Bagels -- Domain model + +## Class: Item + +### Member variables + +- sku: String +- price: double +- name: String +- variant: String +- size: int (e.g., fillings maybe don't take up capacity) + +### Methods + +Accessors for all fields. + +## Class: Basket + +### Member variables + +- capacity: static int +- items: List + +### Methods + +| Method | Scenario | Result | +|----------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------| +| addItem(name: String, variant: String): boolean | Basket is full | Return false. Print error message, don't add item to basket | +| addItem(item: Item): boolean | Basket is not full | Return true. Add item to basket. | +| | One or more fields are invalid (shop doesn't stock) | Return false. Print error message. | +| removeItem(name: String, variant: String): boolean | Item is not found in basket | Return false. Print error message. | +| | Item is found in basket | Return true. Remove item from basket. | +| setCapacity(newCapacity: int): static void | newCapacity is valid (> 0) | Update the basket capacity. | +| | newCapacity is not valid (<= 0) | Don't change the basket capacity. | +| getCapacity(): int | | | +| getTotalCost(): double | | Calculate and return the cost of items in basket. | + +## Class: ShopHandler + +This is the public interface that a customer or manager interacts with the Bob's Bagels system through. + +### Member variables + +- basket: Basket +- ~~stockedItems: `List>()`~~ +- - stockedItems: `List` (copy existing items when adding new items) + +### Methods + +| Method | Scenario | Result | +|--------------------------------------------------------------------------|----------|-------------------------------------------------------------------| +| placeOrder(): void | | Guide the customer through selecting items to order (interactive) | +| showItems(): String | | Return String of bagels and coffee w/ prices | +| showFillings(): String | | Return String of fillings w/ prices | +| orderBagel(): boolean | | Add a bagel with optional filling to basket if it's not full. | +| orderCoffee(): boolean | | Add coffee with optional bagel for a discount if it's not full. | +| setBasketCapacity(): void | | Update the basket capacity for all baskets. | +| calculateDiscounts(): double | | Apply the discounts and return savings. | +| showReceiptWithDiscounts(): String | | Return String with receipt, including discounts. | +| show/Fillings/Bagels/Coffees(): String | | Return String showing possible orders and prices | +| Some helper functions that I don't think are important enough to mention | | | + +## Class: Coffee + +Inherits from: Item. + +### Member variables: +- discountBagel: Bagel + +### Methods + +| Method | Scenario | Result | +|---------------------------|------------------------------------|-----------------------------------------------| +| getSize(): int | | Returns size, includes optional discountBagel | | | +| getPrice(): double | | includes optional discountBagel | +| getDiscountBagel(): Bagel | There is a bagel/there is no bagel | Returns the bagel/returns null | + +## Class: Bagel + +Inherits from: Item + +### Member variables: + +- filling: Item + +### Methods + +| Method | Scenario | Result | +|--------------------|--------------------------------------|----------------------------------| +| getSize(): int | | Returns size | +| getPrice(): double | | includes optional filling | +| getFilling(): Item | There is filling/there is no filling | Returns the filling/returns null | + +# Class: Main + +Run interactive interface to interactively place an order. (Not up to date with extensions.) + +# User stories (extension) + +- As a customer, I want to be able to have a bagel with my coffee for a discount. +- As a customer, I want multi-priced discounts to apply at checkout. +- As a customer, I want to get a receipt when paying. +- As a concerned customer, I want to know how much I saved using discounts. \ No newline at end of file diff --git a/domain-model.md b/domain-model.md new file mode 100644 index 000000000..96bc62182 --- /dev/null +++ b/domain-model.md @@ -0,0 +1,62 @@ +# Bob's Bagels -- Domain model + +## Class: Item + +### Member variables + +- sku: String +- price: double +- name: String +- variant: String +- size: int (e.g., fillings maybe don't take up capacity) + +### Methods + +Accessors for all fields. + +## Class: Basket + +### Member variables + +- capacity: static int +- items: List + +### Methods + +| Method | Scenario | Result | +|----------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------| +| addItem(name: String, variant: String): boolean | Basket is full | Return false. Print error message, don't add item to basket | +| addItem(item: Item): boolean | Basket is not full | Return true. Add item to basket. | +| | One or more fields are invalid (shop doesn't stock) | Return false. Print error message. | +| removeItem(name: String, variant: String): boolean | Item is not found in basket | Return false. Print error message. | +| | Item is found in basket | Return true. Remove item from basket. | +| setCapacity(newCapacity: int): static void | newCapacity is valid (> 0) | Update the basket capacity. | +| | newCapacity is not valid (<= 0) | Don't change the basket capacity. | +| getCapacity(): int | | | +| getTotalCost(): double | | Calculate and return the cost of items in basket. | + +## Class: ShopHandler + +This is the public interface that a customer or manager interacts with the Bob's Bagels system through. + +### Member variables + +- basket: Basket +- ~~stockedItems: `List>()`~~ +- - stockedItems: `List` (copy existing items when adding new items) + +### Methods + +| Method | Scenario | Result | +|-------------------------------|----------|-----------------------------------------------------------------------| +| placeOrder(): void (boolean?) | | Guide the customer through selecting items to order. | +| showItems(): String | | Return String of bagels and coffee w/ prices | +| showFillings(): String | | Return String of fillings w/ prices | +| orderBagel(): void | | Let customer choose a bagel. Then, let customer add optional filling. | +| orderCoffee(): void | | Let customer choose a variant. | +| setBasketCapacity(): void | | Update the basket capacity for all baskets. | +| Some private helper functions | | | + +# Class: Main + +Run interactive interface. \ No newline at end of file diff --git a/src/main/java/com/booleanuk/core/Basket.java b/src/main/java/com/booleanuk/core/Basket.java new file mode 100644 index 000000000..8f0f36541 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Basket.java @@ -0,0 +1,73 @@ +package com.booleanuk.core; + +import java.util.ArrayList; +import java.util.List; + +public class Basket { + private static int capacity = 5; + private List items = new ArrayList<>(); + + public boolean addItem(String sku, double price, String name, String variant, int size) { + Item item = new Item(sku, price, name, variant, size); + return addItem(item); + } + + public boolean addItem(Item item) { + if (canFitItem(item)) { + items.add(item); + return true; + } + return false; + } + + public boolean canFitItem(Item item) { + if (items.size() + item.getSize() > capacity) { + System.out.println("You cannot fit more items in your basket."); + return false; + } + return true; + } + + public boolean addItem(String sku, double price, String name, String variant) { + // Shorthand, assuming size=1 + return addItem(sku, price, name, variant, 1); + } + + + public boolean removeItem(String sku) { + int indexToRemove = 0; + boolean foundItem = false; + for (int i=0; i getItems() { + return items; + } + + public static void setCapacity(int newCapacity) { + capacity = newCapacity; + } + + public static int getCapacity() { + return capacity; + } +} diff --git a/src/main/java/com/booleanuk/core/Item.java b/src/main/java/com/booleanuk/core/Item.java new file mode 100644 index 000000000..b72131de5 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Item.java @@ -0,0 +1,41 @@ +package com.booleanuk.core; + +public class Item { + private String sku; + private double price; + private String name; + private String variant; + private int size; + + public Item(String sku, double price, String name, String variant, int size) { + this.sku = sku; + this.price = price; + this.name = name; + this.variant = variant; + this.size = size; + } + + public Item(String sku, double price, String name, String variant) { + this(sku, price, name, variant, 1); + } + + public String getSku() { + return sku; + } + + public double getPrice() { + return price; + } + + public String getName() { + return name; + } + + public String getVariant() { + return variant; + } + + public int getSize() { + return size; + } +} diff --git a/src/main/java/com/booleanuk/core/Main.java b/src/main/java/com/booleanuk/core/Main.java new file mode 100644 index 000000000..e3b0eb665 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Main.java @@ -0,0 +1,8 @@ +package com.booleanuk.core; + +public class Main { + public static void main(String[] args) { + ShopHandler sh = new ShopHandler(); + sh.placeOrder(); + } +} diff --git a/src/main/java/com/booleanuk/core/ShopHandler.java b/src/main/java/com/booleanuk/core/ShopHandler.java new file mode 100644 index 000000000..f38608e38 --- /dev/null +++ b/src/main/java/com/booleanuk/core/ShopHandler.java @@ -0,0 +1,240 @@ +package com.booleanuk.core; + +import com.booleanuk.extension.Bagel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class ShopHandler { + private static final List stock = new ArrayList<>(Arrays.asList( + new Item("BGLO", 0.49, "Bagel", "Onion"), + new Item("BGLP", 0.39, "Bagel", "Plain"), + new Item("BGLE", 0.49, "Bagel", "Everything"), + new Item("BGLS", 0.49, "Bagel", "Sesame"), + new Item("COFB", 0.99, "Coffee", "Black"), + new Item("COFW", 1.19, "Coffee", "White"), + new Item("COFC", 1.29, "Coffee", "Cappuccino"), + new Item("COFL", 1.29, "Coffee", "Latte"), + new Item("FILB", 0.12, "Filling", "Bacon"), + new Item("FILE", 0.12, "Filling", "Egg"), + new Item("FILC", 0.12, "Filling", "Cheese"), + new Item("FILX", 0.12, "Filling", "Cream Cheese"), + new Item("FILS", 0.12, "Filling", "Smoked Salmon"), + new Item("FILH", 0.12, "Filling", "Ham") + )); + private Scanner scanner; + private Basket basket; + + public ShopHandler() { + this.scanner = new Scanner(System.in); + this.basket = new Basket(); + } + + public void placeOrder() { + while (true) { + String in; + do { + System.out.println("bagel/coffee/remove/pay"); + in = scanner.next(); + } while (!"bagel coffee remove pay".contains(in)); + switch (in) { + case "bagel": + orderBagel(); + break; + case "coffee": + orderCoffee(); + break; + case "remove": + removeItem(); + break; + case "pay": + System.out.println("Print receipt tbd. total cost " + basket.getTotalCost()); + return; + } + } + } + + public void removeItem() { + if (basket.getItems().isEmpty()) { + System.out.println("No items in basket."); + return; + } + System.out.println("Enter number of item to remove (0 to cancel):"); + System.out.println(showBasket()); + int in; + do { + in = scanner.nextInt(); + } while (in < 0 || in > basket.getItems().size()); + if (in == 0) return; + basket.removeItem(basket.getItems().get(in-1).getSku()); + } + + public String showBasket() { + StringBuilder sb = new StringBuilder(); + for (Item item : basket.getItems()) { + sb.append(item.getName()).append(", ").append(item.getVariant()).append(", ").append(item.getPrice()).append("\n"); + } + return sb.toString(); + } + + public String showItems() { + // Maybe this method isn't useful, may remove + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (!item.getName().equals("Filling")) { + sb.append(item.getName()).append("\t").append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public String showFillings() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Filling")) { + sb.append(item.getVariant()).append(" (").append(item.getPrice()).append(")").append("\n"); + } + } + return sb.toString(); + } + + public String showBagels() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Bagel")) { + sb.append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public String showCoffees() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Coffee")) { + sb.append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public void orderBagel() { + System.out.println("Select bagel variant:"); + System.out.println(showBagels()); + String in = ""; + do { + in = this.scanner.next(); + } while (!isValidBagel(in)); + boolean success = basket.addItem(bagelFromVariant(in)); + if (success) { + System.out.println("Add filling ([filling], no)? Available fillings:"); + System.out.println(showFillings()); + do { + in = this.scanner.next(); + } while(!isValidFilling(in) && !in.equals("no")); + if (!in.equals("no")) { + basket.addItem(fillingFromVariant(in)); + } + } else { + System.out.println("Not able to add bagel to basket."); + } + } + + public boolean orderBagel(String variant, String fillingVar) { + if (!isValidFilling(fillingVar) || !isValidBagel(variant)) { + return false; + } + Item bagel = bagelFromVariant(variant); + Item filling = fillingFromVariant(fillingVar); + return basket.addItem(bagel) && basket.addItem(filling); + } + + public boolean orderBagel(String variant) { + if (!isValidBagel(variant)) { + return false; + } + return basket.addItem(bagelFromVariant(variant)); + } + + public void orderCoffee() { + System.out.println("Select coffee variant:"); + System.out.println(showCoffees()); + String in = ""; + do { + in = this.scanner.next(); + } while (!isValidCoffee(in)); + boolean success = basket.addItem(coffeeFromVariant(in)); + if (success) { + System.out.println("Coffee added to basket."); + } else { + System.out.println("Not able to add coffee to basket."); + } + } + + public boolean orderCoffee(String variant) { + if (!isValidCoffee(variant)) { + return false; + } + return basket.addItem(coffeeFromVariant(variant)); + } + + private Item bagelFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Item(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private Item coffeeFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Item(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private Item fillingFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Item(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private static boolean isValidBagel(String variant) { + for (Item item: stock) { + if (item.getName().equals("Bagel") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + private static boolean isValidCoffee(String variant) { + for (Item item: stock) { + if (item.getName().equals("Coffee") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + private static boolean isValidFilling(String variant) { + for (Item item: stock) { + if (item.getName().equals("Filling") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + public static List getStock() { + return stock; + } +} diff --git a/src/main/java/com/booleanuk/extension/Bagel.java b/src/main/java/com/booleanuk/extension/Bagel.java new file mode 100644 index 000000000..716896f72 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Bagel.java @@ -0,0 +1,26 @@ +package com.booleanuk.extension; + +public class Bagel extends Item { + private Item filling = null; + public Bagel(String sku, double price, String name, String variant, int size) { + super(sku, price, name, variant, size); + } + + public Bagel(String sku, double price, String name, String variant) { + super(sku, price, name, variant); + } + + public void setFilling(Item filling) { + this.filling = filling; + } + + public Item getFilling() { + return this.filling; + } + + @Override + public double getPrice() { + if (filling == null) return super.getPrice(); + return super.getPrice() + filling.getPrice(); + } +} diff --git a/src/main/java/com/booleanuk/extension/Basket.java b/src/main/java/com/booleanuk/extension/Basket.java new file mode 100644 index 000000000..949620fcc --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Basket.java @@ -0,0 +1,73 @@ +package com.booleanuk.extension; + +import java.util.ArrayList; +import java.util.List; + +public class Basket { + private static int capacity = 10; + private List items = new ArrayList<>(); + + public boolean addItem(String sku, double price, String name, String variant, int size) { + Item item = new Item(sku, price, name, variant, size); + return addItem(item); + } + + public boolean addItem(Item item) { + if (canFitItem(item)) { + items.add(item); + return true; + } + return false; + } + + public boolean canFitItem(Item item) { + if (items.size() + item.getSize() > capacity) { + System.out.println("You cannot fit more items in your basket."); + return false; + } + return true; + } + + public boolean addItem(String sku, double price, String name, String variant) { + // Shorthand, assuming size=1 + return addItem(sku, price, name, variant, 1); + } + + + public boolean removeItem(String sku) { + int indexToRemove = 0; + boolean foundItem = false; + for (int i=0; i getItems() { + return items; + } + + public static void setCapacity(int newCapacity) { + capacity = newCapacity; + } + + public static int getCapacity() { + return capacity; + } +} diff --git a/src/main/java/com/booleanuk/extension/Coffee.java b/src/main/java/com/booleanuk/extension/Coffee.java new file mode 100644 index 000000000..15bec36af --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Coffee.java @@ -0,0 +1,35 @@ +package com.booleanuk.extension; + +public class Coffee extends Item { + private Bagel discountBagel = null; + + public Coffee(String sku, double price, String name, String variant, int size) { + super(sku, price, name, variant, size); + } + + public Coffee(String sku, double price, String name, String variant) { + super(sku, price, name, variant); + } + + public Bagel getDiscountBagel() { + return discountBagel; + } + + public void setDiscountBagel(Bagel discountBagel) { + this.discountBagel = discountBagel; + } + + @Override + public int getSize() { + if (discountBagel == null) { + return super.getSize(); + } + return super.getSize() + discountBagel.getSize(); + } + + @Override + public double getPrice() { + if (discountBagel == null) return super.getPrice(); + return super.getPrice() + discountBagel.getPrice(); + } +} diff --git a/src/main/java/com/booleanuk/extension/Item.java b/src/main/java/com/booleanuk/extension/Item.java new file mode 100644 index 000000000..9bc14ede9 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Item.java @@ -0,0 +1,41 @@ +package com.booleanuk.extension; + +public class Item { + private String sku; + private double price; + private String name; + private String variant; + private int size; + + public Item(String sku, double price, String name, String variant, int size) { + this.sku = sku; + this.price = price; + this.name = name; + this.variant = variant; + this.size = size; + } + + public Item(String sku, double price, String name, String variant) { + this(sku, price, name, variant, 1); + } + + public String getSku() { + return sku; + } + + public double getPrice() { + return price; + } + + public String getName() { + return name; + } + + public String getVariant() { + return variant; + } + + public int getSize() { + return size; + } +} diff --git a/src/main/java/com/booleanuk/extension/Main.java b/src/main/java/com/booleanuk/extension/Main.java new file mode 100644 index 000000000..342b4f6f7 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Main.java @@ -0,0 +1,10 @@ +package com.booleanuk.extension; + +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + ShopHandler sh = new ShopHandler(new Scanner(System.in)); + sh.placeOrder(); + } +} diff --git a/src/main/java/com/booleanuk/extension/ShopHandler.java b/src/main/java/com/booleanuk/extension/ShopHandler.java new file mode 100644 index 000000000..14c4685f2 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/ShopHandler.java @@ -0,0 +1,389 @@ +package com.booleanuk.extension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class ShopHandler { + private static final List stock = new ArrayList<>(Arrays.asList( + new Bagel("BGLO", 0.49, "Bagel", "Onion"), + new Bagel("BGLP", 0.39, "Bagel", "Plain"), + new Bagel("BGLE", 0.49, "Bagel", "Everything"), + new Bagel("BGLS", 0.49, "Bagel", "Sesame"), + new Coffee("COFB", 0.99, "Coffee", "Black"), + new Coffee("COFW", 1.19, "Coffee", "White"), + new Coffee("COFC", 1.29, "Coffee", "Cappuccino"), + new Coffee("COFL", 1.29, "Coffee", "Latte"), + new Item("FILB", 0.12, "Filling", "Bacon"), + new Item("FILE", 0.12, "Filling", "Egg"), + new Item("FILC", 0.12, "Filling", "Cheese"), + new Item("FILX", 0.12, "Filling", "Cream Cheese"), + new Item("FILS", 0.12, "Filling", "Smoked Salmon"), + new Item("FILH", 0.12, "Filling", "Ham") + )); + + private static final double SIX_BAGEL_DISCOUNT = 2.49; + private static final double DOZEN_BAGEL_DISCOUNT = 3.99; + private static final double COFFEE_DISCOUNT = 1.25; + private Scanner scanner; + private Basket basket; + + public ShopHandler(Scanner in) { + this.scanner = in; + this.basket = new Basket(); + } + + public ShopHandler() { + this(new Scanner(System.in)); + } + + public void placeOrder() { + while (true) { + String in; + do { + System.out.println("bagel/coffee/remove/pay"); + in = scanner.next(); + } while (!"bagel coffee remove pay".contains(in)); + switch (in) { + case "bagel": + orderBagel(); + break; + case "coffee": + orderCoffee(); + break; + case "remove": + removeItem(); + break; + case "pay": + System.out.println("Print receipt tbd. total cost " + basket.getTotalCost() + " - " + calculateDiscounts() + " = " + (basket.getTotalCost() - calculateDiscounts())); + return; + } + } + } + + public double calculateDiscounts() { + double totalDiscount = 0; + for (Item item : basket.getItems()) { + if (item.getName().equals("Coffee")) { + Coffee coffee = (Coffee) item; // not nice! + if (coffee.getDiscountBagel() != null) + totalDiscount += coffee.getPrice() - COFFEE_DISCOUNT; + } + } + for (Item item : stock) { + if (item.getName().equals("Bagel")) { + String sku = item.getSku(); + int n = basket.getItems().stream().filter(i -> i.getSku().equals(sku)).toList().size(); + double discount = ((int) (n/12)) * item.getPrice() * 12 - ((int) n/12) * DOZEN_BAGEL_DISCOUNT; + int bagelsLeft = n % 12; + discount += ((int) (bagelsLeft/6)) * item.getPrice() * 6 - ((int) bagelsLeft/6) * SIX_BAGEL_DISCOUNT; + totalDiscount += discount; + } + } + return totalDiscount; + } + + public String showReceiptWithDiscounts() { + double totalDiscount = 0; + StringBuilder sb = new StringBuilder(); + + sb.append("Bob's Bagels\n--------\n"); + + // Recursively collect all fillings + List fillings = new ArrayList<>(); + for (Item item : basket.getItems()) { + if (item.getName().equals("Coffee")) { + Bagel b = ((Coffee) item).getDiscountBagel(); + if (b != null) { + Item f = b.getFilling(); + if (f != null) { + fillings.add(f); + } + } + } else if (item.getName().equals("Bagel")) { + Item f = ((Bagel) item).getFilling(); + if (f != null) { + fillings.add(f); + } + } + } + + for (Item item : stock) { + String sku = item.getSku(); + + if (item.getName().equals("Filling")) { + int n = fillings.stream().filter(i -> i.getSku().equals(sku)).toList().size(); + if (n > 0) { + sb.append(n).append(" ").append(item.getName()).append(" ").append(item.getVariant()).append(" ").append(String.format("%.2f", item.getPrice() * n)).append("\n"); + } + } + + if (item.getName().equals("Coffee")) { + double discount = 0; + double price = 0; + List coffees = basket.getItems().stream().filter(i -> i.getSku().equals(sku)).toList(); + for (Item coffee : coffees) { // not nice! + if (((Coffee) coffee).getDiscountBagel() != null) { + discount += coffee.getPrice() - COFFEE_DISCOUNT; + price += COFFEE_DISCOUNT; + totalDiscount += discount; + } else { + price += coffee.getPrice(); + } + } + if (!coffees.isEmpty()) { + sb.append(coffees.size()).append(" ").append(item.getName()).append(" ").append(item.getVariant()).append(" ").append(String.format("%.2f", price)).append("\n"); + if (discount > 0) { + sb.append("(-").append(String.format("%.2f", discount)).append(")").append("\n"); + } + } + } else if (item.getName().equals("Bagel")) { + int n = basket.getItems().stream().filter(i -> i.getSku().equals(sku)).toList().size(); + double discount = ((int) (n/12)) * item.getPrice() * 12 - ((int) n/12) * DOZEN_BAGEL_DISCOUNT; + int bagelsLeft = n % 12; + discount += ((int) (bagelsLeft/6)) * item.getPrice() * 6 - ((int) bagelsLeft/6) * SIX_BAGEL_DISCOUNT; + totalDiscount += discount; + if (n > 0) { + sb.append(n).append(" ").append(item.getName()).append(" ").append(item.getVariant()).append(" ").append(String.format("%.2f", item.getPrice() * n - discount)).append("\n"); + if (discount > 0) { + sb.append("(-").append(String.format("%.2f", discount)).append(")").append("\n"); + } + } + } + } + sb.append("--------\n") + .append("Total: ").append(String.format("%.2f", getCostAfterDiscounts())) + .append("\nSavings: ").append(String.format("%.2f", getCostBeforeDiscounts() - getCostAfterDiscounts())); + + return sb.toString(); + } + + public void removeItem() { + if (basket.getItems().isEmpty()) { + System.out.println("No items in basket."); + return; + } + System.out.println("Enter number of item to remove (0 to cancel):"); + System.out.println(showBasket()); + int in; + do { + in = scanner.nextInt(); + } while (in < 0 || in > basket.getItems().size()); + if (in == 0) return; + basket.removeItem(basket.getItems().get(in-1).getSku()); + } + + public String showBasket() { + StringBuilder sb = new StringBuilder(); + for (Item item : basket.getItems()) { + sb.append(item.getName()).append(", ").append(item.getVariant()).append(", ").append(item.getPrice()).append("\n"); + } + return sb.toString(); + } + + public String showItems() { + // Maybe this method isn't useful, may remove + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (!item.getName().equals("Filling")) { + sb.append(item.getName()).append("\t").append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public String showFillings() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Filling")) { + sb.append(item.getVariant()).append(" (").append(item.getPrice()).append(")").append("\n"); + } + } + return sb.toString(); + } + + public String showBagels() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Bagel")) { + sb.append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public String showCoffees() { + StringBuilder sb = new StringBuilder(); + for (Item item : stock) { + if (item.getName().equals("Coffee")) { + sb.append(item.getVariant()).append(item.getVariant().length() > 6 ? "\t" : "\t\t").append(item.getPrice()).append("\n"); + } + } + return sb.toString(); + } + + public void orderBagel() { + Bagel b = selectBagel(); + if (basket.addItem(b)) { + System.out.println("Bagel added to basket."); + } else { + System.out.println("Not able to add bagel to basket."); + } + } + + public boolean orderBagel(String variant, String filling) { + if (!isValidFilling(filling) || !isValidBagel(variant)) { + return false; + } + Bagel bagel = bagelFromVariant(variant); + bagel.setFilling(fillingFromVariant(filling)); + return basket.addItem(bagel); + } + + public boolean orderBagel(String variant) { + if (!isValidBagel(variant)) { + return false; + } + return basket.addItem(bagelFromVariant(variant)); + } + + public Bagel selectBagel() { + System.out.println("Select bagel variant:"); + System.out.println(showBagels()); + String in; + do { + in = this.scanner.next(); + } while (!isValidBagel(in)); + Bagel bagel = bagelFromVariant(in); + System.out.println("Add filling ([filling], no)? Available fillings:"); + System.out.println(showFillings()); + do { + in = this.scanner.next(); + } while(!isValidFilling(in) && !in.equals("no")); + if (!in.equals("no")) { + bagel.setFilling(fillingFromVariant(in)); + } + return bagel; + } + + public void orderCoffee() { + System.out.println("Select coffee variant:"); + System.out.println(showCoffees()); + String in; + do { + in = this.scanner.next(); + } while (!isValidCoffee(in)); + Coffee coffee = coffeeFromVariant(in); + System.out.println("Add bagel for a discount?"); + do { + in = this.scanner.next(); + } while (!"yes no".contains(in)); + + if (in.equals("yes")) { + Bagel b = selectBagel(); + coffee.setDiscountBagel(b); + } + + boolean success = basket.addItem(coffee); + if (success) { + System.out.println("Coffee added to basket."); + } else { + System.out.println("Not able to add coffee to basket."); + } + } + + public boolean orderCoffee(String variant) { + if (!isValidCoffee(variant)) { + return false; + } + return basket.addItem(coffeeFromVariant(variant)); + } + + public boolean orderCoffee(String coffeeVariant, String bagelVariant, String fillingVariant) { + if (!isValidCoffee(coffeeVariant) || !isValidBagel(bagelVariant) || !isValidFilling(fillingVariant)) { + return false; + } + Coffee coffee = coffeeFromVariant(coffeeVariant); + Bagel bagel = bagelFromVariant(bagelVariant); + bagel.setFilling(fillingFromVariant(fillingVariant)); + coffee.setDiscountBagel(bagel); + return basket.addItem(coffee); + } + + public boolean orderCoffee(String coffeeVariant, String bagelVariant) { + if (!isValidCoffee(coffeeVariant) || !isValidBagel(bagelVariant)) { + return false; + } + Coffee coffee = coffeeFromVariant(coffeeVariant); + Bagel bagel = bagelFromVariant(bagelVariant); + coffee.setDiscountBagel(bagel); + return basket.addItem(coffee); + } + + private Bagel bagelFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Bagel(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private Coffee coffeeFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Coffee(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private Item fillingFromVariant(String variant) { + for (Item item : stock) { + if (item.getVariant().equals(variant)) { + return new Item(item.getSku(), item.getPrice(), item.getName(), item.getVariant()); + } + } + return null; + } + + private static boolean isValidBagel(String variant) { + for (Item item: stock) { + if (item.getName().equals("Bagel") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + private static boolean isValidCoffee(String variant) { + for (Item item: stock) { + if (item.getName().equals("Coffee") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + private static boolean isValidFilling(String variant) { + for (Item item: stock) { + if (item.getName().equals("Filling") && item.getVariant().equals(variant)) { + return true; + } + } + return false; + } + + public static List getStock() { + return stock; + } + + public double getCostBeforeDiscounts() { + return basket.getTotalCost(); + } + + public double getCostAfterDiscounts() { + return basket.getTotalCost() - calculateDiscounts(); + } +} diff --git a/src/test/java/com/booleanuk/core/BasketTest.java b/src/test/java/com/booleanuk/core/BasketTest.java new file mode 100644 index 000000000..d7a60bffd --- /dev/null +++ b/src/test/java/com/booleanuk/core/BasketTest.java @@ -0,0 +1,65 @@ +package com.booleanuk.core; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +public class BasketTest { + + @Test + public void canAddBagel() { + Basket basket = new Basket(); + assertTrue(basket.addItem("BGLO", 0.49, "Bagel", "Onion")); + } + + @Test + public void addingBagelIncreasesBasketSize() { + Basket basket = new Basket(); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + assertEquals(1, basket.getItems().size()); + } + + @Test + public void canAndDoesRemoveBagel() { + Basket basket = new Basket(); + assertTrue(basket.addItem("BGLO", 0.49, "Bagel", "Onion")); + assertTrue(basket.removeItem("BGLO")); + assertTrue(basket.getItems().isEmpty()); + } + + @Test + public void removesCorrectBagel() { + Basket basket = new Basket(); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + basket.addItem("BGLP", 0.39, "Bagel", "Plain"); + basket.removeItem("BGLP"); + assertNotEquals("BGLP", basket.getItems().getFirst().getSku()); + } + + @Test + public void cannotAddPastCapacity() { + int originalCapacity = Basket.getCapacity(); + Basket.setCapacity(2); + Basket basket = new Basket(); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + assertFalse(basket.addItem("BGLO", 0.49, "Bagel", "Onion")); + + // Reset the capacity, since it's static + Basket.setCapacity(originalCapacity); + } + + @Test + public void costOfEmptyBasketShouldBeZero() { + Basket basket = new Basket(); + assertEquals(0, basket.getTotalCost()); + } + + @Test + public void totalCostShouldBeSomething() { + Basket basket = new Basket(); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + basket.addItem("BGLO", 0.49, "Bagel", "Onion"); + assertEquals(1.47, basket.getTotalCost()); + } +} diff --git a/src/test/java/com/booleanuk/core/ItemTest.java b/src/test/java/com/booleanuk/core/ItemTest.java new file mode 100644 index 000000000..a6d09b14c --- /dev/null +++ b/src/test/java/com/booleanuk/core/ItemTest.java @@ -0,0 +1,7 @@ +package com.booleanuk.core; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +public class ItemTest { +} diff --git a/src/test/java/com/booleanuk/core/ShopHandlerTest.java b/src/test/java/com/booleanuk/core/ShopHandlerTest.java new file mode 100644 index 000000000..dd00d7262 --- /dev/null +++ b/src/test/java/com/booleanuk/core/ShopHandlerTest.java @@ -0,0 +1,58 @@ +package com.booleanuk.core; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.*; + +public class ShopHandlerTest { + @Test + public void showItemsShowsAllItemsThatAreNotFillings() { + ShopHandler sh = new ShopHandler(); + String list = sh.showItems(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (!item.getName().equals("Filling")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getName() + "\t" + item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } + + @Test + public void showFillingsWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showFillings(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Filling")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant())); + } + } + } + + @Test + public void showBagelsWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showBagels(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Bagel")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } + + @Test + public void showCoffeesWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showCoffees(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Coffee")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } +} diff --git a/src/test/java/com/booleanuk/extension/BagelTest.java b/src/test/java/com/booleanuk/extension/BagelTest.java new file mode 100644 index 000000000..f2a7af7b0 --- /dev/null +++ b/src/test/java/com/booleanuk/extension/BagelTest.java @@ -0,0 +1,13 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class BagelTest { + @Test + public void testCountsFilling() { + Bagel b = new Bagel("A", 11, "Name", "Onion"); + b.setFilling(new Item("B" , 5, "Name", "Variant")); + assertEquals(16, b.getPrice()); + } +} diff --git a/src/test/java/com/booleanuk/extension/CoffeeTest.java b/src/test/java/com/booleanuk/extension/CoffeeTest.java new file mode 100644 index 000000000..9499594e6 --- /dev/null +++ b/src/test/java/com/booleanuk/extension/CoffeeTest.java @@ -0,0 +1,16 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CoffeeTest { + @Test + public void testCountsDiscountBagel() { + Coffee c = new Coffee("A", 11, "Name", "Onion"); + Bagel b = new Bagel("A", 11, "Name", "Onion"); + b.setFilling(new Item("B" , 5, "Name", "Variant")); + c.setDiscountBagel(b); + assertEquals(27, c.getPrice()); + } +} diff --git a/src/test/java/com/booleanuk/extension/ShopHandlerTest.java b/src/test/java/com/booleanuk/extension/ShopHandlerTest.java new file mode 100644 index 000000000..be304408e --- /dev/null +++ b/src/test/java/com/booleanuk/extension/ShopHandlerTest.java @@ -0,0 +1,182 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ShopHandlerTest { + double delta = 0.0001; + @Test + public void showItemsShowsAllItemsThatAreNotFillings() { + ShopHandler sh = new ShopHandler(); + String list = sh.showItems(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (!item.getName().equals("Filling")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getName() + "\t" + item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } + + @Test + public void showFillingsWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showFillings(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Filling")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant())); + } + } + } + + @Test + public void showBagelsWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showBagels(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Bagel")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } + + @Test + public void showCoffeesWorks() { + ShopHandler sh = new ShopHandler(); + String list = sh.showCoffees(); +// System.out.println(list); + for (Item item : ShopHandler.getStock()) { + if (item.getName().equals("Coffee")) { + // if item.name & price is in list, good + assertTrue(list.contains(item.getVariant() + (item.getVariant().length() > 6 ? "\t" : "\t\t") + item.getPrice())); + } + } + } + + @Test + public void sixpackBagelsDiscount() { + double delta = 0.0001; + ShopHandler sh = new ShopHandler(); + for (int i=0; i<7; i++) { + sh.orderBagel("Onion"); + } + assertEquals(0.45, sh.calculateDiscounts(), delta); + } + + @Test + public void dozenBagelsDiscount() { + double delta = 0.0001; + ShopHandler sh = new ShopHandler(); + int oldCapacity = Basket.getCapacity(); + Basket.setCapacity(20); + for (int i=0; i<13; i++) { + sh.orderBagel("Onion"); + } + assertEquals(1.89, sh.calculateDiscounts(), delta); + Basket.setCapacity(oldCapacity); + } + + @Test + public void dozenAndSixBagelsDiscount() { + ShopHandler sh = new ShopHandler(); + int oldCapacity = Basket.getCapacity(); + Basket.setCapacity(20); + for (int i=0; i<19; i++) { + sh.orderBagel("Onion"); + } + assertEquals(2.34, sh.calculateDiscounts(), delta); + Basket.setCapacity(oldCapacity); + } + + @Test + public void coffeeDiscount() { + ShopHandler sh = new ShopHandler(); + sh.orderCoffee("Black", "Onion"); + assertEquals(0.23, sh.calculateDiscounts(), delta); + } + + @Test + public void comboTest() { + ShopHandler sh = new ShopHandler(); + int oldCapacity = Basket.getCapacity(); + Basket.setCapacity(100); + sh.orderBagel("Onion"); + sh.orderBagel("Onion"); + for (int i=0; i<12; i++) { + sh.orderBagel("Plain"); + } + for (int i=0; i<6; i++) { + sh.orderBagel("Everything"); + } + for (int i=0; i<3; i++) { + sh.orderCoffee("Black"); + } + assertEquals(10.43, sh.getCostAfterDiscounts(), delta); + Basket.setCapacity(oldCapacity); + } + + @Test + public void nonemptyBasketPrintsNonemptyReceipt() { + ShopHandler sh = new ShopHandler(); + int oldCapacity = Basket.getCapacity(); + Basket.setCapacity(100); + sh.orderBagel("Onion"); + sh.orderBagel("Onion"); + for (int i=0; i<12; i++) { + sh.orderBagel("Plain"); + } + for (int i=0; i<6; i++) { + sh.orderBagel("Everything"); + } + for (int i=0; i<3; i++) { + sh.orderCoffee("Black"); + } + sh.orderCoffee("Cappuccino", "Plain"); + assertNotEquals("", sh.showReceiptWithDiscounts()); + Basket.setCapacity(oldCapacity); + } + + @Test + public void comboBasketPrintsNonemptyReceipt() { + ShopHandler sh = new ShopHandler(); + int oldCapacity = Basket.getCapacity(); + Basket.setCapacity(100); + sh.orderBagel("Onion", "Ham"); + sh.orderBagel("Onion", "Ham"); + for (int i=0; i<12; i++) { + sh.orderBagel("Plain", "Cheese"); + } + for (int i=0; i<6; i++) { + sh.orderBagel("Everything"); + } + for (int i=0; i<3; i++) { + sh.orderCoffee("Black"); + } + sh.orderCoffee("Cappuccino", "Plain"); +// System.out.println(sh.showReceiptWithDiscounts()); + assertEquals( + """ + Bob's Bagels + -------- + 2 Bagel Onion 0.98 + 12 Bagel Plain 3.99 + (-0.69) + 6 Bagel Everything 2.49 + (-0.45) + 3 Coffee Black 2.97 + 1 Coffee Cappuccino 1.25 + (-0.43) + 12 Filling Cheese 1.44 + 2 Filling Ham 0.24 + -------- + Total: 13.36 + Savings: 1.57""" + , sh.showReceiptWithDiscounts()); + Basket.setCapacity(oldCapacity); + } +}