From fbf60d7b9be48d602f2de608d2ee57755fba1b1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:33:17 +0000 Subject: [PATCH 1/3] Initial plan From 0b427a79fce1180ee5796bad0328d02773a82589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:37:16 +0000 Subject: [PATCH 2/3] Initial plan for discounted bundles feature Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com> --- java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 73ba4a7..c62567d 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -45,8 +45,8 @@ maven-compiler-plugin 3.8.0 - 22 - 22 + 17 + 17 From dc4924eb73eadeb9792413d5239dada54a49bde5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:40:57 +0000 Subject: [PATCH 3/3] Implement bundle discount feature with comprehensive tests Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com> --- .../dojo/supermarket/model/BundleOffer.java | 22 +++ .../dojo/supermarket/model/ShoppingCart.java | 36 +++++ .../supermarket/model/SpecialOfferType.java | 1 + .../java/dojo/supermarket/model/Teller.java | 7 + .../dojo/supermarket/model/BundleDemo.java | 29 ++++ .../supermarket/model/SupermarketTest.java | 146 ++++++++++++++++++ 6 files changed, 241 insertions(+) create mode 100644 java/src/main/java/dojo/supermarket/model/BundleOffer.java create mode 100644 java/src/test/java/dojo/supermarket/model/BundleDemo.java diff --git a/java/src/main/java/dojo/supermarket/model/BundleOffer.java b/java/src/main/java/dojo/supermarket/model/BundleOffer.java new file mode 100644 index 0000000..2241499 --- /dev/null +++ b/java/src/main/java/dojo/supermarket/model/BundleOffer.java @@ -0,0 +1,22 @@ +package dojo.supermarket.model; + +import java.util.List; +import java.util.Collections; + +public class BundleOffer { + private final List products; + private final double discountPercent; + + public BundleOffer(List products, double discountPercent) { + this.products = Collections.unmodifiableList(products); + this.discountPercent = discountPercent; + } + + public List getProducts() { + return products; + } + + public double getDiscountPercent() { + return discountPercent; + } +} \ No newline at end of file diff --git a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java index 9293086..5b2e95d 100644 --- a/java/src/main/java/dojo/supermarket/model/ShoppingCart.java +++ b/java/src/main/java/dojo/supermarket/model/ShoppingCart.java @@ -72,4 +72,40 @@ void handleOffers(Receipt receipt, Map offers, SupermarketCatalo } } } + + void handleBundleOffers(Receipt receipt, List bundleOffers, SupermarketCatalog catalog) { + for (BundleOffer bundleOffer : bundleOffers) { + // Calculate how many complete bundles can be made + int completeBundles = calculateCompleteBundles(bundleOffer.getProducts()); + + if (completeBundles > 0) { + // Calculate total price for one complete bundle + double bundlePrice = 0.0; + for (Product product : bundleOffer.getProducts()) { + bundlePrice += catalog.getUnitPrice(product); + } + + // Calculate discount for all complete bundles + double totalBundlePrice = bundlePrice * completeBundles; + double discountAmount = totalBundlePrice * bundleOffer.getDiscountPercent() / 100.0; + + // Create bundle discount + String description = "Bundle discount (" + bundleOffer.getDiscountPercent() + "% off)"; + Discount bundleDiscount = new Discount(bundleOffer.getProducts().get(0), description, -discountAmount); + receipt.addDiscount(bundleDiscount); + } + } + } + + private int calculateCompleteBundles(List bundleProducts) { + int minQuantity = Integer.MAX_VALUE; + + for (Product product : bundleProducts) { + double quantity = productQuantities.getOrDefault(product, 0.0); + int quantityAsInt = (int) quantity; + minQuantity = Math.min(minQuantity, quantityAsInt); + } + + return minQuantity == Integer.MAX_VALUE ? 0 : minQuantity; + } } diff --git a/java/src/main/java/dojo/supermarket/model/SpecialOfferType.java b/java/src/main/java/dojo/supermarket/model/SpecialOfferType.java index ffc4bf0..e2df7dd 100644 --- a/java/src/main/java/dojo/supermarket/model/SpecialOfferType.java +++ b/java/src/main/java/dojo/supermarket/model/SpecialOfferType.java @@ -5,4 +5,5 @@ public enum SpecialOfferType { TEN_PERCENT_DISCOUNT, TWO_FOR_AMOUNT, FIVE_FOR_AMOUNT, + BUNDLE, } diff --git a/java/src/main/java/dojo/supermarket/model/Teller.java b/java/src/main/java/dojo/supermarket/model/Teller.java index 72dc6e2..d57b335 100644 --- a/java/src/main/java/dojo/supermarket/model/Teller.java +++ b/java/src/main/java/dojo/supermarket/model/Teller.java @@ -3,11 +3,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.ArrayList; public class Teller { private final SupermarketCatalog catalog; private final Map offers = new HashMap<>(); + private final List bundleOffers = new ArrayList<>(); public Teller(SupermarketCatalog catalog) { this.catalog = catalog; @@ -17,6 +19,10 @@ public void addSpecialOffer(SpecialOfferType offerType, Product product, double offers.put(product, new Offer(offerType, product, argument)); } + public void addBundleOffer(List products, double discountPercent) { + bundleOffers.add(new BundleOffer(products, discountPercent)); + } + public Receipt checksOutArticlesFrom(ShoppingCart theCart) { Receipt receipt = new Receipt(); List productQuantities = theCart.getItems(); @@ -28,6 +34,7 @@ public Receipt checksOutArticlesFrom(ShoppingCart theCart) { receipt.addProduct(p, quantity, unitPrice, price); } theCart.handleOffers(receipt, offers, catalog); + theCart.handleBundleOffers(receipt, bundleOffers, catalog); return receipt; } diff --git a/java/src/test/java/dojo/supermarket/model/BundleDemo.java b/java/src/test/java/dojo/supermarket/model/BundleDemo.java new file mode 100644 index 0000000..3c1095f --- /dev/null +++ b/java/src/test/java/dojo/supermarket/model/BundleDemo.java @@ -0,0 +1,29 @@ +package dojo.supermarket.model; + +import dojo.supermarket.ReceiptPrinter; +import java.util.Arrays; +import java.util.List; + +public class BundleDemo { + public static void main(String[] args) { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 1.0); + cart.addItemQuantity(toothpaste, 1.0); + + Receipt receipt = teller.checksOutArticlesFrom(cart); + + String receiptText = new ReceiptPrinter().printReceipt(receipt); + System.out.println(receiptText); + System.out.println("Total Price: €" + String.format("%.2f", receipt.getTotalPrice())); + } +} \ No newline at end of file diff --git a/java/src/test/java/dojo/supermarket/model/SupermarketTest.java b/java/src/test/java/dojo/supermarket/model/SupermarketTest.java index 5a81d18..cf57bb0 100644 --- a/java/src/test/java/dojo/supermarket/model/SupermarketTest.java +++ b/java/src/test/java/dojo/supermarket/model/SupermarketTest.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.Test; import java.util.Collections; +import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -41,5 +43,149 @@ void tenPercentDiscount() { } + @Test + void bundleDiscountBasicCase() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 1.0); + cart.addItemQuantity(toothpaste, 1.0); + + // ACT + Receipt receipt = teller.checksOutArticlesFrom(cart); + + // ASSERT + // Total should be (0.99 + 1.79) - 10% discount = 2.78 - 0.278 = 2.502 + assertEquals(2.502, receipt.getTotalPrice(), 0.01); + assertEquals(1, receipt.getDiscounts().size()); + assertEquals(2, receipt.getItems().size()); + + Discount discount = receipt.getDiscounts().get(0); + assertEquals(-0.278, discount.getDiscountAmount(), 0.01); + assertEquals("Bundle discount (10.0% off)", discount.getDescription()); + } + + @Test + void bundleDiscountOnlyCompleteBundle() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 2.0); // 2 toothbrushes + cart.addItemQuantity(toothpaste, 1.0); // 1 toothpaste + + // ACT + Receipt receipt = teller.checksOutArticlesFrom(cart); + + // ASSERT + // Only 1 complete bundle (limited by toothpaste), so discount on 0.99 + 1.79 = 2.78 + // Total: (2 * 0.99 + 1 * 1.79) - 0.278 = 3.77 - 0.278 = 3.492 + assertEquals(3.492, receipt.getTotalPrice(), 0.01); + assertEquals(1, receipt.getDiscounts().size()); + + Discount discount = receipt.getDiscounts().get(0); + assertEquals(-0.278, discount.getDiscountAmount(), 0.01); + } + + @Test + void bundleDiscountMultipleBundles() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 2.0); + cart.addItemQuantity(toothpaste, 2.0); + + // ACT + Receipt receipt = teller.checksOutArticlesFrom(cart); + + // ASSERT + // 2 complete bundles, so discount on 2 * (0.99 + 1.79) = 2 * 2.78 = 5.56 + // Discount: 5.56 * 0.1 = 0.556 + // Total: 5.56 - 0.556 = 5.004 + assertEquals(5.004, receipt.getTotalPrice(), 0.01); + assertEquals(1, receipt.getDiscounts().size()); + + Discount discount = receipt.getDiscounts().get(0); + assertEquals(-0.556, discount.getDiscountAmount(), 0.01); + } + + @Test + void noBundleDiscountWhenMissingProduct() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 1.0); // Only toothbrush, no toothpaste + + // ACT + Receipt receipt = teller.checksOutArticlesFrom(cart); + + // ASSERT + // No bundle discount since toothpaste is missing + assertEquals(0.99, receipt.getTotalPrice(), 0.01); + assertEquals(Collections.emptyList(), receipt.getDiscounts()); + } + + @Test + void bundleDiscountWithRegularOffers() { + SupermarketCatalog catalog = new FakeCatalog(); + Product toothbrush = new Product("toothbrush", ProductUnit.EACH); + catalog.addProduct(toothbrush, 0.99); + Product toothpaste = new Product("toothpaste", ProductUnit.EACH); + catalog.addProduct(toothpaste, 1.79); + Product apples = new Product("apples", ProductUnit.KILO); + catalog.addProduct(apples, 1.99); + + Teller teller = new Teller(catalog); + List bundleProducts = Arrays.asList(toothbrush, toothpaste); + teller.addBundleOffer(bundleProducts, 10.0); + teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, apples, 20.0); + + ShoppingCart cart = new ShoppingCart(); + cart.addItemQuantity(toothbrush, 1.0); + cart.addItemQuantity(toothpaste, 1.0); + cart.addItemQuantity(apples, 1.0); + + // ACT + Receipt receipt = teller.checksOutArticlesFrom(cart); + + // ASSERT + // Bundle discount: (0.99 + 1.79) * 0.1 = 0.278 + // Apples discount: 1.99 * 0.2 = 0.398 + // Total: (0.99 + 1.79 + 1.99) - 0.278 - 0.398 = 4.77 - 0.676 = 4.094 + assertEquals(4.094, receipt.getTotalPrice(), 0.01); + assertEquals(2, receipt.getDiscounts().size()); + } + }