From f623ee56d5582fc308457f2a97aa2df565bb32a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 27 Aug 2025 09:57:00 +0000
Subject: [PATCH 1/3] Initial plan
From 95d24c53ab2ff9d8fcb744e635920dc8f27a2999 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 27 Aug 2025 10:00:31 +0000
Subject: [PATCH 2/3] Initial setup: Fix Java version compatibility for
building
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 060ed25d0592f4741c88e848414be01d9c7350c3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 27 Aug 2025 10:06:44 +0000
Subject: [PATCH 3/3] Implement HTML receipt feature with shared formatting
logic
Co-authored-by: nstubbe <20206435+nstubbe@users.noreply.github.com>
---
.../dojo/supermarket/BaseReceiptPrinter.java | 27 +++++
.../dojo/supermarket/HtmlReceiptDemo.java | 53 +++++++++
.../dojo/supermarket/HtmlReceiptPrinter.java | 82 ++++++++++++++
.../java/dojo/supermarket/ReceiptDemo.java | 54 +++++++++
.../java/dojo/supermarket/ReceiptPrinter.java | 34 ++++--
.../supermarket/HtmlReceiptPrinterTest.java | 105 ++++++++++++++++++
6 files changed, 345 insertions(+), 10 deletions(-)
create mode 100644 java/src/main/java/dojo/supermarket/BaseReceiptPrinter.java
create mode 100644 java/src/main/java/dojo/supermarket/HtmlReceiptDemo.java
create mode 100644 java/src/main/java/dojo/supermarket/HtmlReceiptPrinter.java
create mode 100644 java/src/main/java/dojo/supermarket/ReceiptDemo.java
rename java/src/{test => main}/java/dojo/supermarket/ReceiptPrinter.java (70%)
create mode 100644 java/src/test/java/dojo/supermarket/HtmlReceiptPrinterTest.java
diff --git a/java/src/main/java/dojo/supermarket/BaseReceiptPrinter.java b/java/src/main/java/dojo/supermarket/BaseReceiptPrinter.java
new file mode 100644
index 0000000..b44ba53
--- /dev/null
+++ b/java/src/main/java/dojo/supermarket/BaseReceiptPrinter.java
@@ -0,0 +1,27 @@
+package dojo.supermarket;
+
+import dojo.supermarket.model.*;
+
+import java.util.Locale;
+
+public abstract class BaseReceiptPrinter {
+
+ public abstract String printReceipt(Receipt receipt);
+
+ protected abstract String formatReceiptItem(ReceiptItem item);
+
+ protected abstract String formatDiscount(Discount discount);
+
+ protected abstract String formatTotal(Receipt receipt);
+
+ // Shared data formatting methods - keep these identical across implementations
+ protected static String presentPrice(double price) {
+ return String.format(Locale.UK, "%.2f", price);
+ }
+
+ protected static String presentQuantity(ReceiptItem item) {
+ return ProductUnit.EACH.equals(item.getProduct().getUnit())
+ ? String.format("%d", (int)item.getQuantity())
+ : String.format(Locale.UK, "%.3f", item.getQuantity());
+ }
+}
\ No newline at end of file
diff --git a/java/src/main/java/dojo/supermarket/HtmlReceiptDemo.java b/java/src/main/java/dojo/supermarket/HtmlReceiptDemo.java
new file mode 100644
index 0000000..b7e07ce
--- /dev/null
+++ b/java/src/main/java/dojo/supermarket/HtmlReceiptDemo.java
@@ -0,0 +1,53 @@
+package dojo.supermarket;
+
+import dojo.supermarket.model.*;
+
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class HtmlReceiptDemo {
+ public static void main(String[] args) throws IOException {
+ // Set up sample data with multiple items and discounts
+ SupermarketCatalog catalog = new ReceiptDemo.FakeCatalog();
+
+ Product apples = new Product("apples", ProductUnit.KILO);
+ Product toothbrush = new Product("toothbrush", ProductUnit.EACH);
+ Product toothpaste = new Product("toothpaste", ProductUnit.EACH);
+ Product cherryTomatoes = new Product("cherry tomatoes", ProductUnit.EACH);
+
+ catalog.addProduct(apples, 1.99);
+ catalog.addProduct(toothbrush, 0.99);
+ catalog.addProduct(toothpaste, 1.79);
+ catalog.addProduct(cherryTomatoes, 0.69);
+
+ Teller teller = new Teller(catalog);
+ teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0);
+ teller.addSpecialOffer(SpecialOfferType.TWO_FOR_AMOUNT, cherryTomatoes, 0.99);
+
+ ShoppingCart cart = new ShoppingCart();
+ cart.addItemQuantity(apples, 2.5);
+ cart.addItemQuantity(toothbrush, 3);
+ cart.addItemQuantity(toothpaste, 2);
+ cart.addItemQuantity(cherryTomatoes, 2); // Buy 2 for 0.99 deal
+
+ Receipt receipt = teller.checksOutArticlesFrom(cart);
+
+ // Generate HTML receipt
+ HtmlReceiptPrinter htmlPrinter = new HtmlReceiptPrinter();
+ String htmlReceipt = htmlPrinter.printReceipt(receipt);
+
+ // Write to file
+ try (FileWriter writer = new FileWriter("/tmp/sample_receipt.html")) {
+ writer.write(htmlReceipt);
+ System.out.println("HTML receipt written to /tmp/sample_receipt.html");
+ }
+
+ // Also show comparison
+ ReceiptPrinter textPrinter = new ReceiptPrinter();
+ System.out.println("\n=== TEXT RECEIPT ===");
+ System.out.println(textPrinter.printReceipt(receipt));
+
+ System.out.println("\n=== HTML RECEIPT ===");
+ System.out.println(htmlReceipt);
+ }
+}
\ No newline at end of file
diff --git a/java/src/main/java/dojo/supermarket/HtmlReceiptPrinter.java b/java/src/main/java/dojo/supermarket/HtmlReceiptPrinter.java
new file mode 100644
index 0000000..9bb74a8
--- /dev/null
+++ b/java/src/main/java/dojo/supermarket/HtmlReceiptPrinter.java
@@ -0,0 +1,82 @@
+package dojo.supermarket;
+
+import dojo.supermarket.model.*;
+
+public class HtmlReceiptPrinter extends BaseReceiptPrinter {
+
+ @Override
+ public String printReceipt(Receipt receipt) {
+ StringBuilder result = new StringBuilder();
+
+ result.append("\n");
+ result.append("
\n");
+ result.append("Receipt
\n");
+ result.append("\n");
+
+ for (ReceiptItem item : receipt.getItems()) {
+ result.append(formatReceiptItem(item));
+ }
+
+ for (Discount discount : receipt.getDiscounts()) {
+ result.append(formatDiscount(discount));
+ }
+
+ result.append(formatTotal(receipt));
+ result.append("
\n");
+ result.append("\n");
+ result.append("\n");
+
+ return result.toString();
+ }
+
+ @Override
+ protected String formatReceiptItem(ReceiptItem item) {
+ StringBuilder result = new StringBuilder();
+ String totalPricePresentation = presentPrice(item.getTotalPrice());
+ String name = item.getProduct().getName();
+
+ result.append("");
+ result.append("| ").append(name).append(" | ");
+ result.append("").append(totalPricePresentation).append(" | ");
+ result.append("
\n");
+
+ if (item.getQuantity() != 1) {
+ result.append("");
+ result.append("| ");
+ result.append(" ").append(presentPrice(item.getPrice()));
+ result.append(" * ").append(presentQuantity(item));
+ result.append(" | ");
+ result.append("
\n");
+ }
+
+ return result.toString();
+ }
+
+ @Override
+ protected String formatDiscount(Discount discount) {
+ String name = discount.getDescription() + "(" + discount.getProduct().getName() + ")";
+ String value = presentPrice(discount.getDiscountAmount());
+
+ StringBuilder result = new StringBuilder();
+ result.append("");
+ result.append("| ").append(name).append(" | ");
+ result.append("").append(value).append(" | ");
+ result.append("
\n");
+
+ return result.toString();
+ }
+
+ @Override
+ protected String formatTotal(Receipt receipt) {
+ String name = "Total: ";
+ String value = presentPrice(receipt.getTotalPrice());
+
+ StringBuilder result = new StringBuilder();
+ result.append("");
+ result.append("| ").append(name).append(" | ");
+ result.append("").append(value).append(" | ");
+ result.append("
\n");
+
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/src/main/java/dojo/supermarket/ReceiptDemo.java b/java/src/main/java/dojo/supermarket/ReceiptDemo.java
new file mode 100644
index 0000000..0b09de5
--- /dev/null
+++ b/java/src/main/java/dojo/supermarket/ReceiptDemo.java
@@ -0,0 +1,54 @@
+package dojo.supermarket;
+
+import dojo.supermarket.model.*;
+
+public class ReceiptDemo {
+
+ static class FakeCatalog implements SupermarketCatalog {
+ private java.util.Map products = new java.util.HashMap<>();
+ private java.util.Map prices = new java.util.HashMap<>();
+
+ @Override
+ public void addProduct(Product product, double price) {
+ this.products.put(product.getName(), product);
+ this.prices.put(product.getName(), price);
+ }
+
+ @Override
+ public double getUnitPrice(Product p) {
+ return this.prices.get(p.getName());
+ }
+ }
+
+ public static void main(String[] args) {
+ // Set up sample data
+ SupermarketCatalog catalog = new FakeCatalog();
+ Product apples = new Product("apples", ProductUnit.KILO);
+ Product toothbrush = new Product("toothbrush", ProductUnit.EACH);
+ Product toothpaste = new Product("toothpaste", ProductUnit.EACH);
+
+ catalog.addProduct(apples, 1.99);
+ catalog.addProduct(toothbrush, 0.99);
+ catalog.addProduct(toothpaste, 1.79);
+
+ Teller teller = new Teller(catalog);
+ teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0);
+
+ ShoppingCart cart = new ShoppingCart();
+ cart.addItemQuantity(apples, 2.5);
+ cart.addItemQuantity(toothbrush, 2);
+ cart.addItemQuantity(toothpaste, 1);
+
+ Receipt receipt = teller.checksOutArticlesFrom(cart);
+
+ // Print text receipt
+ ReceiptPrinter textPrinter = new ReceiptPrinter();
+ System.out.println("=== TEXT RECEIPT ===");
+ System.out.println(textPrinter.printReceipt(receipt));
+
+ // Print HTML receipt
+ HtmlReceiptPrinter htmlPrinter = new HtmlReceiptPrinter();
+ System.out.println("\n=== HTML RECEIPT ===");
+ System.out.println(htmlPrinter.printReceipt(receipt));
+ }
+}
\ No newline at end of file
diff --git a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java b/java/src/main/java/dojo/supermarket/ReceiptPrinter.java
similarity index 70%
rename from java/src/test/java/dojo/supermarket/ReceiptPrinter.java
rename to java/src/main/java/dojo/supermarket/ReceiptPrinter.java
index 070a13f..23e928d 100644
--- a/java/src/test/java/dojo/supermarket/ReceiptPrinter.java
+++ b/java/src/main/java/dojo/supermarket/ReceiptPrinter.java
@@ -4,7 +4,7 @@
import java.util.Locale;
-public class ReceiptPrinter {
+public class ReceiptPrinter extends BaseReceiptPrinter {
private final int columns;
@@ -16,22 +16,38 @@ public ReceiptPrinter(int columns) {
this.columns = columns;
}
+ @Override
public String printReceipt(Receipt receipt) {
StringBuilder result = new StringBuilder();
for (ReceiptItem item : receipt.getItems()) {
- String receiptItem = presentReceiptItem(item);
+ String receiptItem = formatReceiptItem(item);
result.append(receiptItem);
}
for (Discount discount : receipt.getDiscounts()) {
- String discountPresentation = presentDiscount(discount);
+ String discountPresentation = formatDiscount(discount);
result.append(discountPresentation);
}
result.append("\n");
- result.append(presentTotal(receipt));
+ result.append(formatTotal(receipt));
return result.toString();
}
+ @Override
+ protected String formatReceiptItem(ReceiptItem item) {
+ return presentReceiptItem(item);
+ }
+
+ @Override
+ protected String formatDiscount(Discount discount) {
+ return presentDiscount(discount);
+ }
+
+ @Override
+ protected String formatTotal(Receipt receipt) {
+ return presentTotal(receipt);
+ }
+
private String presentReceiptItem(ReceiptItem item) {
String totalPricePresentation = presentPrice(item.getTotalPrice());
String name = item.getProduct().getName();
@@ -69,13 +85,11 @@ private String formatLineWithWhitespace(String name, String value) {
return line.toString();
}
- private static String presentPrice(double price) {
- return String.format(Locale.UK, "%.2f", price);
+ protected static String presentPrice(double price) {
+ return BaseReceiptPrinter.presentPrice(price);
}
- private static String presentQuantity(ReceiptItem item) {
- return ProductUnit.EACH.equals(item.getProduct().getUnit())
- ? String.format("%d", (int)item.getQuantity())
- : String.format(Locale.UK, "%.3f", item.getQuantity());
+ protected static String presentQuantity(ReceiptItem item) {
+ return BaseReceiptPrinter.presentQuantity(item);
}
}
diff --git a/java/src/test/java/dojo/supermarket/HtmlReceiptPrinterTest.java b/java/src/test/java/dojo/supermarket/HtmlReceiptPrinterTest.java
new file mode 100644
index 0000000..38e89a9
--- /dev/null
+++ b/java/src/test/java/dojo/supermarket/HtmlReceiptPrinterTest.java
@@ -0,0 +1,105 @@
+package dojo.supermarket;
+
+import dojo.supermarket.model.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HtmlReceiptPrinterTest {
+
+ @Test
+ void printHtmlReceiptWithSingleItem() {
+ // Arrange
+ SupermarketCatalog catalog = new FakeCatalog();
+ Product apples = new Product("apples", ProductUnit.KILO);
+ catalog.addProduct(apples, 1.99);
+
+ Teller teller = new Teller(catalog);
+ ShoppingCart cart = new ShoppingCart();
+ cart.addItemQuantity(apples, 2.5);
+
+ Receipt receipt = teller.checksOutArticlesFrom(cart);
+ HtmlReceiptPrinter printer = new HtmlReceiptPrinter();
+
+ // Act
+ String htmlReceipt = printer.printReceipt(receipt);
+
+ // Assert
+ assertTrue(htmlReceipt.contains(""));
+ assertTrue(htmlReceipt.contains("apples"));
+ assertTrue(htmlReceipt.contains("4.98")); // 2.5 * 1.99 = 4.975 rounded to 4.98
+ assertTrue(htmlReceipt.contains("1.99"));
+ assertTrue(htmlReceipt.contains("2.500"));
+ assertTrue(htmlReceipt.contains("Total"));
+ assertTrue(htmlReceipt.contains(""));
+ }
+
+ @Test
+ void printHtmlReceiptWithDiscount() {
+ // Arrange
+ SupermarketCatalog catalog = new FakeCatalog();
+ Product toothbrush = new Product("toothbrush", ProductUnit.EACH);
+ catalog.addProduct(toothbrush, 0.99);
+
+ Teller teller = new Teller(catalog);
+ teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0);
+
+ ShoppingCart cart = new ShoppingCart();
+ cart.addItemQuantity(toothbrush, 3); // Buy 3 to trigger discount
+
+ Receipt receipt = teller.checksOutArticlesFrom(cart);
+ HtmlReceiptPrinter printer = new HtmlReceiptPrinter();
+
+ // Act
+ String htmlReceipt = printer.printReceipt(receipt);
+
+ // Assert
+ assertTrue(htmlReceipt.contains(""));
+ assertTrue(htmlReceipt.contains("toothbrush"));
+ assertTrue(htmlReceipt.contains("2.97")); // 3 * 0.99
+ assertTrue(htmlReceipt.contains("0.99"));
+ assertTrue(htmlReceipt.contains("3"));
+ assertTrue(htmlReceipt.contains("10.0% off"));
+ assertTrue(htmlReceipt.contains("Total"));
+ assertTrue(htmlReceipt.contains(""));
+ }
+
+ @Test
+ void htmlReceiptShouldHaveSameDataAsTextReceipt() {
+ // Arrange
+ SupermarketCatalog catalog = new FakeCatalog();
+ Product toothbrush = new Product("toothbrush", ProductUnit.EACH);
+ catalog.addProduct(toothbrush, 0.99);
+
+ Teller teller = new Teller(catalog);
+ teller.addSpecialOffer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0);
+
+ ShoppingCart cart = new ShoppingCart();
+ cart.addItemQuantity(toothbrush, 2);
+
+ Receipt receipt = teller.checksOutArticlesFrom(cart);
+
+ ReceiptPrinter textPrinter = new ReceiptPrinter();
+ HtmlReceiptPrinter htmlPrinter = new HtmlReceiptPrinter();
+
+ // Act
+ String textReceipt = textPrinter.printReceipt(receipt);
+ String htmlReceipt = htmlPrinter.printReceipt(receipt);
+
+ // Assert - Both should contain the same prices and data
+ assertTrue(textReceipt.contains("1.98")); // 2 * 0.99
+ assertTrue(htmlReceipt.contains("1.98"));
+
+ assertTrue(textReceipt.contains("0.99"));
+ assertTrue(htmlReceipt.contains("0.99"));
+
+ assertTrue(textReceipt.contains("toothbrush"));
+ assertTrue(htmlReceipt.contains("toothbrush"));
+
+ if (receipt.getDiscounts().size() > 0) {
+ String discountAmount = String.format("%.2f", Math.abs(receipt.getDiscounts().get(0).getDiscountAmount()));
+ assertTrue(textReceipt.contains(discountAmount));
+ assertTrue(htmlReceipt.contains(discountAmount));
+ }
+ }
+}
\ No newline at end of file