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());
+ }
+
}