diff --git a/History.md b/History.md
index 1efe1d53..f2a968b9 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,17 @@
+- added JSONIgnore for Products intra community supply, reverse charge and invoice's isValid (which rather means isComplete, by the way)
+- #917
+- #915
+- #921
+- #926
+- upgrade apache fop 2.10 to 2.11
+- #931
+- #932
+- #933, #413, #557, #765
+- make Line Calculation, e.g. total line net amount, accessible via JSON using getCalculation
+- #940
+- #939
+- #692
+
2.19.0
=======
2025-08-12
diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java
index cca2033a..ab8bc8e9 100644
--- a/library/src/main/java/org/mustangproject/Allowance.java
+++ b/library/src/main/java/org/mustangproject/Allowance.java
@@ -39,9 +39,8 @@ public class Allowance extends Charge {
return totalAmount;
} else if (percent!=null) {
BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
-// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice);
- return singlePriceDiff;
+ return singlePriceDiff.multiply(currentItem.getQuantity());
} else {
throw new RuntimeException("percent must be set");
}
diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java
index 3d647757..524c8ce0 100644
--- a/library/src/main/java/org/mustangproject/Charge.java
+++ b/library/src/main/java/org/mustangproject/Charge.java
@@ -145,9 +145,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
if(totalAmount != null) {
return totalAmount;
} else if (percent!=null) {
- BigDecimal factor=getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP);
- BigDecimal singlePrice=currentItem.getValue().multiply(factor);
- return singlePrice;
+ BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP)));
+ BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice);
+ return singlePriceDiff.multiply(currentItem.getQuantity());
+
} else {
throw new RuntimeException("percent must be set");
}
diff --git a/library/src/main/java/org/mustangproject/FileAttachment.java b/library/src/main/java/org/mustangproject/FileAttachment.java
index 99a60419..791db6d4 100644
--- a/library/src/main/java/org/mustangproject/FileAttachment.java
+++ b/library/src/main/java/org/mustangproject/FileAttachment.java
@@ -10,7 +10,7 @@ public class FileAttachment {
protected String filename;
protected String mimetype;
protected String relation = "Unspecified";
- protected String description;
+ protected String description = "Additional file attachment";
protected byte[] data;
@@ -21,19 +21,25 @@ public class FileAttachment {
}
+ public FileAttachment(String filename, String mimetype, String relation, byte[] data, String description) {
+ this.filename = filename;
+ this.mimetype = mimetype;
+ this.relation = relation;
+ this.data = data;
+ this.description = description;
+ }
+
public FileAttachment(String filename, String mimetype, String relation, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;
this.relation = relation;
this.data = data;
- this.description = "Additional file attachment";
}
public FileAttachment(String filename, String mimetype, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;
this.data = data;
- this.description = "Additional file attachment";
}
public String getDescription() {
diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java
index 72d71a6c..d1a31537 100644
--- a/library/src/main/java/org/mustangproject/Invoice.java
+++ b/library/src/main/java/org/mustangproject/Invoice.java
@@ -23,6 +23,7 @@ package org.mustangproject;
import java.math.BigDecimal;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.*;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
@@ -393,24 +394,29 @@ public class Invoice implements IExportableTransaction {
return this;
}
+
+ @JsonIgnore
@Override
public String getOwnStreet() {
return sender.getStreet();
}
+ @JsonIgnore
@Override
public String getOwnZIP() {
return sender.getZIP();
}
+ @JsonIgnore
@Override
public String getOwnLocation() {
return sender.getLocation();
}
+ @JsonIgnore
@Override
public String getOwnCountry() {
return sender.getCountry();
@@ -754,6 +760,7 @@ public class Invoice implements IExportableTransaction {
* checks if all required items are set in order to be able to export it
* @return true if all required items are set
*/
+ @JsonIgnore
public boolean isValid() {
return (dueDate != null) && (sender != null) && (sender.getTaxID() != null) && (sender.getVATID() != null) && (recipient != null);
//contact
diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java
index b8afa6be..5a3853a0 100644
--- a/library/src/main/java/org/mustangproject/Item.java
+++ b/library/src/main/java/org/mustangproject/Item.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
+import org.mustangproject.ZUGFeRD.LineCalculator;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -203,9 +204,6 @@ public class Item implements IZUGFeRDExportableItem {
}
if (amountString != null) {
izac.setTotalAmount(new BigDecimal(amountString));
- if (percentString != null && (!percentString.equals("0"))) {
- izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity()));
- }
}
if (basisAmountString != null) {
izac.setBasisAmount(new BigDecimal(basisAmountString));
diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java
index 467fbb60..6aca773a 100644
--- a/library/src/main/java/org/mustangproject/Product.java
+++ b/library/src/main/java/org/mustangproject/Product.java
@@ -214,11 +214,14 @@ public class Product implements IZUGFeRDExportableProduct {
return this;
}
+ @JsonIgnore
@Override
public boolean isReverseCharge() {
return isReverseCharge;
}
+
+ @JsonIgnore
@Override
public boolean isIntraCommunitySupply() {
return isIntraCommunitySupply;
diff --git a/library/src/main/java/org/mustangproject/SubjectCode.java b/library/src/main/java/org/mustangproject/SubjectCode.java
index 808c09d4..539314d1 100644
--- a/library/src/main/java/org/mustangproject/SubjectCode.java
+++ b/library/src/main/java/org/mustangproject/SubjectCode.java
@@ -40,5 +40,9 @@ public enum SubjectCode {
/**
* Vehicle licence number
*/
- ABZ
+ ABZ,
+ /**
+ * Payment information
+ */
+ PMT
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
index 9bac1b07..7caa3e52 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
@@ -79,7 +79,7 @@ public class DAPullProvider extends ZUGFeRD2PullProvider {
if (currentItem.getProduct().getTaxExemptionReason() != null) {
// exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "";
}
- final LineCalculator lc = new LineCalculator(currentItem);
+ final LineCalculator lc = currentItem.getCalculation();
xml += "" +
""
+ "" + lineID + ""
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java
index 3c4be8b5..cf3e50dc 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IAbsoluteValueProvider.java
@@ -24,4 +24,7 @@ public interface IAbsoluteValueProvider {
public BigDecimal getValue();
+ default BigDecimal getQuantity() {
+ return BigDecimal.ONE;
+ }
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
index 9ce55ef1..c7a6d903 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
@@ -181,6 +182,7 @@ public interface IExportableTransaction {
*
* @return Tax ID (not VAT ID) of the sender
*/
+ @JsonIgnore
default String getOwnTaxID() {
if (getSender() != null) {
return getSender().getTaxID();
@@ -194,6 +196,7 @@ public interface IExportableTransaction {
*
* @return VAT ID (Umsatzsteueridentifikationsnummer) of the sender
*/
+ @JsonIgnore
default String getOwnVATID() {
if (getSender() != null) {
return getSender().getVATID();
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
index 04cadc22..05d86170 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
@@ -205,4 +205,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
default String getAccountingReference() {
return null;
}
+
+ default LineCalculator getCalculation() {return new LineCalculator(this); };
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
index 50ea54ae..33f883f4 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
@@ -24,26 +24,17 @@ public class LineCalculator {
if (currentItem.getItemAllowances() != null) {
for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
- BigDecimal factor=BigDecimal.ONE;
BigDecimal singleAllowance=allowance.getTotalAmount(currentItem);
addItemAllowance(singleAllowance);
-
- if ((allowance.getPercent()!=null)&&(allowance.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
- factor=currentItem.getQuantity();
- }
- addAllowanceItemTotal(singleAllowance.multiply(factor));
+ addAllowanceItemTotal(singleAllowance);
}
}
if (currentItem.getItemCharges() != null) {
for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
- BigDecimal factor=BigDecimal.ONE;
BigDecimal singleCharge=charge.getTotalAmount(currentItem);
addItemCharge(singleCharge);
- if ((charge.getPercent()!=null)&&(charge.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
- factor=currentItem.getQuantity();
- }
- subtractAllowanceItemTotal(singleCharge.multiply(factor));
+ subtractAllowanceItemTotal(singleCharge);
}
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
index f2a032f2..7659b526 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
@@ -107,7 +107,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
// exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "";
}
- final LineCalculator lc = new LineCalculator(currentItem);
+ final LineCalculator lc = currentItem.getCalculation();
xml += "" +
""
+ "" + lineID + ""
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
index 02ae965d..f8c80127 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
@@ -195,7 +195,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
percent = currentItem.getProduct().getVATPercent();
}
if (percent != null) {
- LineCalculator lc = new LineCalculator(currentItem);
+ LineCalculator lc = currentItem.getCalculation();
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
String reasonText = currentItem.getProduct().getTaxExemptionReason();
@@ -265,7 +265,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
if (percent != null)
{
- final LineCalculator lc = new LineCalculator(currentItem);
+ 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();
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
index 87e6f479..8c74e361 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
@@ -344,7 +344,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
}
- final LineCalculator lc = new LineCalculator(currentItem);
+ final LineCalculator lc = currentItem.getCalculation();
xml += "" +
""
+ "" + lineID + ""
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
index 87ffb274..c51cb60c 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
@@ -415,7 +415,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if (currentItem.getId() != null) {
lineIDStr = currentItem.getId();
}
- final LineCalculator lc = new LineCalculator(currentItem);
+ final LineCalculator lc = currentItem.getCalculation();
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
xml += "" +
""
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
index fce06f01..4442443a 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
@@ -8,21 +8,8 @@ import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
-import org.mustangproject.Allowance;
-import org.mustangproject.BankDetails;
-import org.mustangproject.CalculatedInvoice;
-import org.mustangproject.Charge;
-import org.mustangproject.DirectDebit;
-import org.mustangproject.EStandard;
+import org.mustangproject.*;
import org.mustangproject.Exceptions.StructureException;
-import org.mustangproject.FileAttachment;
-import org.mustangproject.IncludedNote;
-import org.mustangproject.Invoice;
-import org.mustangproject.Item;
-import org.mustangproject.ReferencedDocument;
-import org.mustangproject.SchemedID;
-import org.mustangproject.TradeParty;
-import org.mustangproject.XMLTools;
import org.mustangproject.util.NodeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,6 +48,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -606,10 +595,12 @@ public class ZUGFeRDInvoiceImporter {
}
zpp.addNotes(includedNotes);
String rootNode = extractString("local-name(/*)");
+ String potentialCashDiscountTerms=null;
if (rootNode != null && Set.of("Invoice", "CreditNote").contains(rootNode)) {
// UBL...
// //*[local-name()="Invoice" or local-name()="CreditNote"]
number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").trim();
+ potentialCashDiscountTerms = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]").trim();
typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim();
String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim();
if (!issueDateStr.isEmpty()) {
@@ -623,6 +614,10 @@ public class ZUGFeRDInvoiceImporter {
if (!deliveryDt.isEmpty()) {
deliveryDate = parseDate(deliveryDt, "yyyy-MM-dd");
}
+ } else {
+ //CII
+ potentialCashDiscountTerms = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]").trim();
+
}
String creditorReferenceID = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"CreditorReferenceID\"]").trim();//BT-90
@@ -994,6 +989,12 @@ public class ZUGFeRDInvoiceImporter {
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) {
FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
+ NodeList nl = attachmentNodes.item(i).getParentNode().getChildNodes();
+ for (int j = 0; j < nl.getLength(); j++) {
+ if (nl.item(j).getLocalName() != null && nl.item(j).getLocalName().equals("Name")) {
+ fa.setDescription(nl.item(j).getTextContent());
+ }
+ }
zpp.embedFileInXML(fa);
// filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
@@ -1121,6 +1122,63 @@ public class ZUGFeRDInvoiceImporter {
}
}
+ xpr = xpath.compile("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"ApplicableTradePaymentDiscountTerms\"]");// cash discounts, UBL unknown
+ NodeList cashdiscountNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
+ for (int i = 0; i < cashdiscountNodes.getLength(); i++) {
+ NodeList cashDiscountNodeChilds = cashdiscountNodes.item(i).getChildNodes();
+ String chargeAmount = null;
+ String taxPercent = null;
+ CashDiscount cd=new CashDiscount();
+ for (int cashDiscountChildIndex = 0; cashDiscountChildIndex < cashDiscountNodeChilds.getLength(); cashDiscountChildIndex++) {
+ Node currentNode=cashDiscountNodeChilds.item(cashDiscountChildIndex);
+ String chargeChildName = currentNode.getLocalName();
+ if (chargeChildName != null) {
+ if (chargeChildName.equals("BasisPeriodMeasure")) {
+ if (currentNode.getAttributes().getNamedItem("unitCode").getNodeValue().equals("DAY")) {
+ cd.setDays(Integer.valueOf(XMLTools.trimOrNull(currentNode)));
+ }
+ } else if (chargeChildName.equals("CalculationPercent")) {
+ cd.setPercent(new BigDecimal(XMLTools.trimOrNull(currentNode)));
+ }
+ }
+ //appliedAmount
+ //AppliedTradeTax
+ }
+ if ((cd.getPercent() != null)&&(cd.getDays() != null)) {
+ zpp.addCashDiscount(cd);
+ }
+ }
+ if ((potentialCashDiscountTerms!=null&&potentialCashDiscountTerms.length()>3)) {
+ for (String currentLine:potentialCashDiscountTerms.split("\\n")) {
+ if (currentLine.startsWith("#SKONTO#")) {
+ CashDiscount cd=new CashDiscount();
+ Pattern pattern = Pattern.compile("#TAGE=(.*?)#", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(currentLine);
+ boolean daysFound = matcher.find();
+ String days=matcher.group(1);
+ pattern = Pattern.compile("#PROZENT=(.*?)#", Pattern.CASE_INSENSITIVE);
+ matcher = pattern.matcher(currentLine);
+ boolean percentFound = matcher.find();
+ String percent=matcher.group(1);
+
+ if (daysFound&&percentFound) {
+ cd.setDays(Integer.valueOf(days));
+ cd.setPercent(new BigDecimal(percent));
+ zpp.addCashDiscount(cd);
+ } //else : could not parse skonto
+
+
+
+
+/*
+ String percent=;
+
+ cd.setDays()
+ cd.setPercent()*/
+
+ }
+ }
+ }
TransactionCalculator tc = new TransactionCalculator(zpp);
String calculatedPayableTotal = tc.getDuePayable().toPlainString();
@@ -1140,7 +1198,7 @@ public class ZUGFeRDInvoiceImporter {
try {
moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
+ Stream.of(tc.trans.getZFItems())
- .map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
+ .map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString())
.collect(Collectors.joining(" + "));
} catch (Exception ignored) {
}
diff --git a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl
index de3c25aa..5255ab52 100644
--- a/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl
+++ b/library/src/main/resources/stylesheets/xr-pdf/lib/structure/content-templates.xsl
@@ -194,7 +194,12 @@
-
+
+
+
+ ;
+
+
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
index f386d655..d7fd83af 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
@@ -5,12 +5,15 @@ import static java.math.BigDecimal.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.xmlunit.assertj.XmlAssert.assertThat;
import org.junit.Test;
import org.mustangproject.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.xmlunit.builder.Input;
+import javax.xml.transform.Source;
import javax.xml.xpath.XPathExpressionException;
import java.io.*;
import java.math.BigDecimal;
@@ -32,7 +35,7 @@ public class CalculationTest extends ResourceCase {
.setQuantity(TEN)
.setProduct(product);
- final LineCalculator calculator = new LineCalculator(currentItem);
+ final LineCalculator calculator = currentItem.getCalculation();
assertEquals(valueOf(100).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
assertEquals(valueOf(1000).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
@@ -51,7 +54,7 @@ public class CalculationTest extends ResourceCase {
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
.setProduct(product);
- final LineCalculator calculator = new LineCalculator(currentItem);
+ final LineCalculator calculator = currentItem.getCalculation();
assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
assertEquals(valueOf(1769.89).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
@@ -71,7 +74,7 @@ public class CalculationTest extends ResourceCase {
.setItemCharges(new IZUGFeRDAllowanceCharge[]{charge})
.setProduct(product);
- final LineCalculator calculator = new LineCalculator(currentItem);
+ final LineCalculator calculator = currentItem.getCalculation();
assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
assertEquals(valueOf(1799.63).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
@@ -110,13 +113,13 @@ public class CalculationTest extends ResourceCase {
product.addAllowance(new Allowance(new BigDecimal(1)));
item = new Item(product, new BigDecimal("9.50"), new BigDecimal(25));
item.addCharge(new Charge(new BigDecimal(10)).setReasonCode("ZZZ").setReason("Zuschlag"));
- LineCalculator lc = new LineCalculator(item);
+ LineCalculator lc = item.getCalculation();
assertEquals(new BigDecimal("222.50"), lc.getItemTotalNetAmount());
invoice.addItem(item);
product = new Product("Paper", "", "H87", new BigDecimal(25));
item = new Item(product, new BigDecimal("4.50"), new BigDecimal(15));
item.addAllowance(new Allowance().setPercent(new BigDecimal(5)).setReasonCode("ZZZ").setReason("Zuschlag"));
- lc = new LineCalculator(item);
+ lc = item.getCalculation();
assertEquals(new BigDecimal("64.12"), lc.getItemTotalNetAmount());
invoice.addItem(item);
invoice.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(new BigDecimal(25)).setReasonCode("ZZZ").setReason("Mengenrabatt"));
@@ -162,6 +165,40 @@ public class CalculationTest extends ResourceCase {
}
+/* @Test
+ public void testRounding() {
+/*** xml of official fx sample with allowances and charges
+ * 10x100 with 10% and 50€ item discount =850€
+ * +8,75 charges on document level=858,75, +19%VAT=1021,91
+ * prepaid 500->due payable=521,91
+ *
+ File inputCII = getResourceAsFile("EN16931_1_Teilrechnung_corrected.xml");
+
+ ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
+ Invoice invoice = null;
+ zii.doIgnoreCalculationErrors();
+ boolean hasExceptions = false;
+ try {
+ zii.setInputStream(new FileInputStream(inputCII));
+
+ invoice = zii.extractInvoice();
+ } catch (XPathExpressionException | ParseException e) {
+// handle Exceptions
+ hasExceptions = true;
+ } catch (FileNotFoundException e) {
+ hasExceptions = true;
+ }
+ assertFalse(hasExceptions);
+ // Reading ZUGFeRD
+
+ final TransactionCalculator calculator = new TransactionCalculator(invoice);
+
+ assertEquals(valueOf(147.59).stripTrailingZeros(), calculator.getDuePayable().stripTrailingZeros());
+
+
+ }
+*/
+
@Test
public void testTotalCalculatorGrandTotalRounding() {
SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd");
@@ -283,10 +320,76 @@ public class CalculationTest extends ResourceCase {
item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO));
invoice.addItem(item);
+
+ ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
+ zf2p.setProfile(Profiles.getByName("XRechnung"));
+ zf2p.generateXML(invoice);
+
+
+ String theXML = new String(zf2p.getXML());
+ assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']")
+ .asString()
+ .isEqualTo("0.55");// test for issue #917
+
+
TransactionCalculator calculator = new TransactionCalculator(invoice);
assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros());
}
+ public void testSimpleItemPercentCharge() {
+ /***
+ * a product with net 1.10 and qty 5 and relative item allowance of 10% should return 5 as line and grand total
+ */
+ SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd");
+
+ 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.error("Failed to set dates", e);
+
+ }
+ 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;
+
+ product = new Product("AAA", "", "H87", BigDecimal.ZERO);
+ item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00));
+
+ item.addCharge(new Charge().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO));
+ invoice.addItem(item);
+
+
+ ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
+ zf2p.setProfile(Profiles.getByName("XRechnung"));
+ zf2p.generateXML(invoice);
+
+
+ String theXML = new String(zf2p.getXML());
+ assertThat(theXML).valueByXPath("//*[local-name()='ActualAmount']")
+ .asString()
+ .isEqualTo("0.55");// test for issue #917
+
+
+ TransactionCalculator calculator = new TransactionCalculator(invoice);
+ assertEquals(new BigDecimal("6.05"), calculator.getGrandTotal().stripTrailingZeros());
+ }
+
public void testSimpleDocumentPercentCharge() {
String orgname = "Test company";
@@ -390,7 +493,7 @@ public class CalculationTest extends ResourceCase {
.setQuantity(BigDecimal.valueOf(31))
.setBasisQuantity(BigDecimal.valueOf(366))
.setProduct(product);
- final LineCalculator calculator = new LineCalculator(currentItem);
+ final LineCalculator calculator = currentItem.getCalculation();
assertEquals(BigDecimal.valueOf(32.74), calculator.getItemTotalNetAmount());
}
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
index 6f0a910c..8c8dbd7d 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
@@ -479,6 +479,20 @@ public class DeSerializationTest extends ResourceCase {
assertEquals("sender@test.org", fromJSON.getSender().getEmail());
}
+ public void testItemAbsoluteChargeFromJSON() throws JsonProcessingException {
+ String globalID = "4000001123452";
+ String globalIDScheme = "0088";
+
+ String json="{\"number\":\"471102\",\"currency\":\"EUR\",\"issueDate\":\"2018-03-04T00:00:00.000+01:00\",\"dueDate\":\"2018-03-04T00:00:00.000+01:00\",\"deliveryDate\":\"2018-03-04T00:00:00.000+01:00\",\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"globalID\":\"4000001123452\",\"globalIDScheme\":\"0088\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\"},\"zfitems\":[{\"price\":9.9,\"quantity\":20,\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"description\":\"\",\"vatpercent\":19,\"taxCategoryCode\":\"S\"},\"itemCharges\":[{\"totalAmount\":1,\"taxPercent\":19,\"reason\":\"Invoice line charge reason\",\"categoryCode\":\"S\"}]},{\"price\":5.5,\"quantity\":50,\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"description\":\"\",\"vatpercent\":7,\"taxCategoryCode\":\"S\"}}]}";
+
+ ObjectMapper mapper = new ObjectMapper();
+ Invoice fromJSON = mapper.readValue(json, Invoice.class);
+ assertEquals(globalID, fromJSON.getSender().getGlobalID());
+ assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme());
+ TransactionCalculator tc=new TransactionCalculator(fromJSON);
+ assertEquals(new BigDecimal("531.06"), tc.getDuePayable());
+ }
+
public void testGrossFromJSON() throws JsonProcessingException {
String json="{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 28.07.2025\", \"issueDate\": 1753653600000, \"dueDate\": 1753653600000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"lineTotalAmount\": 29.00, \"duePayable\": 34.51, \"grandTotal\": 34.51, \"taxBasis\": 29.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 10.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"allowances\": [ { \"totalAmount\": 0.1000, \"categoryCode\": \"S\" } ], \"vatpercent\": 19.00, \"intraCommunitySupply\": false, \"reverseCharge\": false }, \"value\": 3.0000 } ], \"ownVATID\": \"DE0815\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\", \"ownCountry\": \"DE\", \"ownStreet\": \"teststr\"}";
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
index 42fd653c..095bafa0 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
@@ -102,7 +102,7 @@ public class XRTest extends TestCase {
BigDecimal amount = new BigDecimal(amountStr);
byte[] b = {12, 13};
- FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b);
+ FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b,"Beschreibung");
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX").setAccountName("kontoInhaber")))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setEmail("recipient@sample.org"))
@@ -158,7 +158,7 @@ public class XRTest extends TestCase {
assertEquals(attachedFiles.length, 1);
assertTrue(Arrays.equals(attachedFiles[0].getData(), b));
-
+ assertEquals("Beschreibung",attachedFiles[0].getDescription());
}
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
index 47ac24cb..b99f7108 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
@@ -35,7 +35,6 @@ import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -235,9 +234,9 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
}
- public void testSpecifiedLogisticsChargeImport() {
+ public void testSpecifiedLogisticsChargeCashDiscountImport() {
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
- File expectedResult = getResourceAsFile("cii/extended_warenrechnung.xml");
+ File expectedResult = getResourceAsFile("cii/extended_warenrechnung_based_doublecashdiscount.xml");
boolean hasExceptions = false;
@@ -249,9 +248,11 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
hasExceptions = true;
}
assertFalse(hasExceptions);
+ assertEquals(invoice.getCashDiscounts().length,2);
TransactionCalculator tc = new TransactionCalculator(invoice);
assertEquals(new BigDecimal("518.99"), tc.getGrandTotal());
+
}
public void testItemAllowancesChargesImport() {
@@ -347,10 +348,10 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
TransactionCalculator tc = new TransactionCalculator(invoice);
assertEquals(new BigDecimal("1.00"), tc.getGrandTotal());
-
+ assertEquals(invoice.getCashDiscounts().length,2);
assertEquals(version,2);
assertTrue(new BigDecimal("1").compareTo(invoice.getZFItems()[0].getQuantity()) == 0);
- LineCalculator lc=new LineCalculator(invoice.getZFItems()[0]);
+ LineCalculator lc=invoice.getZFItems()[0].getCalculation();
assertTrue(new BigDecimal("1").compareTo(lc.getItemTotalNetAmount()) == 0);
assertEquals("Z", invoice.getZFItems()[0].getProduct().getTaxCategoryCode());
@@ -407,7 +408,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
ObjectMapper mapper = new ObjectMapper();
String jsonArray = mapper.writeValueAsString(i);
- JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false);
+ JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}]}",jsonArray,false);
} catch (IOException e) {
fail("IOException not expected");
@@ -439,7 +440,153 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000);
String expectedPaymentTermDesciption="Please remit until "+german.format(now);
- JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true);
+ JSONAssert.assertEquals("{\n" +
+ " \"documentCode\" : \"380\",\n" +
+ " \"number\" : \"123\",\n" +
+ " \"currency\" : \"EUR\",\n" +
+ " \"paymentTermDescription\" : "+expectedPaymentTermDesciption+",\n" +
+ " \"issueDate\" : "+expectedIssueDate+",\n" +
+ " \"dueDate\" : "+expectedDueDate+",\n" +
+ " \"sender\" : {\n" +
+ " \"name\" : \"Test company\",\n" +
+ " \"zip\" : \"55232\",\n" +
+ " \"street\" : \"teststr\",\n" +
+ " \"location\" : \"teststadt\",\n" +
+ " \"country\" : \"DE\",\n" +
+ " \"taxID\" : \"4711\",\n" +
+ " \"vatID\" : \"DE0815\",\n" +
+ " \"vatid\" : \"DE0815\"\n" +
+ " },\n" +
+ " \"recipient\" : {\n" +
+ " \"name\" : \"Franz Müller\",\n" +
+ " \"zip\" : \"55232\",\n" +
+ " \"street\" : \"teststr.12\",\n" +
+ " \"location\" : \"Entenhausen\",\n" +
+ " \"country\" : \"DE\",\n" +
+ " \"contact\" : {\n" +
+ " \"name\" : \"contact testname\",\n" +
+ " \"phone\" : \"123456\",\n" +
+ " \"email\" : \"contact.testemail@example.org\",\n" +
+ " \"fax\" : \"0911623562\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"totalPrepaidAmount\" : 0.0,\n" +
+ " \"zfitems\" : [ {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"quantity\" : 1.0,\n" +
+ " \"basisQuantity\" : 1.0,\n" +
+ " \"id\" : \"1\",\n" +
+ " \"product\" : {\n" +
+ " \"unit\" : \"H87\",\n" +
+ " \"name\" : \"Testprodukt\",\n" +
+ " \"taxCategoryCode\" : \"S\",\n" +
+ " \"vatpercent\" : 19.0\n" +
+ " },\n" +
+ " \"itemAllowances\" : [ {\n" +
+ " \"totalAmount\" : 0.1,\n" +
+ " \"taxPercent\" : 0,\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ],\n" +
+ " \"value\" : 3.0,\n" +
+ " \"calculation\" : {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"priceGross\" : 3.0,\n" +
+ " \"itemTotalNetAmount\" : 2.9,\n" +
+ " \"itemTotalVATAmount\" : 0.551,\n" +
+ " \"itemTotalGrossAmount\" : 2.9\n" +
+ " }\n" +
+ " }, {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"quantity\" : 1.0,\n" +
+ " \"basisQuantity\" : 1.0,\n" +
+ " \"id\" : \"2\",\n" +
+ " \"product\" : {\n" +
+ " \"unit\" : \"H87\",\n" +
+ " \"name\" : \"Testprodukt\",\n" +
+ " \"taxCategoryCode\" : \"S\",\n" +
+ " \"vatpercent\" : 19.0\n" +
+ " },\n" +
+ " \"itemAllowances\" : [ {\n" +
+ " \"percent\" : 50.0,\n" +
+ " \"totalAmount\" : 1.5,\n" +
+ " \"basisAmount\" : 3.0,\n" +
+ " \"taxPercent\" : 0,\n" +
+ " \"reason\" : \"In love with salesperson\",\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ],\n" +
+ " \"value\" : 3.0,\n" +
+ " \"calculation\" : {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"priceGross\" : 3.0,\n" +
+ " \"itemTotalNetAmount\" : 1.5,\n" +
+ " \"itemTotalVATAmount\" : 0.285,\n" +
+ " \"itemTotalGrossAmount\" : 1.5\n" +
+ " }\n" +
+ " }, {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"quantity\" : 2.0,\n" +
+ " \"basisQuantity\" : 1.0,\n" +
+ " \"id\" : \"3\",\n" +
+ " \"product\" : {\n" +
+ " \"unit\" : \"H87\",\n" +
+ " \"name\" : \"Testprodukt\",\n" +
+ " \"taxCategoryCode\" : \"S\",\n" +
+ " \"vatpercent\" : 19.0\n" +
+ " },\n" +
+ " \"itemCharges\" : [ {\n" +
+ " \"totalAmount\" : 1.0,\n" +
+ " \"taxPercent\" : 0,\n" +
+ " \"reason\" : \"AnotherReason\",\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ],\n" +
+ " \"value\" : 3.0,\n" +
+ " \"calculation\" : {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"priceGross\" : 3.0,\n" +
+ " \"itemTotalNetAmount\" : 7.0,\n" +
+ " \"itemTotalVATAmount\" : 1.33,\n" +
+ " \"itemTotalGrossAmount\" : 7.0\n" +
+ " }\n" +
+ " }, {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"quantity\" : 1.0,\n" +
+ " \"basisQuantity\" : 1.0,\n" +
+ " \"id\" : \"4\",\n" +
+ " \"product\" : {\n" +
+ " \"unit\" : \"H87\",\n" +
+ " \"name\" : \"Testprodukt\",\n" +
+ " \"taxCategoryCode\" : \"S\",\n" +
+ " \"vatpercent\" : 19.0\n" +
+ " },\n" +
+ " \"itemAllowances\" : [ {\n" +
+ " \"totalAmount\" : 1.0,\n" +
+ " \"taxPercent\" : 0,\n" +
+ " \"reason\" : \"Something completely strange\",\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ],\n" +
+ " \"itemCharges\" : [ {\n" +
+ " \"totalAmount\" : 1.0,\n" +
+ " \"taxPercent\" : 0,\n" +
+ " \"reason\" : \"Yet another reason\",\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ],\n" +
+ " \"value\" : 3.0,\n" +
+ " \"calculation\" : {\n" +
+ " \"price\" : 3.0,\n" +
+ " \"priceGross\" : 3.0,\n" +
+ " \"itemTotalNetAmount\" : 3.0,\n" +
+ " \"itemTotalVATAmount\" : 0.57,\n" +
+ " \"itemTotalGrossAmount\" : 3.0\n" +
+ " }\n" +
+ " } ],\n" +
+ " \"zfcharges\" : [ {\n" +
+ " \"totalAmount\" : 1.0,\n" +
+ " \"taxPercent\" : 19.0,\n" +
+ " \"reason\" : \"AReason\",\n" +
+ " \"reasonCode\" : \"ABK\",\n" +
+ " \"categoryCode\" : \"S\"\n" +
+ " } ]\n" +
+ "}",jsonArray,true);
} catch (IOException e) {
fail("IOException not expected");
} catch (XPathExpressionException e) {
diff --git a/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml
new file mode 100644
index 00000000..8ffdde89
--- /dev/null
+++ b/library/src/test/resources/EN16931_1_Teilrechnung_corrected.xml
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
+
+
+
+ urn:cen.eu:en16931:2017
+
+
+
+ 471102
+ 380
+
+ 20180605
+
+
+ Rechnung gemäß Bestellung Nr. 2018-471331 vom 01.03.2018.
+
+
+ Es bestehen Rabatt- und Bonusvereinbarungen.
+ AAK
+
+
+ Lieferant GmbH
+Lieferantenstraße 20
+80333 München
+Deutschland
+Geschäftsführer: Hans Muster
+Handelsregisternummer: H A 123
+
+ REG
+
+
+
+
+
+ 1
+
+ Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2018-51112 in Rechnung zu stellen:
+
+
+
+ 4012345001235
+ KR3M
+
+ Kunstrasen grün 3m breit
+ 300cm x 100 cm
+
+
+
+ 4.0000
+
+
+ false
+
+ 0.6667
+
+
+
+ 3.3333
+
+
+
+ 3.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 10.00
+
+
+
+
+
+ 2
+
+ Bestellt wurden 5 kg Schweinesteak. Mit dieser Rechnung werden nur die bereits gelieferten Steaks berechnet. Die noch offenen 4 kg Schweinesteak werden separat geliefert und berechnet.
+
+
+
+ 4000050986428
+ SFK5
+
+ Schweinesteak
+ Schweinesteak aus Deutschland
+
+
+
+ 5.5000
+
+
+ 5.5000
+
+
+
+ 1.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 5.50
+
+
+
+
+
+ 3
+
+
+ 4000001234561
+ GTRWA5
+
+ Mineralwasser Medium
+12 x 1,0l PET
+
+
+
+
+ 5.4900
+
+
+ 5.4900
+
+
+
+ 20.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 109.80
+
+
+
+
+
+ 4
+
+
+ 4000001234578
+ PFA5
+
+ Pfand
+
+
+
+ 2.7700
+
+
+ 2.7700
+
+
+
+ 20.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 55.46
+
+
+
+
+
+ 549910
+ 4000001123452
+ Lieferant GmbH
+
+ 80333
+ Lieferantenstraße 20
+ München
+ DE
+
+
+ 201/113/40209
+
+
+ DE123456789
+
+
+
+ GE2020211
+ Kunden AG Mitte
+
+ 69876
+ Kundenstraße 15
+ Frankfurt
+ DE
+
+
+
+ 2018-471331
+
+
+
+
+
+ 20180603
+
+
+
+
+ EUR
+
+ 7.91
+ VAT
+ 113.03
+ S
+ 7.00
+
+
+ 12.25
+ VAT
+ 64.46
+ S
+ 19.00
+
+
+
+ false
+
+ 10.00
+ 1.00
+ Sondernachlass
+
+ VAT
+ S
+ 19.00
+
+
+
+
+ false
+
+ 115.30
+ 8.07
+ Sondernachlass
+
+ VAT
+ S
+ 7.00
+
+
+
+
+ true
+
+ 115.30
+ 5.80
+ Versandkosten
+
+ VAT
+ S
+ 7.00
+
+
+
+ Zahlbar innerhalb 30 Tagen netto bis 04.07.2018, 3% Skonto innerhalb 10 Tagen bis 15.06.2018
+
+
+ 180.70
+ 5.80
+ 9.07
+ 177.43
+ 20.16
+ 197.59
+ 50.00
+ 147.59
+
+
+
+
diff --git a/library/src/test/resources/cii/extended_warenrechnung.xml b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml
similarity index 78%
rename from library/src/test/resources/cii/extended_warenrechnung.xml
rename to library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml
index 46665e1a..b425194a 100644
--- a/library/src/test/resources/cii/extended_warenrechnung.xml
+++ b/library/src/test/resources/cii/extended_warenrechnung_based_doublecashdiscount.xml
@@ -1,90 +1,4 @@
-
-
-
-
-
@@ -545,13 +459,20 @@ WEEE-Reg-Nr.: DE87654321
19.00
-
- Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto.
-
- 14
- 2.00
-
-
+
+ Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto.
+
+ 14
+ 2.00
+
+
+
+ Bei Zahlung innerhalb 7 Tagen gewähren wir 1,0% Skonto.
+
+ 7
+ 1.00
+
+
457.20
3.00
@@ -564,4 +485,4 @@ WEEE-Reg-Nr.: DE87654321
-
\ No newline at end of file
+
diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
index 4c5edb88..ba939eaa 100644
--- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
+++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
@@ -14,6 +14,7 @@ public class ValidationContext {
private String profile = null;
private String signature = null;
private boolean isValid = true;
+ private boolean hasPDF = false;
protected Logger logger;
private String filename;
@@ -22,6 +23,14 @@ public class ValidationContext {
results = new Vector<>();
}
+ public void setHasPDF() {
+ hasPDF=true;
+ }
+
+ public boolean hasPDF() {
+ return hasPDF;
+ }
+
public void addResultItem(ValidationResultItem vr) throws IrrecoverableValidationError {
results.add(vr);
diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
index 5e8ce312..8d827418 100644
--- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
@@ -374,6 +374,14 @@ public class XMLValidator extends Validator {
addUnsupportedProfileResultItem();
}
}
+ } else {
+ // no CII -> has to be UBL
+ if (context.hasPDF()) {
+ final ValidationResultItem vri = new ValidationResultItem(ESeverity.error, "Factur-X/ZUGFeRD and Order-X are always strictly CII only, no UBL allowed.").setSection(17)
+ .setPart(EPart.fx);
+ context.addResultItem(vri);
+
+ }
}
if (xsltFilename != null) {
@@ -536,12 +544,15 @@ public class XMLValidator extends Validator {
}
ESeverity severity;
+ Node failNode = currentFailNode.getAttributes().getNamedItem("flag");
+ String failVal = failNode == null ? null : failNode.getNodeValue();
if (defaultSeverity == ESeverity.notice) {
severity = defaultSeverity;
- } else if (currentFailNode.getAttributes().getNamedItem("flag") != null
- && "warning".equals(currentFailNode.getAttributes().getNamedItem("flag").getNodeValue())) {
+ } else if ("warning".equals(failVal)) {
// the XR issues warnings with flag=warning
severity = ESeverity.warning;
+ } else if ("information".equals(failVal)) {
+ severity = ESeverity.notice;
} else {
severity = ESeverity.error;
}
diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java
index bb5e9576..e162edd9 100644
--- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java
@@ -121,6 +121,7 @@ public class ZUGFeRDValidator {
// Avoid reading again from file
pdfv.setFilenameAndContents(contextFilename, content);
+ context.setHasPDF();
optionsRecognized = true;
finalStringResult.append("");
try {
diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
index 4adb787b..5e28bdec 100644
--- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
+++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
@@ -202,7 +202,7 @@ public class ZUGFeRDValidatorTest extends ResourceCase {
assertThat(res).valueByXPath("count(//notice)")
.asInt()
- .isEqualTo(0);
+ .isEqualTo(1);
assertThat(res).valueByXPath("/validation/summary/@status")
.asString()
.isEqualTo("valid");// expect to be valid because XR notices are, well, only notices
@@ -216,14 +216,14 @@ public class ZUGFeRDValidatorTest extends ResourceCase {
assertThat(res).valueByXPath("count(//error)")
.asInt()
- .isEqualTo(5);
+ .isEqualTo(4);
assertThat(res).valueByXPath("count(//warning)")
.asInt()
.isEqualTo(1);
assertThat(res).valueByXPath("count(//notice)")
.asInt()
- .isEqualTo(0); // 12 notices RE XRechnung 3.0
+ .isEqualTo(1); // 12 notices RE XRechnung 3.0
assertThat(res).valueByXPath("/validation/summary/@status")
.asString()
.isEqualTo("invalid");// expect to be valid
diff --git a/validator/src/test/resources/validXRV30.xml b/validator/src/test/resources/validXRV30.xml
index adffb992..93caadfb 100644
--- a/validator/src/test/resources/validXRV30.xml
+++ b/validator/src/test/resources/validXRV30.xml
@@ -102,6 +102,32 @@
+
+
+ Zeitlose Dienstleistung
+
+
+ Zeitlose Dienstleistung
+
+
+
+ 0.00
+
+
+
+ 1
+
+
+
+ VAT
+ S
+ 7
+
+
+ 0.00
+
+
+
04011000-12345-03