diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index df5e329f..73e18fd1 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -13,6 +13,8 @@ import java.util.HashMap; public class Product implements IZUGFeRDExportableProduct { protected String unit, name, sellerAssignedID, buyerAssignedID; protected String description=""; + protected String taxExemptionReason=null; + protected String taxCategoryCode=null; protected BigDecimal VATPercent; protected boolean isReverseCharge = false; protected boolean isIntraCommunitySupply = false; @@ -66,6 +68,44 @@ public class Product implements IZUGFeRDExportableProduct { return this; } + /*** + * + * @return e.g. intra-commnunity supply or small business + */ + @Override + public String getTaxExemptionReason() { + return taxExemptionReason; + } + + /*** + * + * @param taxExemptionReasonText String e.g. Kleinunternehmer gemäß §19 UStG https://github.com/ZUGFeRD/mustangproject/issues/463 + * @return + */ + public Product setTaxExemptionReason(String taxExemptionReasonText) { + taxExemptionReason = taxExemptionReasonText; + return this; + } + + /*** + * + * @return e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply) + */ + @Override + public String getTaxCategoryCode() { + return taxCategoryCode; + } + + /*** + * + * @param code e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply) see also https://github.com/ZUGFeRD/mustangproject/issues/463 + * @return + */ + public Product setTaxCategoryCode(String code) { + taxCategoryCode = code; + return this; + } + @Override public String getSellerAssignedID() { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index b67e7598..4d9382ea 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -172,6 +172,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider { LineCalculator lc = new LineCalculator(currentItem); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); + String reasonText=currentItem.getProduct().getTaxExemptionReason(); + if (reasonText!=null) { + itemVATAmount.setVatExemptionReasonText(reasonText); + } VATAmount current = hm.get(percent.stripTrailingZeros()); if (current == null) { hm.put(percent.stripTrailingZeros(), itemVATAmount); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java b/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java index a7a82f61..2a7a7920 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/VATAmount.java @@ -32,6 +32,16 @@ import java.math.RoundingMode; */ public class VATAmount { + + protected BigDecimal basis, calculated, applicablePercent; + + protected String categoryCode; + + protected String vatExemptionReasonText; + + protected String dueDateTypeCode; + + public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode) { super(); this.basis = basis; @@ -48,35 +58,41 @@ public class VATAmount { this.dueDateTypeCode = dueDateTypeCode; } - BigDecimal basis, calculated, applicablePercent; - - String categoryCode; - - String dueDateTypeCode; - public BigDecimal getApplicablePercent() { return applicablePercent; } - public void setApplicablePercent(BigDecimal applicablePercent) { + public VATAmount setApplicablePercent(BigDecimal applicablePercent) { + this.applicablePercent = applicablePercent; + return this; } public BigDecimal getBasis() { return basis; } - public void setBasis(BigDecimal basis) { + public VATAmount setBasis(BigDecimal basis) { this.basis = basis.setScale(2, RoundingMode.HALF_UP); - + return this; } public BigDecimal getCalculated() { return calculated; } - public void setCalculated(BigDecimal calculated) { + public VATAmount setCalculated(BigDecimal calculated) { this.calculated = calculated; + return this; + } + + public String getVatExemptionReasonText() { + return vatExemptionReasonText; + } + + public VATAmount setVatExemptionReasonText(String theText) { + this.vatExemptionReasonText = theText; + return this; } /** @@ -94,24 +110,27 @@ public class VATAmount { * @deprecated Use {@link #setCategoryCode(String)} instead */ @Deprecated - public void setDocumentCode(String documentCode) { + public VATAmount setDocumentCode(String documentCode) { this.categoryCode = documentCode; + return this; } public String getCategoryCode() { return categoryCode; } - public void setCategoryCode(String categoryCode) { + public VATAmount setCategoryCode(String categoryCode) { this.categoryCode = categoryCode; + return this; } public String getDueDateTypeCode() { return dueDateTypeCode; } - public void setDueDateTypeCode(String dueDateTypeCode) { + public VATAmount setDueDateTypeCode(String dueDateTypeCode) { this.dueDateTypeCode = dueDateTypeCode; + return this; } public VATAmount add(VATAmount v) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index f98674c4..1b5b0242 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -51,7 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ZUGFeRD2PullProvider implements IXMLProvider { - private static final Logger LOGGER = LoggerFactory.getLogger (ZUGFeRD2PullProvider.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRD2PullProvider.class); protected byte[] zugferdData; protected IExportableTransaction trans; @@ -93,7 +93,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { try { document = DocumentHelper.parseText(new String(zugferdData)); } catch (final DocumentException e1) { - LOGGER.error ("Failed to parse ZUGFeRD data", e1); + LOGGER.error("Failed to parse ZUGFeRD data", e1); } try { final OutputFormat format = OutputFormat.createPrettyPrint(); @@ -103,7 +103,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { res = sw.toString().getBytes(StandardCharsets.UTF_8); } catch (final IOException e) { - LOGGER.error ("Failed to write ZUGFeRD data", e); + LOGGER.error("Failed to write ZUGFeRD data", e); } return res; @@ -325,7 +325,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { paymentTermsDescription += discount.getAsXRechnung(); } } else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) { - if ( trans.getDueDate() != null ) { + if (trans.getDueDate() != null) { paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate()); } } @@ -424,7 +424,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { ""; } if (currentItem.getProduct().getAttributes() != null) { - for ( Entry entry : currentItem.getProduct().getAttributes().entrySet() ) { + for (Entry entry : currentItem.getProduct().getAttributes().entrySet()) { xml += "" + "" + XMLTools.encodeXML(entry.getKey()) + "" + "" + XMLTools.encodeXML(entry.getValue()) + "" + @@ -433,8 +433,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } if (currentItem.getProduct().getCountryOfOrigin() != null) { xml += "" + - XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) + - ""; + XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) + + ""; } xml += "" @@ -654,12 +654,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { final String amountDueDateTypeCode = amount.getDueDateTypeCode(); final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode); if (getProfile() != Profiles.getByName("Minimum")) { + String exemptionReasonTextXML = ""; + if ((displayExemptionReason) && (amount.getVatExemptionReasonText() != null)) { + exemptionReasonTextXML = "" + XMLTools.encodeXML(amount.getVatExemptionReasonText()) + ""; + + } xml += "" + "" + currencyFormat(amount.getCalculated()) + "" //currencyID=\"EUR\" + "VAT" - + (displayExemptionReason ? exemptionReason : "") + + exemptionReasonTextXML + "" + currencyFormat(amount.getBasis()) + "" // currencyID=\"EUR\" + "" + amountCategoryCode + "" + (amountDueDateTypeCode != null ? "" + amountDueDateTypeCode + "" : "") @@ -681,7 +686,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) { if (profile == Profiles.getByName("XRechnung")) { - for(IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) { + for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) { xml += "" + "" + "true" + @@ -724,7 +729,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) { if (profile == Profiles.getByName("XRechnung")) { - for(IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) { + for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) { xml += "" + "" + "false" + 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 62badd8c..c400cbbc 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/model/TaxCategoryCodeTypeConstants.java @@ -30,5 +30,5 @@ public class TaxCategoryCodeTypeConstants { public static final String UNTAXEDSERVICE = "O"; public static final String INTRACOMMUNITY = "K"; - public static Set CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE).collect(Collectors.toSet()); + public static Set CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT).collect(Collectors.toSet()); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 5156950f..227383e6 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -125,7 +125,7 @@ public class ZF2PushTest extends TestCase { String number = "123"; String priceStr = "1.00"; - String senderDescription = "Kein Kleinunternehmer"; + String senderDescription = "Kleinunternehmer"; String taxID = "9990815"; BigDecimal price = new BigDecimal(priceStr); try { @@ -144,7 +144,7 @@ public class ZF2PushTest extends TestCase { .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711") .setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))) .setNumber(number) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E"), price, new BigDecimal(1.0))) ); String theXML = new String(ze.getProvider().getXML()); assertTrue(theXML.contains("