diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 000000000..b477d81d3 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,18 @@ +name: CI for Java Invoice +on: [push] +jobs: + test: + name: Unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'adopt' + - name: Test + run: mvn test + - name: Checkstyle + run: mvn checkstyle:check + diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 000000000..fbcd05132 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2d40c3e8a..03c23ac83 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,27 @@ 17 + + + org.apache.maven.plugins + + maven-checkstyle-plugin + + 3.6.0 + + + + checkstyle.xml + + true + + true + + + + + + diff --git a/src/main/java/pl/edu/agh/mwo/invoice/Invoice.java b/src/main/java/pl/edu/agh/mwo/invoice/Invoice.java index 56fe02359..87ab080f0 100644 --- a/src/main/java/pl/edu/agh/mwo/invoice/Invoice.java +++ b/src/main/java/pl/edu/agh/mwo/invoice/Invoice.java @@ -1,30 +1,87 @@ package pl.edu.agh.mwo.invoice; import java.math.BigDecimal; -import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import pl.edu.agh.mwo.invoice.product.Product; public class Invoice { - private Collection products; + + private static int nextInvoiceNumber = 1; + private final int invoiceNumber; + + private Map products = new HashMap(); + + + public Invoice() { + this.invoiceNumber = nextInvoiceNumber; + nextInvoiceNumber++; + } + + public int getInvoiceNumber() { + return invoiceNumber; + } public void addProduct(Product product) { - // TODO: implement + addProduct(product, 1); } public void addProduct(Product product, Integer quantity) { - // TODO: implement + if (product == null || quantity <= 0) { + throw new IllegalArgumentException(); + + } + if (products.containsKey(product)) { + products.put(product, products.get(product) + quantity); + } else { + products.put(product, quantity); + } } - public BigDecimal getSubtotal() { - return null; + public BigDecimal getNetTotal() { + BigDecimal totalNet = BigDecimal.ZERO; + for (Product product : products.keySet()) { + BigDecimal quantity = new BigDecimal(products.get(product)); + totalNet = totalNet.add(product.getPrice().multiply(quantity)); + } + return totalNet; } - public BigDecimal getTax() { - return null; + public BigDecimal getTaxTotal() { + return getGrossTotal().subtract(getNetTotal()); } - public BigDecimal getTotal() { - return null; + public BigDecimal getGrossTotal() { + BigDecimal totalGross = BigDecimal.ZERO; + for (Product product : products.keySet()) { + BigDecimal quantity = new BigDecimal(products.get(product)); + totalGross = totalGross.add(product.getPriceWithTax().multiply(quantity)); + } + return totalGross; } -} + + + public String printInvoice() { + if (products.isEmpty()) { + throw new IllegalStateException(); + } + + StringBuilder builder = new StringBuilder(); + builder.append("Faktura nr ").append(invoiceNumber).append("\n"); + + for (Product product : products.keySet()) { + Integer quantity = products.get(product); + builder.append(product.getName()) + .append("\t") + .append(quantity) + .append("\t") + .append(product.getPriceWithTax()) + .append("\n"); + } + + builder.append("Liczba pozycji: ").append(products.size()); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/pl/edu/agh/mwo/invoice/product/Product.java b/src/main/java/pl/edu/agh/mwo/invoice/product/Product.java index 318de9ac9..5951f7d3f 100644 --- a/src/main/java/pl/edu/agh/mwo/invoice/product/Product.java +++ b/src/main/java/pl/edu/agh/mwo/invoice/product/Product.java @@ -10,24 +10,49 @@ public abstract class Product { private final BigDecimal taxPercent; protected Product(String name, BigDecimal price, BigDecimal tax) { + if (name == null + || name.equals("") + || price == null + || tax == null + || tax.compareTo(new BigDecimal(0)) < 0 + || price.compareTo(new BigDecimal(0)) < 0) { + throw new IllegalArgumentException("Invalid product data"); + } this.name = name; this.price = price; this.taxPercent = tax; } public String getName() { - return null; + return name; } public BigDecimal getPrice() { - return null; + return price; } public BigDecimal getTaxPercent() { - return null; + return taxPercent; } public BigDecimal getPriceWithTax() { - return null; + return price.multiply(taxPercent).add(price); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Product other = (Product) obj; + return name.equals(other.name) && price.compareTo(other.price) == 0; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(name, price); } } diff --git a/src/test/java/pl/edu/agh/mwo/invoice/InvoiceTest.java b/src/test/java/pl/edu/agh/mwo/invoice/InvoiceTest.java index 7f4b6f795..7c350935c 100644 --- a/src/test/java/pl/edu/agh/mwo/invoice/InvoiceTest.java +++ b/src/test/java/pl/edu/agh/mwo/invoice/InvoiceTest.java @@ -23,17 +23,17 @@ public void createEmptyInvoiceForTheTest() { @Test public void testEmptyInvoiceHasEmptySubtotal() { - Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getNetTotal())); } @Test public void testEmptyInvoiceHasEmptyTaxAmount() { - Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getTax())); + Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getTaxTotal())); } @Test public void testEmptyInvoiceHasEmptyTotal() { - Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getTotal())); + Assert.assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(invoice.getGrossTotal())); } @Test @@ -42,21 +42,21 @@ public void testInvoiceSubtotalWithTwoDifferentProducts() { Product apples = new TaxFreeProduct("Owoce", new BigDecimal("10")); invoice.addProduct(onions); invoice.addProduct(apples); - Assert.assertThat(new BigDecimal("20"), Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(new BigDecimal("20"), Matchers.comparesEqualTo(invoice.getNetTotal())); } @Test public void testInvoiceSubtotalWithManySameProducts() { Product onions = new TaxFreeProduct("Warzywa", BigDecimal.valueOf(10)); invoice.addProduct(onions, 100); - Assert.assertThat(new BigDecimal("1000"), Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(new BigDecimal("1000"), Matchers.comparesEqualTo(invoice.getNetTotal())); } @Test public void testInvoiceHasTheSameSubtotalAndTotalIfTaxIsZero() { Product taxFreeProduct = new TaxFreeProduct("Warzywa", new BigDecimal("199.99")); invoice.addProduct(taxFreeProduct); - Assert.assertThat(invoice.getTotal(), Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(invoice.getNetTotal(), Matchers.comparesEqualTo(invoice.getGrossTotal())); } @Test @@ -64,7 +64,7 @@ public void testInvoiceHasProperSubtotalForManyProducts() { invoice.addProduct(new TaxFreeProduct("Owoce", new BigDecimal("200"))); invoice.addProduct(new DairyProduct("Maslanka", new BigDecimal("100"))); invoice.addProduct(new OtherProduct("Wino", new BigDecimal("10"))); - Assert.assertThat(new BigDecimal("310"), Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(new BigDecimal("310"), Matchers.comparesEqualTo(invoice.getNetTotal())); } @Test @@ -75,7 +75,7 @@ public void testInvoiceHasProperTaxValueForManyProduct() { invoice.addProduct(new DairyProduct("Kefir", new BigDecimal("100"))); // tax: 2.30 invoice.addProduct(new OtherProduct("Piwko", new BigDecimal("10"))); - Assert.assertThat(new BigDecimal("10.30"), Matchers.comparesEqualTo(invoice.getTax())); + Assert.assertThat(new BigDecimal("10.30"), Matchers.comparesEqualTo(invoice.getTaxTotal())); } @Test @@ -86,7 +86,7 @@ public void testInvoiceHasProperTotalValueForManyProduct() { invoice.addProduct(new DairyProduct("Maslo", new BigDecimal("100"))); // price with tax: 12.30 invoice.addProduct(new OtherProduct("Chipsy", new BigDecimal("10"))); - Assert.assertThat(new BigDecimal("320.30"), Matchers.comparesEqualTo(invoice.getTotal())); + Assert.assertThat(new BigDecimal("320.30"), Matchers.comparesEqualTo(invoice.getGrossTotal())); } @Test @@ -97,7 +97,7 @@ public void testInvoiceHasPropoerSubtotalWithQuantityMoreThanOne() { invoice.addProduct(new DairyProduct("Kozi Serek", new BigDecimal("10")), 3); // 1000x pinezka - price: 10 invoice.addProduct(new OtherProduct("Pinezka", new BigDecimal("0.01")), 1000); - Assert.assertThat(new BigDecimal("50"), Matchers.comparesEqualTo(invoice.getSubtotal())); + Assert.assertThat(new BigDecimal("50"), Matchers.comparesEqualTo(invoice.getNetTotal())); } @Test @@ -108,7 +108,7 @@ public void testInvoiceHasPropoerTotalWithQuantityMoreThanOne() { invoice.addProduct(new DairyProduct("Chedar", new BigDecimal("10")), 3); // 1000x pinezka - price with tax: 12.30 invoice.addProduct(new OtherProduct("Pinezka", new BigDecimal("0.01")), 1000); - Assert.assertThat(new BigDecimal("54.70"), Matchers.comparesEqualTo(invoice.getTotal())); + Assert.assertThat(new BigDecimal("54.70"), Matchers.comparesEqualTo(invoice.getGrossTotal())); } @Test(expected = IllegalArgumentException.class) @@ -125,4 +125,90 @@ public void testInvoiceWithNegativeQuantity() { public void testAddingNullProduct() { invoice.addProduct(null); } + + @Test + public void testInvoiceHasNumberGreaterThan0 () { + Invoice invoice = new Invoice(); + int number = invoice.getInvoiceNumber(); + Assert.assertThat(number, Matchers.greaterThan(0)); + } + + @Test + public void testTwoInvoicesHaveDifferentNumber() { + int number1 = new Invoice().getInvoiceNumber(); + int number2 = new Invoice().getInvoiceNumber(); + Assert.assertNotEquals(number1, number2); + } + + @Test + public void testInvoiceNumberDoesNotChange() { + Invoice invoice = new Invoice(); + int firstCheck = invoice.getInvoiceNumber(); + int secondCheck = invoice.getInvoiceNumber(); + Assert.assertEquals(firstCheck, secondCheck); + + } + @Test + public void testPrintInvoiceContainsInvoiceNumber() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00"))); + String result = invoice.printInvoice(); + Assert.assertTrue(result.contains("Faktura nr " + invoice.getInvoiceNumber())); + } + + + @Test + public void testPrintInvoiceContainsProductNames() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 2); + invoice.addProduct(new DairyProduct("Mleko", new BigDecimal("4.00")), 1); + String result = invoice.printInvoice(); + Assert.assertTrue(result.contains("Chleb")); + Assert.assertTrue(result.contains("Mleko")); + } + + + @Test + public void testPrintInvoiceEndsWithPositionCount() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("A", new BigDecimal("1.00"))); + invoice.addProduct(new OtherProduct("B", new BigDecimal("2.00"))); + invoice.addProduct(new DairyProduct("C", new BigDecimal("3.00"))); + String result = invoice.printInvoice(); + Assert.assertTrue(result.contains("Liczba pozycji: 3")); + + } + + @Test(expected = IllegalStateException.class) + public void testPrintInvoiceForEmptyInvoiceThrowsException() { + Invoice invoice = new Invoice(); + invoice.printInvoice(); + } + + @Test + public void testInvoiceMergesDuplicateProductsByName() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 2); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 3); + Assert.assertEquals(new BigDecimal("18.45"), + invoice.getGrossTotal().setScale(2, java.math.RoundingMode.HALF_UP)); + } + + @Test + public void testInvoiceWithDuplicateProductHasOnlyOnePosition() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 2); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 3); + String result = invoice.printInvoice(); + Assert.assertTrue(result.contains("Liczba pozycji: 1")); + } + + @Test + public void testInvoiceTreatsDifferentPricesAsSeparateProducts() { + Invoice invoice = new Invoice(); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("3.00")), 2); + invoice.addProduct(new OtherProduct("Chleb", new BigDecimal("2.50")), 3); + String result = invoice.printInvoice(); + Assert.assertTrue(result.contains("Liczba pozycji: 2")); + } }