Merge pull request #309 from chris-webert-its4u/issues/308-vat-due-date

Issue 308: define when VAT collection become applicable
This commit is contained in:
Jochen Staerk
2023-03-06 09:29:53 +01:00
committed by GitHub
8 changed files with 80 additions and 6 deletions

View File

@@ -63,6 +63,7 @@ public class Invoice implements IExportableTransaction {
protected String specifiedProcuringProjectID = null; protected String specifiedProcuringProjectID = null;
protected String specifiedProcuringProjectName = null; protected String specifiedProcuringProjectName = null;
protected String despatchAdviceReferencedDocumentID = null; protected String despatchAdviceReferencedDocumentID = null;
protected String vatDueDateTypeCode = null;
public Invoice() { public Invoice() {
ZFItems = new ArrayList<>(); ZFItems = new ArrayList<>();
@@ -703,4 +704,18 @@ public class Invoice implements IExportableTransaction {
return this; return this;
} }
@Override
public String getVATDueDateTypeCode() {
return vatDueDateTypeCode;
}
/**
* Decide when the VAT should be collected.
* @param vatDueDateTypeCode use EventTimeCodeTypeConstants
*/
public Invoice setVATDueDateTypeCode(String vatDueDateTypeCode) {
this.vatDueDateTypeCode = vatDueDateTypeCode;
return this;
}
} }

View File

@@ -497,4 +497,8 @@ public interface IExportableTransaction {
return null; return null;
} }
default String getVATDueDateTypeCode() {
return null;
}
} }

View File

@@ -164,13 +164,14 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
*/ */
protected HashMap<BigDecimal, VATAmount> getVATPercentAmountMap() { protected HashMap<BigDecimal, VATAmount> getVATPercentAmountMap() {
HashMap<BigDecimal, VATAmount> hm = new HashMap<>(); HashMap<BigDecimal, VATAmount> hm = new HashMap<>();
final String vatDueDateTypeCode = trans.getVATDueDateTypeCode();
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
BigDecimal percent = currentItem.getProduct().getVATPercent(); BigDecimal percent = currentItem.getProduct().getVATPercent();
if (percent != null) { if (percent != null) {
LineCalculator lc = new LineCalculator(currentItem); LineCalculator lc = new LineCalculator(currentItem);
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
currentItem.getProduct().getTaxCategoryCode()); currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
VATAmount current = hm.get(percent.stripTrailingZeros()); VATAmount current = hm.get(percent.stripTrailingZeros());
if (current == null) { if (current == null) {
hm.put(percent.stripTrailingZeros(), itemVATAmount); hm.put(percent.stripTrailingZeros(), itemVATAmount);
@@ -188,7 +189,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros()); VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
if (theAmount == null) { if (theAmount == null) {
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S"); currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S",
vatDueDateTypeCode);
} }
theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this))); theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this)));
BigDecimal factor = taxPercent.divide(new BigDecimal(100)); BigDecimal factor = taxPercent.divide(new BigDecimal(100));
@@ -205,7 +207,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros()); VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
if (theAmount == null) { if (theAmount == null) {
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO, theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S"); currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
vatDueDateTypeCode);
} }
theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this))); theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this)));
BigDecimal factor = taxPercent.divide(new BigDecimal(100)); BigDecimal factor = taxPercent.divide(new BigDecimal(100));

View File

@@ -37,12 +37,23 @@ public class VATAmount {
this.basis = basis; this.basis = basis;
this.calculated = calculated; this.calculated = calculated;
this.categoryCode = categoryCode; this.categoryCode = categoryCode;
this.dueDateTypeCode = null;
}
public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode, String dueDateTypeCode) {
super();
this.basis = basis;
this.calculated = calculated;
this.categoryCode = categoryCode;
this.dueDateTypeCode = dueDateTypeCode;
} }
BigDecimal basis, calculated, applicablePercent; BigDecimal basis, calculated, applicablePercent;
String categoryCode; String categoryCode;
String dueDateTypeCode;
public BigDecimal getApplicablePercent() { public BigDecimal getApplicablePercent() {
return applicablePercent; return applicablePercent;
} }
@@ -95,12 +106,20 @@ public class VATAmount {
this.categoryCode = categoryCode; this.categoryCode = categoryCode;
} }
public String getDueDateTypeCode() {
return dueDateTypeCode;
}
public void setDueDateTypeCode(String dueDateTypeCode) {
this.dueDateTypeCode = dueDateTypeCode;
}
public VATAmount add(VATAmount v) { public VATAmount add(VATAmount v) {
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode); return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
} }
public VATAmount subtract(VATAmount v) { public VATAmount subtract(VATAmount v) {
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode); return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
} }
} }

View File

@@ -271,6 +271,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
if (amount != null) { if (amount != null) {
final String amountCategoryCode = amount.getCategoryCode(); final String amountCategoryCode = amount.getCategoryCode();
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode); final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
xml += "<ram:ApplicableTradeTax>" xml += "<ram:ApplicableTradeTax>"
+ "<ram:CalculatedAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getCalculated()) + "<ram:CalculatedAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getCalculated())
@@ -279,6 +280,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ (displayExemptionReason ? exemptionReason : "") + (displayExemptionReason ? exemptionReason : "")
+ "<ram:BasisAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\" + "<ram:BasisAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
+ "<ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>" + "<ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>"
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
+ "<ram:ApplicablePercent>" + vatFormat(currentTaxPercent) + "<ram:ApplicablePercent>" + vatFormat(currentTaxPercent)
+ "</ram:ApplicablePercent></ram:ApplicableTradeTax>"; + "</ram:ApplicablePercent></ram:ApplicableTradeTax>";
} }

View File

@@ -618,6 +618,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
if (amount != null) { if (amount != null) {
final String amountCategoryCode = amount.getCategoryCode(); final String amountCategoryCode = amount.getCategoryCode();
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode); final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
if (getProfile() != Profiles.getByName("Minimum")) { if (getProfile() != Profiles.getByName("Minimum")) {
@@ -628,6 +629,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ (displayExemptionReason ? exemptionReason : "") + (displayExemptionReason ? exemptionReason : "")
+ "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\" + "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>" + "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
+ "<ram:RateApplicablePercent>" + "<ram:RateApplicablePercent>"
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>"; + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>";
} }

View File

@@ -0,0 +1,25 @@
/** **********************************************************************
*
* Copyright 2018 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.model;
public class EventTimeCodeTypeConstants {
public static final String INVOICE_DATE = "5";
public static final String DELIVERY_DATE = "29";
public static final String PAYMENT_DATE = "72";
}

View File

@@ -33,6 +33,8 @@ import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants;
import org.xmlunit.builder.Input; import org.xmlunit.builder.Input;
import org.xmlunit.xpath.JAXPXPathEngine; import org.xmlunit.xpath.JAXPXPathEngine;
import org.xmlunit.xpath.XPathEngine; import org.xmlunit.xpath.XPathEngine;
@@ -390,7 +392,7 @@ public class ZF2PushTest extends TestCase {
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin), price, new BigDecimal(1.0)).addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15"))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin), price, new BigDecimal(1.0)).addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
.addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16)))
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16))) .addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
.setDeliveryDate(sdf.parse("2020-11-02")).setOwnVATID("DE0815").setNumber(number) .setDeliveryDate(sdf.parse("2020-11-02")).setOwnVATID("DE0815").setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE)
); );
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); e.printStackTrace();
@@ -429,6 +431,8 @@ public class ZF2PushTest extends TestCase {
assertFalse(zi.getUTF8().contains("DE47110")); // but not the VAT ID of the shiptotradeparty assertFalse(zi.getUTF8().contains("DE47110")); // but not the VAT ID of the shiptotradeparty
assertTrue(zi.getUTF8().contains("document level 2/2")); assertTrue(zi.getUTF8().contains("document level 2/2"));
assertFalse(zi.getUTF8().contains("++49555123456")); // in profile EN16931 contact fax number is not allowed assertFalse(zi.getUTF8().contains("++49555123456")); // in profile EN16931 contact fax number is not allowed
assertThat(zi.getUTF8()).valueByXPath("//*[local-name()='ApplicableTradeTax']/*[local-name()='DueDateTypeCode']").asString()
.isEqualTo(EventTimeCodeTypeConstants.PAYMENT_DATE);
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PUSHEDGE); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PUSHEDGE);
try { try {