diff --git a/History.md b/History.md index dd00de12..4835a7c2 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,9 @@ - #954 - #947 - #969 +- #984 +- #983 +- #979, #985 2.20.0 ======= diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 9f0b4208..0358abe9 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -194,6 +194,12 @@ public class Invoice implements IExportableTransaction { } public Invoice setCreditNote() { + documentCode = DocumentCodeTypeConstants.CREDITNOTE; // this value should somewhen be changed to Selfbilling + return this; + } + + public Invoice setCreditNote(String number) { + setInvoiceReferencedDocumentID(number); documentCode = DocumentCodeTypeConstants.CREDITNOTE; return this; } diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 8ca3680b..a472ac4a 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -233,6 +233,9 @@ public class Product implements IZUGFeRDExportableProduct { */ public Product setReverseCharge() { isReverseCharge = true; + if ((getTaxExemptionReason()==null)||(getTaxExemptionReason().isEmpty())) { + setTaxExemptionReason("Reverse charge"); + } setVATPercent(BigDecimal.ZERO); return this; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java index 6ee5f22e..71402d8b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -120,7 +120,7 @@ public interface IZUGFeRDExportableProduct { return TaxCategoryCodeTypeConstants.INTRACOMMUNITY;// "K"; // within europe } else if (isReverseCharge()) { return TaxCategoryCodeTypeConstants.REVERSECHARGE;// "AE"; // to out of europe... - } else if (getVATPercent().compareTo(BigDecimal.ZERO) == 0) { + } else if ((getVATPercent()==null)||(getVATPercent().compareTo(BigDecimal.ZERO) == 0)) { return TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS; // "Z"; // zero rated goods } else { return TaxCategoryCodeTypeConstants.STANDARDRATE;// "S"; // one of the "standard" rates (not diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index daf5b0e0..8b823e5f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -175,7 +175,6 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * @return item sum +- charges/allowances */ public BigDecimal getTaxBasis() { - BigDecimal debug_1=getTotal(); return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP)) .subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP)) .setScale(2, RoundingMode.HALF_UP); @@ -259,8 +258,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { return hm; } - protected List getVATAmountList() - { + protected List getVATAmountList() { final List vatAmounts = new ArrayList<>(); final String vatDueDateTypeCode = this.trans.getVATDueDateTypeCode(); for (final IZUGFeRDExportableItem currentItem : this.trans.getZFItems()) @@ -270,50 +268,40 @@ public class TransactionCalculator implements IAbsoluteValueProvider { continue; } BigDecimal percent = null; - if (currentItem.getProduct() != null) - { + if (currentItem.getProduct() != null) { percent = currentItem.getProduct().getVATPercent(); } - if (percent != null) - { - final LineCalculator lc = currentItem.getCalculation(); - final VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode, percent); - final String reasonText = currentItem.getProduct().getTaxExemptionReason(); - if (reasonText != null) - { - itemVATAmount.setVatExemptionReasonText(reasonText); - } - final Optional currentVatAmount = this.getCurrentVatAmount(vatAmounts, currentItem.getProduct().getTaxCategoryCode(), percent); - if (currentVatAmount.isEmpty()) - { - vatAmounts.add(itemVATAmount); - } - else - { - this.mergeAdding(currentVatAmount.get(), itemVATAmount); - } + if (percent == null) { + percent = ZERO; + } + final LineCalculator lc = currentItem.getCalculation(); + final VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), + currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode, percent); + final String reasonText = currentItem.getProduct().getTaxExemptionReason(); + if (reasonText != null) { + itemVATAmount.setVatExemptionReasonText(reasonText); + } + final Optional currentVatAmount = this.getCurrentVatAmount(vatAmounts, currentItem.getProduct().getTaxCategoryCode(), percent); + if (currentVatAmount.isEmpty()) { + vatAmounts.add(itemVATAmount); + } else { + this.mergeAdding(currentVatAmount.get(), itemVATAmount); } } final IZUGFeRDAllowanceCharge[] charges = this.trans.getZFCharges(); if (charges != null) { - for (final IZUGFeRDAllowanceCharge currentCharge : charges) - { + for (final IZUGFeRDAllowanceCharge currentCharge : charges) { final BigDecimal taxPercent = currentCharge.getTaxPercent(); - if (taxPercent != null) - { + if (taxPercent != null) { final String vatCategoryCode = currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S"; final Optional currentChargeVatAmount = this.getCurrentVatAmount(vatAmounts, vatCategoryCode, taxPercent); final BigDecimal chargeBasis = currentCharge.getTotalAmount(this); final VATAmount chargeVatAmount = new VATAmount(chargeBasis, chargeBasis.multiply(taxPercent.divide(new BigDecimal(100))), vatCategoryCode, vatDueDateTypeCode, taxPercent); - if (currentChargeVatAmount.isEmpty()) - { + if (currentChargeVatAmount.isEmpty()) { vatAmounts.add(chargeVatAmount); - } - else - { + } else { this.mergeAdding(currentChargeVatAmount.get(), chargeVatAmount); } } @@ -321,11 +309,9 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } final IZUGFeRDAllowanceCharge[] allowances = this.trans.getZFAllowances(); if (allowances != null) { - for (final IZUGFeRDAllowanceCharge currentAllowance : allowances) - { + for (final IZUGFeRDAllowanceCharge currentAllowance : allowances) { final BigDecimal taxPercent = currentAllowance.getTaxPercent(); - if (taxPercent != null) - { + if (taxPercent != null) { final String vatCategoryCode = currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S"; final Optional currentAllowanceVatAmount = this.getCurrentVatAmount(vatAmounts, vatCategoryCode, taxPercent); final BigDecimal allowanceNegativeBasis = currentAllowance.getTotalAmount(this).multiply(BigDecimal.valueOf(-1)); @@ -333,12 +319,9 @@ public class TransactionCalculator implements IAbsoluteValueProvider { allowanceNegativeBasis.multiply(taxPercent.divide(new BigDecimal(100))), currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S", vatDueDateTypeCode, taxPercent); - if (currentAllowanceVatAmount.isEmpty()) - { + if (currentAllowanceVatAmount.isEmpty()) { vatAmounts.add(allowanceVATAmount); - } - else - { + } else { this.mergeAdding(currentAllowanceVatAmount.get(), allowanceVATAmount); } } @@ -346,19 +329,17 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } return vatAmounts; } - - public void mergeAdding(VATAmount vatAmount, VATAmount toAdd) - { + + public void mergeAdding(VATAmount vatAmount, VATAmount toAdd) { vatAmount.setBasis(vatAmount.getBasis().add(toAdd.getBasis())); vatAmount.setCalculated(vatAmount.getCalculated().add(toAdd.getCalculated())); - if (toAdd.getVatExemptionReasonText() != null && !toAdd.getVatExemptionReasonText().isBlank()) - { + if (toAdd.getVatExemptionReasonText() != null && !toAdd.getVatExemptionReasonText().isBlank()) { Optional.ofNullable(vatAmount.getVatExemptionReasonText()).filter(reasonText -> !reasonText.equals(toAdd.getVatExemptionReasonText())).ifPresentOrElse( text -> vatAmount.setVatExemptionReasonText(String.join(", ", text, toAdd.getVatExemptionReasonText())), () -> vatAmount.setVatExemptionReasonText(toAdd.getVatExemptionReasonText())); } } - + @Override public BigDecimal getValue() { return getTotal(); @@ -371,13 +352,12 @@ public class TransactionCalculator implements IAbsoluteValueProvider { public BigDecimal getAllowanceTotal() { return getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP); } - - private Optional getCurrentVatAmount(List vatAmounts, String vatCategoryCode, BigDecimal percentage) - { + + private Optional getCurrentVatAmount(List vatAmounts, String vatCategoryCode, BigDecimal percentage) { return vatAmounts.stream() .filter(va -> Objects.equals(vatCategoryCode, va.getCategoryCode()) && Optional.ofNullable(percentage).map(p -> va.getApplicablePercent() == null && p == null || p.compareTo(va.getApplicablePercent()) == 0) - .orElse(true)) + .orElse(true)) .findFirst(); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index cddadf7a..ded4e1b6 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -538,8 +538,15 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } xml += "" + currentItem.getProduct().getTaxCategoryCode() + ""; - if (!currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) { - xml += "" + vatFormat(currentItem.getProduct().getVATPercent()) + ""; + BigDecimal vatValue=BigDecimal.ZERO; + if (currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS)) { + vatValue=BigDecimal.ZERO; + } else { + vatValue=currentItem.getProduct().getVATPercent(); + } + + if ((!currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) ) { + xml += "" + vatFormat(vatValue) + ""; } xml += ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java b/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java index 74fac56a..61cfd66a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/model/DocumentCodeTypeConstants.java @@ -23,5 +23,6 @@ public class DocumentCodeTypeConstants { public static final String CREDITNOTE = "381"; public static final String DEBITNOTE = "84"; public static final String CORRECTEDINVOICE = "384"; + public static final String SELFBILLING = "389"; public static final String PARTIAL_BILLING = "326"; } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/VATrelatedTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/VATrelatedTest.java new file mode 100644 index 00000000..4c51d321 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/VATrelatedTest.java @@ -0,0 +1,153 @@ + +/** ********************************************************************** + * + * Copyright 2019 Jochen Staerk + * + * Use is subject to license terms. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *********************************************************************** */ +package org.mustangproject.ZUGFeRD; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; +import org.mustangproject.*; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class VATrelatedTest extends ResourceCase { + final String TARGET_PDF_REVERSE = "./target/testout-ReverseCharge.pdf"; + final String TARGET_PDF_Z = "./target/testout-TaxcodeZ.pdf"; + + public void testReverseCharge() { + + String orgname = "Test company"; + String number = "123"; + String priceStr = "3.00"; + BigDecimal price = new BigDecimal(priceStr); + try { + InputStream SOURCE_PDF = this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf"); + + ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1(); + ze.ignorePDFAErrors().load(SOURCE_PDF); + ze.setProfile(Profiles.getByName("Extended")); + + ze.setProducer("My Application").setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("extended"); + // ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname,"teststr", "55232","teststadt","DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) + // .addItem(new Item(new Product("Testprodukt", "", "H84", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50))))); + + Product p=new Product("Testprodukt", "", "H87", new BigDecimal(19)); + p.setReverseCharge(); + Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE") + .setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562")).addVATID("DE0915")) + .setNumber(number) + .addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("AReason").setTaxPercent(new BigDecimal(19))) + + .addItem(new Item(p, price, new BigDecimal(1.0))); + ze.setTransaction(i); + + + String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8); + assertTrue(theXML.contains("