diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index bafab84d..8fee7481 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,3 @@ # These are supported funding model platforms -github: [jstaerk] +custom: ["https://www.mustangproject.org/support/"] diff --git a/History.md b/History.md index 97088002..9227afca 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +2.1.0 +======= + +- fixed a charge/allowance rounding error #212 +- Corrected intra community supply tax exemption category code + 2.0.3 ======= 2020-12-06 diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 274d1f1c..c7f94c3d 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -8,7 +8,7 @@ 4.0.0 org.mustangproject Mustang-CLI - Mustang commandline tool + e-invoices commandline tool, allowing to create(embed), split and validate Factur-X/ZUGFeRD files. Validation should also work for XRechnung/CII. jar 2.0.4-SNAPSHOT @@ -53,7 +53,6 @@ 4.13.1 test - org.xmlunit diff --git a/library/pom.xml b/library/pom.xml index d1c0b4bf..6686b648 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -11,8 +11,8 @@ library 2.0.4-SNAPSHOT jar - Library to write and read FacturX and ZUGFeRD e-invoices. - The Mustang project is a java library to read and write ZUGFeRD meta data inside your invoice PDFs. To write files, a provided PDF/A will be combined with generated or provided XML. + Library to write, read and validate e-invoices (Factur-X, ZUGFeRD and to a limited extend XRechnung/CII). + The Mustang project is a java library to read, write and validate Factur-X/ZUGFeRD meta data inside your invoice PDFs. To write files, a provided PDF/A will be combined with generated or provided XML. http://www.mustangproject.org/ @@ -83,6 +83,14 @@ 4.13.1 test + + + + com.helger + en16931-cii2ubl + 1.2.5 + + com.helger ph-schematron @@ -101,6 +109,12 @@ xmlunit-assertj 2.6.3 + + com.helger + ph-commons + 9.1.1 + compile + diff --git a/library/src/main/java/org/mustangproject/CII/CIIToUBL.java b/library/src/main/java/org/mustangproject/CII/CIIToUBL.java new file mode 100644 index 00000000..8b2a28e3 --- /dev/null +++ b/library/src/main/java/org/mustangproject/CII/CIIToUBL.java @@ -0,0 +1,50 @@ +package org.mustangproject.CII; +import com.helger.commons.error.list.ErrorList; +import com.helger.en16931.cii2ubl.CIIToUBL22Converter; +import com.helger.ubl21.UBL21Writer; +import com.helger.ubl22.UBL22Writer; + +import java.io.File; +import java.io.Serializable; + +public class CIIToUBL { + /*** + * converts a CII XML file to a UBL XML file + * thanks to Philip Helger for his library + * @param input + * @param output + */ + public void convert(File input, File output) { + ErrorList occurred=new ErrorList(); + CIIToUBL22Converter cc=new CIIToUBL22Converter(); + Serializable aUBL = cc.convertCIItoUBL(input, occurred); + if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_21.InvoiceType) + { + UBL21Writer.invoice () + .setFormattedOutput (true) + .write ((oasis.names.specification.ubl.schema.xsd.invoice_21.InvoiceType) aUBL, output); + } + else + if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_21.CreditNoteType) + { + UBL21Writer.creditNote () + .setFormattedOutput (true) + .write ((oasis.names.specification.ubl.schema.xsd.creditnote_21.CreditNoteType) aUBL, output); + } + else + if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_22.InvoiceType) + { + UBL22Writer.invoice () + .setFormattedOutput (true) + .write ((oasis.names.specification.ubl.schema.xsd.invoice_22.InvoiceType) aUBL, output); + } + else + if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_22.CreditNoteType) + { + UBL22Writer.creditNote () + .setFormattedOutput (true) + .write ((oasis.names.specification.ubl.schema.xsd.creditnote_22.CreditNoteType) aUBL, output); + } + } + +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index e7ef193c..70fa9e50 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -126,13 +126,19 @@ public interface IExportableTransaction { return null; } - IZUGFeRDAllowanceCharge[] getZFAllowances(); + default IZUGFeRDAllowanceCharge[] getZFAllowances() { + return null; + } - IZUGFeRDAllowanceCharge[] getZFCharges(); + default IZUGFeRDAllowanceCharge[] getZFCharges() { + return null; + } - IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges(); + default IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() { + return null; + } IZUGFeRDExportableItem[] getZFItems(); @@ -290,7 +296,7 @@ public interface IExportableTransaction { * @return three character currency of this invoice */ default String getCurrency() { - return null; + return "EUR"; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 80ead5ed..a584c718 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -37,13 +37,17 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ * item level discounts * @return array of the discounts on a single item */ - IZUGFeRDAllowanceCharge[] getItemAllowances(); + default IZUGFeRDAllowanceCharge[] getItemAllowances() { + return null; + } /** * item level price additions * @return array of the additional charges on the item */ - IZUGFeRDAllowanceCharge[] getItemCharges(); + default IZUGFeRDAllowanceCharge[] getItemCharges() { + return null; + } /** diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java index 66f25163..315de277 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableTradeParty.java @@ -68,17 +68,13 @@ public interface IZUGFeRDExportableTradeParty { * * @return First and last name of the recipient */ - default String getName() { - return null; - } + String getName(); /** * Postal code of the recipient * * @return Postal code of the recipient */ - default String getZIP() { - return null; - } + String getZIP(); /** @@ -96,9 +92,7 @@ public interface IZUGFeRDExportableTradeParty { * * @return two-letter iso country code of the contact */ - default String getCountry() { - return null; - } + String getCountry(); /** @@ -106,9 +100,7 @@ public interface IZUGFeRDExportableTradeParty { * * @return Returns the city of the recipient */ - default String getLocation() { - return null; - } + String getLocation(); /** @@ -116,9 +108,7 @@ public interface IZUGFeRDExportableTradeParty { * * @return street address (street+number) of the contact */ - default String getStreet() { - return null; - } + String getStreet(); /** * returns additional address information which is display in xml tag "LineTwo" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java index 515e9505..dc0d6e4f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java @@ -20,7 +20,6 @@ */ package org.mustangproject.ZUGFeRD; -import java.time.Year; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -32,7 +31,7 @@ public class Profiles { {"BASIC", new Profile("BASIC", "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic")}, {"EN16931", new Profile("EN16931", "urn:cen.eu:en16931:2017")}, {"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")}, - {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_"+((Year.now().getValue()<=2020)?"1.2":"2.0"))} //intentionally switch to XRechnung 2.0 on 01.01.2021 + {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0")} //switched to XRechnung 2.0 in 2021 }).collect(Collectors.toMap(data -> (String) data[0], data -> (Profile) data[1])); static Map zf1Map = Stream.of(new Object[][]{ 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/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java b/library/src/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java index 979207c8..b7669ba0 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java @@ -24,5 +24,5 @@ public class TaxCategoryCodeTypeConstants { public static final String TAXEXEMPT = "E"; public static final String ZEROTAXPRODUCTS = "Z"; public static final String UNTAXEDSERVICE = "O"; - public static final String INTRACOMMUNITY = "IC"; + public static final String INTRACOMMUNITY = "K"; } 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()); - } -}