diff --git a/History.md b/History.md index 789221f7..9227afca 100644 --- a/History.md +++ b/History.md @@ -1,7 +1,7 @@ 2.1.0 ======= - +- fixed a charge/allowance rounding error #212 - Corrected intra community supply tax exemption category code 2.0.3 diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 8352cf7c..dc126ab4 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -19,7 +19,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * @param trans the invoice (or IExportableTransaction) to be calculated */ public TransactionCalculator(IExportableTransaction trans) { - this.trans=trans; + this.trans = trans; } /*** @@ -30,7 +30,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { if (trans.getTotalPrepaidAmount() == null) { return BigDecimal.ZERO; } else { - return trans.getTotalPrepaidAmount().setScale(2, RoundingMode.HALF_UP); + return trans.getTotalPrepaidAmount().setScale(2, RoundingMode.HALF_UP); } } @@ -42,9 +42,9 @@ public class TransactionCalculator implements IAbsoluteValueProvider { final BigDecimal res = getTaxBasis(); return getVATPercentAmountMap().values().stream() - .map(VATAmount::getCalculated) - .map(p -> p.setScale(2, RoundingMode.HALF_UP)) - .reduce(BigDecimal.ZERO, BigDecimal::add).add(res); + .map(VATAmount::getCalculated) + .map(p -> p.setScale(2, RoundingMode.HALF_UP)) + .reduce(BigDecimal.ZERO, BigDecimal::add).add(res); } /*** @@ -58,15 +58,15 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) { - BigDecimal res = BigDecimal.ZERO; - if ((charges != null) && (charges.length > 0)) { - for (IZUGFeRDAllowanceCharge currentCharge : charges) { - if ((percent==null)||(currentCharge.getTaxPercent().compareTo(percent)==0)) { - res = res.add(currentCharge.getTotalAmount(this)); - } - } - } - return res; + BigDecimal res = BigDecimal.ZERO; + if ((charges != null) && (charges.length > 0)) { + for (IZUGFeRDAllowanceCharge currentCharge : charges) { + if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)) { + res = res.add(currentCharge.getTotalAmount(this)); + } + } + } + return res; } /*** @@ -78,23 +78,23 @@ public class TransactionCalculator implements IAbsoluteValueProvider { IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); String res = getAllowanceChargeReasonForPercent(percent, charges); if ("".equals(res)) { - res="Charges"; + res = "Charges"; } return res; } private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) { - String res = " "; - if ((charges != null) && (charges.length > 0)) { - for (IZUGFeRDAllowanceCharge currentCharge : charges) { - if ((percent==null)||(currentCharge.getTaxPercent().compareTo(percent)==0) - && currentCharge.getReason()!=null) { - res += currentCharge.getReason()+" "; - } - } - } - res=res.substring(0,res.length()-1); - return res; + String res = " "; + if ((charges != null) && (charges.length > 0)) { + for (IZUGFeRDAllowanceCharge currentCharge : charges) { + if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0) + && currentCharge.getReason() != null) { + res += currentCharge.getReason() + " "; + } + } + } + res = res.substring(0, res.length() - 1); + return res; } /*** @@ -106,7 +106,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); String res = getAllowanceChargeReasonForPercent(percent, allowances); if ("".equals(res)) { - res="Allowances"; + res = "Allowances"; } return res; } @@ -127,10 +127,11 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * @return item sum */ protected BigDecimal getTotal() { - return Stream.of(trans.getZFItems()) - .map(LineCalculator::new) - .map(LineCalculator::getItemTotalNetAmount) - .reduce(ZERO, BigDecimal::add); + BigDecimal dec = Stream.of(trans.getZFItems()) + .map(LineCalculator::new) + .map(LineCalculator::getItemTotalNetAmount) + .reduce(ZERO, BigDecimal::add); + return dec; } /*** @@ -139,8 +140,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * @return item sum +- charges/allowances */ protected BigDecimal getTaxBasis() { - BigDecimal res = getTotal().add(getChargesForPercent(null)).subtract(getAllowancesForPercent(null)); - return res.setScale(2, RoundingMode.HALF_UP); + return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP)).subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP); } /** @@ -172,8 +172,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider { for (IZUGFeRDAllowanceCharge currentCharge : charges) { VATAmount theAmount = hm.get(currentCharge.getTaxPercent().stripTrailingZeros()); if (theAmount == null) { - theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, - currentCharge.getCategoryCode()!=null?currentCharge.getCategoryCode():"S"); + theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, + currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S"); } theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this))); BigDecimal factor = currentCharge.getTaxPercent().divide(new BigDecimal(100)); @@ -186,8 +186,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider { for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { VATAmount theAmount = hm.get(currentAllowance.getTaxPercent().stripTrailingZeros()); if (theAmount == null) { - theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, - currentAllowance.getCategoryCode()!=null?currentAllowance.getCategoryCode():"S"); + theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, + currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S"); } theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this))); BigDecimal factor = currentAllowance.getTaxPercent().divide(new BigDecimal(100)); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java b/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java index f08b406f..6644d97e 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java @@ -19,6 +19,7 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; +import java.math.RoundingMode; /** * Mustangproject's ZUGFeRD implementation @@ -55,7 +56,7 @@ public class VATAmount { } public void setBasis(BigDecimal basis) { - this.basis = basis; + this.basis = basis.setScale(2, RoundingMode.HALF_UP); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java new file mode 100644 index 00000000..88d398f8 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -0,0 +1,202 @@ +package org.mustangproject.ZUGFeRD; + +import static java.math.BigDecimal.TEN; +import static java.math.BigDecimal.valueOf; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.mustangproject.*; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.logging.Level; +import java.util.logging.Logger; + +/*** + * tests the linecalculator and transactioncalculator classes + * + */ +public class CalculationTest { + + @Test + public void testLineCalculator_simpleAmounts_resultInValidVATAmount() { + final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); + final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(100)) + .setQuantity(TEN) + .setProduct(product); + + final LineCalculator calculator = new LineCalculator(currentItem); + + assertEquals(valueOf(100).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); + assertEquals(valueOf(1000).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); + assertEquals(valueOf(160).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); + } + + @Test + public void testLineCalculatorInclusiveAllowance() { + //This test failed with previous implementation. By rounding the totalVATAmount to 2 decimal places the result became wrong + final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); + // 10 % discount on each item + final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.8730)); + + final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) + .setQuantity(valueOf(12)) + .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) + .setProduct(product); + + final LineCalculator calculator = new LineCalculator(currentItem); + + assertEquals(valueOf(133.857).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); + assertEquals(valueOf(1606.28).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); + assertEquals(valueOf(257.0048).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); + } + + @Test + public void testLineCalculatorInclusiveAllowanceAndCharge() { + final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); + // 10 % discount on each item + final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.873)); + // 20 % charge + final IZUGFeRDAllowanceCharge charge = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(29.746)); + final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) + .setQuantity(valueOf(12)) + .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) + .setItemCharges(new IZUGFeRDAllowanceCharge[]{charge}) + .setProduct(product); + + final LineCalculator calculator = new LineCalculator(currentItem); + + assertEquals(valueOf(163.603).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); + assertEquals(valueOf(1963.24).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); + assertEquals(valueOf(314.1184).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); + } + + + @Test + public void testTotalCalculatorGrandTotalRounding() { + SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); + + BigDecimal sales_tax_percent1 = new BigDecimal(16); + BigDecimal total_increase_percent = new BigDecimal(0.80); + BigDecimal total_discount_percent = new BigDecimal(2.00); + + + /* invoice (1st part) */ + + Invoice invoice = new Invoice(); + invoice.setDocumentName("Rechnung"); + invoice.setNumber("777777"); + try { + invoice.setIssueDate(sqlDate.parse("2020-12-31")); + invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1])); + invoice.setDeliveryDate(sqlDate.parse("2020-12-31")); + invoice.setDueDate(sqlDate.parse("2021-01-15")); + } catch (Exception e) { + Logger.getLogger(CalculationTest.class.getName()).log(Level.SEVERE, null, e); + + } + + /* trade party (sender) */ + + TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE"); + sender.addVATID("DE2222222222"); + invoice.setSender(sender); + /* trade party (recipient) */ + + TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE"); + recipient.setID("111111"); + recipient.addVATID("DE111111111"); + invoice.setRecipient(recipient); + /* item */ + + Product product; + Item item; + BigDecimal item_increase = BigDecimal.ZERO; + BigDecimal item_discount = BigDecimal.ZERO; + + product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA"); + item = new Item(product, new BigDecimal("4.750"), new BigDecimal(5.00)); + + item_discount = new BigDecimal("10.00"); + + + if (item_increase.compareTo(BigDecimal.ZERO) > 0) { + item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag")); + } + + if (item_discount.compareTo(BigDecimal.ZERO) > 0) { + item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt")); + } + + invoice.addItem(item); + product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB"); + item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00)); + item_discount = BigDecimal.ZERO; + if (item_increase.compareTo(BigDecimal.ZERO) > 0) { + item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag")); + } + if (item_discount.compareTo(BigDecimal.ZERO) > 0) { + item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt")); + } + + invoice.addItem(item); + product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC"); + item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00)); + item_discount = new BigDecimal("10.00"); + if (item_increase.compareTo(BigDecimal.ZERO) > 0) { + item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag")); + } + + if (item_discount.compareTo(BigDecimal.ZERO) > 0) { + item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt")); + } + + invoice.addItem(item); + + + product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD"); + item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00)); + + item_discount = BigDecimal.ZERO; + + + if (item_increase.compareTo(BigDecimal.ZERO) > 0) { + item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag")); + } + + if (item_discount.compareTo(BigDecimal.ZERO) > 0) { + item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt")); + } + + invoice.addItem(item); + + + product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE"); + item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00)); + + item_discount = BigDecimal.ZERO; + + + if (item_increase.compareTo(BigDecimal.ZERO) > 0) { + item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag")); + } + + if (item_discount.compareTo(BigDecimal.ZERO) > 0) { + item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt")); + + } + invoice.addItem(item); + + if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) { + invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschläge")); + } + + if (total_discount_percent.compareTo(BigDecimal.ZERO) > 0) { + invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatte")); + } + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(valueOf(99.54).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); + } + + +} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/LineCalculatorTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/LineCalculatorTest.java deleted file mode 100644 index 438470aa..00000000 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/LineCalculatorTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.mustangproject.ZUGFeRD; - -import static java.math.BigDecimal.TEN; -import static java.math.BigDecimal.valueOf; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class LineCalculatorTest { - - @Test - public void testLineCalculator_simpleAmounts_resultInValidVATAmount() { - final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); - final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(100)) - .setQuantity(TEN) - .setProduct(product); - - final LineCalculator calculator = new LineCalculator(currentItem); - - assertEquals(valueOf(100).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); - assertEquals(valueOf(1000).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); - assertEquals(valueOf(160).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); - } - - @Test - public void testLineCalculatorInclusiveAllowance() { - //This test failed with previous implementation. By rounding the totalVATAmount to 2 decimal places the result became wrong - final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); - // 10 % discount on each item - final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.8730)); - - final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) - .setQuantity(valueOf(12)) - .setItemAllowances(new IZUGFeRDAllowanceCharge[] { allowance }) - .setProduct(product); - - final LineCalculator calculator = new LineCalculator(currentItem); - - assertEquals(valueOf(133.857).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); - assertEquals(valueOf(1606.28).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); - assertEquals(valueOf(257.0048).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); - } - - @Test - public void testLineCalculatorInclusiveAllowanceAndCharge() { - final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); - // 10 % discount on each item - final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.873)); - // 20 % charge - final IZUGFeRDAllowanceCharge charge = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(29.746)); - final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) - .setQuantity(valueOf(12)) - .setItemAllowances(new IZUGFeRDAllowanceCharge[] { allowance }) - .setItemCharges(new IZUGFeRDAllowanceCharge[] { charge }) - .setProduct(product); - - final LineCalculator calculator = new LineCalculator(currentItem); - - assertEquals(valueOf(163.603).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros()); - assertEquals(valueOf(1963.24).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros()); - assertEquals(valueOf(314.1184).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); - } -}