diff --git a/History.md b/History.md
index cdd3208c..256e7c9f 100644
--- a/History.md
+++ b/History.md
@@ -1,17 +1,29 @@
+2.18.1
+=======
+- #893 Tradeparty globalID is not read from JSON
+
+2.18.0
+=======
+2025-07-14
+
- support parsing of BT-90 CreditorReferenceID
-- #870
-- #871
-- #868
-- #729
-- #863
-- #731
-- #865
-- #849
-- #856
-- #850/#843
-- #855
-- #874
-- #876/830
+- #871 schema validation does not ignore external entities
+- #868 Fix wrong version in History.md
+- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans
+- #863 LineCalculator throws NPE if product is null (since 2.17.0)
+- #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type)
+- #865 Add sevdesk signature to PDF creators
+- #849 Ignore calculation errors when extracting xml from pdf
+- #856 Read contact´s fax number.
+- #850/#843 Correction for "Re-Initialize the HTML-template on language change
+- #855 Suppress empty nodes in output XML
+- #874 Skip PDNameTreeNodes if the names are null or empty
+- #830 Invalid XML generated: Item vat-category-code summed up with other 0 percent category codes
+- #878 report arithmetic issues in validation report
+- corrected typo ArithmetricException to ArithmeticException
+- #726 Financial account information (IBAN) is lost when converting a cii invoice to ubl
+- #885 JSON duplicates on item allowances/charges
+- #887 incorrect percentual item allowances
2.17.0
=======
diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml
index 2922415d..22f41162 100644
--- a/Mustang-CLI/pom.xml
+++ b/Mustang-CLI/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
4.0.0
org.mustangproject
@@ -12,7 +12,7 @@
should also work for XRechnung/CII.
jar
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
UTF-8
11
@@ -23,7 +23,7 @@
org.mustangproject
validator
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
@@ -106,8 +106,10 @@
org.apache.maven.plugins
maven-surefire-plugin
+ 3.5.3
alphabetical
+ -Duser.timezone=UTC
diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java
index b2ea96fc..1657a91c 100755
--- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java
+++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java
@@ -371,17 +371,17 @@ public class Main {
boolean optionsRecognized = false;
String action = "";
- Boolean disableFileLogging = false;
+ boolean disableFileLogging = false;
try {
cmd = parser.parse(options, args);
// Retrieve all options
action = cmd.getOptionValue("action");
String directoryName = cmd.getOptionValue("directory");
- Boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue();
- Boolean ignoreFileExt = cmd.hasOption("ignorefileextension");
- Boolean noAttachments = cmd.hasOption("no-additional-attachments");
- Boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help")));
+ boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue();
+ boolean ignoreFileExt = cmd.hasOption("ignorefileextension");
+ boolean noAttachments = cmd.hasOption("no-additional-attachments");
+ boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help")));
disableFileLogging = cmd.hasOption("disable-file-logging");
String sourceName = cmd.getOptionValue("source");
@@ -389,8 +389,8 @@ public class Main {
String outName = cmd.getOptionValue("out");
String format = cmd.getOptionValue("format");
String lang = cmd.getOptionValue("language");
- Boolean noNotices = cmd.hasOption("no-notices");
- Boolean LogAsPDF = cmd.hasOption("log-as-pdf");
+ boolean noNotices = cmd.hasOption("no-notices");
+ boolean LogAsPDF = cmd.hasOption("log-as-pdf");
String zugferdVersion = cmd.getOptionValue("version");
String zugferdProfile = cmd.getOptionValue("profile");
diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java
index a368baaa..722b7167 100644
--- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java
+++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java
@@ -68,7 +68,7 @@ public class ValidatorFileWalker
thisResultString = "invalid";
allValid = false;
}
- LOGGER.info(String.format("\n@%s Testing file %d: %s (%s) ", dateFormat.format(date), fileCount++, thisResultString, file));
+ LOGGER.info("\n@{} Testing file {}: {} ({}) ", dateFormat.format(date), fileCount++, thisResultString, file);
}
}
}
@@ -79,7 +79,7 @@ public class ValidatorFileWalker
@Override
public FileVisitResult postVisitDirectory(Path dir,
IOException exc) {
- LOGGER.info(String.format("\nDirectory: %s \n", dir));
+ LOGGER.info("\nDirectory: {} \n", dir);
return FileVisitResult.CONTINUE;
}
diff --git a/doc/ZUV-Architektur.graphml b/doc/ZUV-Architektur.graphml
index f1e1e09d..cf52259e 100644
--- a/doc/ZUV-Architektur.graphml
+++ b/doc/ZUV-Architektur.graphml
@@ -1,6 +1,6 @@
-
+
@@ -13,43 +13,37 @@
-
+
-
+
- PDF+XML
-
-
-
-
-
-
+ PDF+XML
-
+
-
+
- ZUGFeRD
+ ZUGFeRD
-
+
- Folder 2
+ Folder 2
@@ -62,73 +56,55 @@
-
+
- Schematron files
-
-
-
-
-
-
+ Schematron files
-
+
- Schema files
-
-
-
-
-
-
+ Schema files
-
+
- Codelist XMLs
-
-
-
-
-
-
+ Codelist XMLs
-
+
-
+
- EN16931
+ EN16931
-
+
- Folder 3
+ Folder 3
@@ -141,57 +117,45 @@
-
+
- CEF Codelist Excel
-
-
-
-
-
-
+ CEF Codelist Excel
-
+
- CEN Schematron
-
-
-
-
-
-
+ CEN Schematron
-
+
-
+
- UN/CEFACT
+ UN/CEFACT
-
+
- Folder 4
+ Folder 4
@@ -204,16 +168,10 @@
-
+
- Schema files
-
-
-
-
-
-
+ Schema files
@@ -222,39 +180,33 @@
-
+
- XML Report
-
-
-
-
-
-
+ XML Report
-
+
-
+
- ZUV
+ ZUV
-
+
- Folder 5
+ Folder 5
@@ -267,167 +219,113 @@
-
+
- PH-Schematron
-
-
-
-
-
-
+ PH-Schematron
-
+
- XML
-
-
-
-
-
-
+ XML
-
+
- Metadata
-
-
-
-
-
-
+ Metadata
-
+
- Metadata check
-
-
-
-
-
-
+ Metadata check
-
+
- XSLT files
-
-
-
-
-
-
+ XSLT files
-
+
- XSLT files
-
-
-
-
-
-
+ XSLT files
-
+
- Additional data
-
-
-
-
-
-
+ Additional data
-
+
- Additional data check
-
-
-
-
-
-
+ Additional data check
-
+
- PH-Schematron
-
-
-
-
-
-
+ PH-Schematron
-
+
-
+
- Mustangproject
+ Mustangproject
-
+
- Folder 1
+ Folder 1
@@ -440,57 +338,45 @@
-
+
- PDFBox
-
-
-
-
-
-
+ PDFBox
-
+
- Mustang
-
-
-
-
-
-
+ Mustang
-
+
-
+
- VeraPDF
+ VeraPDF
-
+
- Folder 6
+ Folder 6
@@ -503,16 +389,10 @@
-
+
- VeraPDF
-
-
-
-
-
-
+ VeraPDF
@@ -521,74 +401,66 @@
-
+
- Schema check
-
-
-
-
-
-
+ Schema check
-
+
- XSLT files
-
-
-
-
-
-
+ XSLT files
-
+
- PH-Schematron
-
-
-
-
-
-
+ PH-Schematron
+
+
+
+
+
+
+
+
+
+
+ Intra-line calculation
-
-
+
-
+
- XRechnung
+ XRechnung
-
+
- Folder 7
+ Folder 7
@@ -599,19 +471,12 @@
-
-
+
- Schematron
-
-
-
-
-
-
+ Schematron
@@ -621,8 +486,8 @@
-
-
+
+
@@ -634,8 +499,8 @@
-
-
+
+
@@ -647,8 +512,8 @@
-
-
+
+
@@ -660,8 +525,8 @@
-
-
+
+
@@ -683,8 +548,8 @@
-
-
+
+
@@ -696,8 +561,8 @@
-
-
+
+
@@ -729,8 +594,8 @@
-
-
+
+
@@ -742,8 +607,8 @@
-
-
+
+
@@ -754,7 +619,10 @@
-
+
+
+
+
@@ -764,9 +632,9 @@
-
-
-
+
+
+
@@ -777,11 +645,9 @@
-
-
-
-
-
+
+
+
@@ -792,9 +658,9 @@
-
-
-
+
+
+
@@ -805,9 +671,9 @@
-
-
-
+
+
+
@@ -818,9 +684,9 @@
-
-
-
+
+
+
@@ -831,9 +697,9 @@
-
-
-
+
+
+
@@ -844,9 +710,9 @@
-
-
-
+
+
+
@@ -858,8 +724,8 @@
-
-
+
+
@@ -871,8 +737,8 @@
-
-
+
+
@@ -884,8 +750,8 @@
-
-
+
+
@@ -896,9 +762,9 @@
-
-
-
+
+
+
@@ -910,8 +776,8 @@
-
-
+
+
@@ -923,8 +789,8 @@
-
-
+
+
@@ -933,13 +799,12 @@
-
-
+
-
-
+
+
@@ -948,11 +813,13 @@
-
-
+
-
+
+
+
+
@@ -960,13 +827,12 @@
-
-
+
-
-
+
+
@@ -975,11 +841,43 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/ZUV-Architektur.svg b/doc/ZUV-Architektur.svg
index f3eda531..5b4bab18 100644
--- a/doc/ZUV-Architektur.svg
+++ b/doc/ZUV-Architektur.svg
@@ -1,1117 +1,6098 @@
-
+
+ org.skyscreamer
+ jsonassert
+ 2.0-rc1
+ test
+
org.junit.jupiter
junit-jupiter-api
@@ -173,8 +179,10 @@
org.apache.maven.plugins
maven-surefire-plugin
+ 3.5.3
alphabetical
+ -Duser.timezone=UTC
diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java
index d8b5f3fe..cca2033a 100644
--- a/library/src/main/java/org/mustangproject/Allowance.java
+++ b/library/src/main/java/org/mustangproject/Allowance.java
@@ -38,7 +38,7 @@ public class Allowance extends Charge {
if(totalAmount != null) {
return totalAmount;
} else if (percent!=null) {
- BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
+ 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;
diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java
index c906a704..3d647757 100644
--- a/library/src/main/java/org/mustangproject/Charge.java
+++ b/library/src/main/java/org/mustangproject/Charge.java
@@ -145,10 +145,9 @@ public class Charge implements IZUGFeRDAllowanceCharge {
if(totalAmount != null) {
return totalAmount;
} else if (percent!=null) {
- BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
-// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
- BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice);
- return singlePriceDiff;
+ BigDecimal factor=getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP);
+ BigDecimal singlePrice=currentItem.getValue().multiply(factor);
+ return singlePrice;
} else {
throw new RuntimeException("percent must be set");
}
diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java
new file mode 100644
index 00000000..d0679073
--- /dev/null
+++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java
@@ -0,0 +1,17 @@
+package org.mustangproject.Exceptions;
+
+import java.text.ParseException;
+
+/***
+ * will be thrown if an invoice cant be reproduced numerically
+ * ArithmetricException for backwards compatibility, was a spelling error
+ */
+public class ArithmeticException extends ArithmetricException {
+ public ArithmeticException() {
+ super();
+ }
+
+ public ArithmeticException(String details) {
+ super(details);
+ }
+}
diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java
index e5b4071d..16af335b 100644
--- a/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java
+++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java
@@ -4,6 +4,7 @@ import java.text.ParseException;
/***
* will be thrown if an invoice cant be reproduced numerically
+ * (deprecated, because of typo)
*/
public class ArithmetricException extends ParseException {
public ArithmetricException() {
diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java
index e3123b7a..72d71a6c 100644
--- a/library/src/main/java/org/mustangproject/Invoice.java
+++ b/library/src/main/java/org/mustangproject/Invoice.java
@@ -591,11 +591,7 @@ public class Invoice implements IExportableTransaction {
* @return fluent setter
*/
public Invoice setZFAllowances(Allowance[] iza) {
- Allowances=new ArrayList<>();
-
- for (IZUGFeRDAllowanceCharge cz:iza) {
- Allowances.add(cz);
- }
+ Allowances=new ArrayList<>(Arrays.asList(iza));
return this;
}
@@ -616,9 +612,7 @@ public class Invoice implements IExportableTransaction {
*/
public Invoice setZFCharges(Charge[] iza) {
Charges=new ArrayList<>();
- for (IZUGFeRDAllowanceCharge cz:iza) {
- Charges.add(cz);
- }
+ Charges.addAll(Arrays.asList(iza));
return this;
}
diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java
index 8e0cc8c7..b8afa6be 100644
--- a/library/src/main/java/org/mustangproject/Item.java
+++ b/library/src/main/java/org/mustangproject/Item.java
@@ -1,5 +1,6 @@
package org.mustangproject;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
@@ -77,20 +78,21 @@ public class Item implements IZUGFeRDExportableItem {
icnm.getAsString("Name").ifPresent(product::setName);
icnm.getAsString("Description").ifPresent(product::setDescription);
- icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> {
- SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID);
- });
+ icnm.getAsNodeMap("SellersItemIdentification")
+ .flatMap(SellersItemIdentification -> SellersItemIdentification.getAsString("ID"))
+ .ifPresent(product::setSellerAssignedID);
- icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> {
- BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID);
- });
+ icnm.getAsNodeMap("BuyersItemIdentification")
+ .flatMap(BuyersItemIdentification -> BuyersItemIdentification.getAsString("ID"))
+ .ifPresent(product::setBuyerAssignedID);
- icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
+ icnm.getAsNodeMap("ClassifiedTaxCategory")
+ .flatMap(m -> m.getAsBigDecimal("Percent"))
.ifPresent(product::setVATPercent);
});
- itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> {
- icnm.getAsString("LineID").ifPresent(this::setId);
- });
+ itemMap.getAsNodeMap("AssociatedDocumentLineDocument")
+ .flatMap(icnm -> icnm.getAsString("LineID"))
+ .ifPresent(this::setId);
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
// ubl
@@ -118,10 +120,16 @@ public class Item implements IZUGFeRDExportableItem {
itemMap.getAsString("ID")
.ifPresent(this::setId);
-
itemMap.getAsString("Note")
.ifPresent(this::addNote);
+ if (product==null) { // CII
+ if (itemMap.getNode("SpecifiedTradeProduct").isPresent()) {
+ product = new Product(itemMap.getNode("SpecifiedTradeProduct").get());
+ } else {
+ product = new Product();
+ }
+ }
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
@@ -136,14 +144,29 @@ public class Item implements IZUGFeRDExportableItem {
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
});
+ icnm.getAsNodeMap("GrossPriceProductTradePrice").ifPresent(gpptpNodes -> {
+ gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").ifPresent(gpptpAtacNodes -> {
+ /** mustang attributes differences between net and gross price to the product */
+ String chargeIndicator = gpptpAtacNodes.getAsStringOrNull("ChargeIndicator");
+ if ((chargeIndicator != null)&&(gpptpAtacNodes.getAsBigDecimal("ActualAmount").isPresent())) {
+ BigDecimal actual = gpptpAtacNodes.getAsBigDecimal("ActualAmount").get();
+ if (chargeIndicator.equals("true")) {
+ product.addCharge(new Charge(actual));
+ setPrice(getPrice().subtract(actual)); // the gross price affects the net price, which is read,
+ // so if we do not ignore charges|allowances we have to re-compensate the net price
+ } else {
+ product.addAllowance(new Allowance(actual));
+ setPrice(getPrice().add(actual));
+ }
+
+ }
+ });
+ });
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).
forEach(this::addReferencedDocument);
});
- itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//CII
- itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//UBL
-
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery")
.flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity"))
@@ -180,7 +203,7 @@ public class Item implements IZUGFeRDExportableItem {
}
if (amountString != null) {
izac.setTotalAmount(new BigDecimal(amountString));
- if (percentString!=null&&(percentString!="0")) {
+ if (percentString != null && (!percentString.equals("0"))) {
izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity()));
}
}
@@ -210,16 +233,16 @@ public class Item implements IZUGFeRDExportableItem {
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
- icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim());
+ icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s.trim());
icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> {
- Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
- Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
+ Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(XMLTools::tryDate).orElse(null);
+ Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(XMLTools::tryDate).orElse(null);
setDetailedDeliveryPeriod(start, end);
});
});
- itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //UBL
+ itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //CII
String isChargeString = stac.getAsString("ChargeIndicator").get();
String percentString = stac.getAsStringOrNull("MultiplierFactorNumeric");
@@ -301,13 +324,17 @@ public class Item implements IZUGFeRDExportableItem {
return this;
}
- @Override public IZUGFeRDAllowanceCharge[] getAllowances() {
- IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()];
+ @JsonIgnore
+ @Override
+ public IZUGFeRDAllowanceCharge[] getAllowances() { // in JSON is already returned as itemAllowances (and only read from there)
+ IZUGFeRDAllowanceCharge[] izac = new IZUGFeRDAllowanceCharge[Allowances.size()];
return Allowances.toArray(izac);
}
- @Override public IZUGFeRDAllowanceCharge[] getCharges() {
- IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()];
+ @JsonIgnore
+ @Override
+ public IZUGFeRDAllowanceCharge[] getCharges() { // in JSON is already returned as itemAllowances (and only read from there)
+ IZUGFeRDAllowanceCharge[] izac = new IZUGFeRDAllowanceCharge[Charges.size()];
return Charges.toArray(izac);
}
@@ -425,9 +452,7 @@ public class Item implements IZUGFeRDExportableItem {
public void setItemAllowances(ArrayList theAllowances) {
if (theAllowances != null) {
Allowances.clear();
- for (Allowance theAllowance : theAllowances) {
- Allowances.add(theAllowance);
- }
+ Allowances.addAll(theAllowances);
}
}
@@ -437,9 +462,7 @@ public class Item implements IZUGFeRDExportableItem {
public void setItemCharges(ArrayList theCharges) {
if (theCharges != null) {
Charges.clear();
- for (Charge theCharge : theCharges) {
- Charges.add(theCharge);
- }
+ Charges.addAll(theCharges);
}
}
diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java
index 93e48a79..467fbb60 100644
--- a/library/src/main/java/org/mustangproject/Product.java
+++ b/library/src/main/java/org/mustangproject/Product.java
@@ -1,13 +1,10 @@
package org.mustangproject;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.*;
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -107,7 +104,10 @@ public class Product implements IZUGFeRDExportableProduct {
classifications.add(new DesignatedProductClassification(classCode, className)));
});
- nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
+ nodeMap.getAsNodeMap("OriginTradeCountry")
+ .flatMap(nodes -> nodes.getNode("ID"))
+ .map(Node::getTextContent)
+ .ifPresent(this::setCountryOfOrigin);
}
/***
@@ -406,6 +406,16 @@ public class Product implements IZUGFeRDExportableProduct {
return this;
}
+
+ /***
+ * Jackson courtesy function, please use addCharge if you have the choice
+ * @return array of or null, if none
+ */
+ public Product setCharges(ArrayList charges) {
+ this.charges=charges;
+ return this;
+ }
+
/***
* returns the AppliedTradeAllowanceCharges of this product which are actually Charges
* @return array of or null, if none
@@ -432,5 +442,13 @@ public class Product implements IZUGFeRDExportableProduct {
return allowances.toArray(allowanceArr);
}
+ /***
+ * Jackson courtesy function, please use addAllowance if you have the choice
+ * @return array of or null, if none
+ */
+ public Product setAllowances(ArrayList allowances) {
+ this.allowances=allowances;
+ return this;
+ }
}
diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java
index 73b1ae81..3367a106 100644
--- a/library/src/main/java/org/mustangproject/TradeParty.java
+++ b/library/src/main/java/org/mustangproject/TradeParty.java
@@ -541,6 +541,32 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this;
}
+ /***
+ * for jackson, primarily, use addGlobalID(SchemedID) instead
+ * @param ID the id part without scheme
+ * @return fluent setter
+ */
+ public TradeParty setGlobalID(String ID) {
+ if (globalId==null) {
+ globalId=new SchemedID();
+ }
+ globalId.setId(ID);
+ return this;
+ }
+
+ /***
+ * for jackson, primarily, use addGlobalID(SchemedID) instead
+ * @param scheme the scheme part without id
+ * @return fluent setter
+ */
+ public TradeParty setGlobalIDScheme(String scheme) {
+ if (globalId==null) {
+ globalId=new SchemedID();
+ }
+ globalId.setScheme(scheme);
+ return this;
+ }
+
public TradeParty addGlobalID(SchemedID schemedID) {
globalId = schemedID;
return this;
@@ -746,7 +772,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
return null;
}
- List tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).map(IZUGFeRDTradeSettlement.class::cast).collect(Collectors.toList());
+ List tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).collect(Collectors.toList());
IZUGFeRDTradeSettlement[] result = new IZUGFeRDTradeSettlement[tradeSettlements.size()];
for (int i = 0; i < tradeSettlements.size(); i++) {
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
index a202b371..9bac1b07 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java
@@ -97,12 +97,12 @@ public class DAPullProvider extends ZUGFeRD2PullProvider {
+ XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "";
}
String allowanceChargeStr = "";
- if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
+ if (currentItem.getItemAllowances() != null) {
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem);
}
}
- if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
+ if (currentItem.getItemCharges() != null) {
for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
allowanceChargeStr += getAllowanceChargeStr(charge, currentItem);
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
index a0566f3e..50ea54ae 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
@@ -22,7 +22,7 @@ public class LineCalculator {
public LineCalculator(IZUGFeRDExportableItem currentItem) {
- if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
+ if (currentItem.getItemAllowances() != null) {
for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
BigDecimal factor=BigDecimal.ONE;
BigDecimal singleAllowance=allowance.getTotalAmount(currentItem);
@@ -35,7 +35,7 @@ public class LineCalculator {
}
}
- if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
+ if (currentItem.getItemCharges() != null) {
for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
BigDecimal factor=BigDecimal.ONE;
BigDecimal singleCharge=charge.getTotalAmount(currentItem);
@@ -47,7 +47,7 @@ public class LineCalculator {
}
}
- if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) {
+ if (currentItem.getItemTotalAllowances() != null) {
for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getItemTotalAllowances()) {
addAllowanceItemTotal(itemTotalAllowance.getTotalAmount(currentItem));
}
@@ -94,7 +94,7 @@ public class LineCalculator {
? BigDecimal.ONE.setScale(4)
: currentItem.getBasisQuantity();
itemTotalNetAmount = quantity.multiply(price).divide(basisQuantity, 18, RoundingMode.HALF_UP)
- .add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
+ .add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal.setScale(2, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);//.setScale(2, RoundingMode.HALF_UP);
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
index f6329d52..f2a032f2 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
@@ -57,7 +57,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription());
}
- if ((paymentTermsDescription == null) && (trans.getDocumentCode() != CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) {
+ if (paymentTermsDescription == null && !CORRECTEDINVOICE.equals(trans.getDocumentCode())/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) {
paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
}
@@ -125,12 +125,12 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
+ XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "";
}
String allowanceChargeStr = "";
- if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
+ if (currentItem.getItemAllowances() != null) {
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem);
}
}
- if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
+ if (currentItem.getItemCharges() != null) {
for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
allowanceChargeStr += getAllowanceChargeStr(charge, currentItem);
@@ -313,8 +313,9 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
if (payment != null) {
hasDueDate = true;
- // xml += payment.getSettlementXML();
- }
+ break;
+ // xml += payment.getSettlementXML();
+ }
}
}
if (trans.getTradeSettlement() != null) {
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
index fc15b318..02ae965d 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java
@@ -89,7 +89,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
BigDecimal res = BigDecimal.ZERO;
- if ((charges != null) && (charges.length > 0)) {
+ if (charges != null) {
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)) {
res = res.add(currentCharge.getTotalAmount(this));
@@ -172,6 +172,7 @@ 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);
@@ -211,7 +212,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges();
- if ((charges != null) && (charges.length > 0)) {
+ if (charges != null) {
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
BigDecimal taxPercent = currentCharge.getTaxPercent();
if (taxPercent != null) {
@@ -229,7 +230,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
}
IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances();
- if ((allowances != null) && (allowances.length > 0)) {
+ if (allowances != null) {
for (IZUGFeRDAllowanceCharge currentAllowance : allowances) {
BigDecimal taxPercent = currentAllowance.getTaxPercent();
if (taxPercent != null) {
@@ -285,8 +286,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
final IZUGFeRDAllowanceCharge[] charges = this.trans.getZFCharges();
- if (charges != null && charges.length > 0)
- {
+ if (charges != null) {
for (final IZUGFeRDAllowanceCharge currentCharge : charges)
{
final BigDecimal taxPercent = currentCharge.getTaxPercent();
@@ -309,8 +309,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
}
final IZUGFeRDAllowanceCharge[] allowances = this.trans.getZFAllowances();
- if (allowances != null && allowances.length > 0)
- {
+ if (allowances != null) {
for (final IZUGFeRDAllowanceCharge currentAllowance : allowances)
{
final BigDecimal taxPercent = currentAllowance.getTaxPercent();
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java
index dc00e2bb..d9662240 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.text.ParseException;
import org.mustangproject.XMLTools;
import org.slf4j.Logger;
@@ -18,7 +19,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
try {
setRawXML(rawXml);
containsMeta = true;
- } catch (final IOException e) {
+ } catch (final IOException | ParseException e) {
LOGGER.error ("Failed to set raw XML", e);
throw new ZUGFeRDExportException(e);
}
@@ -30,24 +31,21 @@ public class XRechnungImporter extends ZUGFeRDImporter {
try {
setRawXML(Files.readAllBytes(Paths.get(filename)));
containsMeta = true;
- } catch (final IOException e) {
- LOGGER.error ("Failed to set raw XML", e);
+ } catch (final IOException | ParseException e) {
+ LOGGER.error ("Failed to set raw XML", e);
throw new ZUGFeRDExportException(e);
}
}
public XRechnungImporter(InputStream fileinput) {
super();
-
try {
setRawXML(XMLTools.getBytesFromStream(fileinput));
containsMeta = true;
- } catch (final IOException e) {
- LOGGER.error ("Failed to set raw XML", e);
+ } catch (final IOException | ParseException e) {
+ LOGGER.error ("Failed to set raw XML", e);
throw new ZUGFeRDExportException(e);
}
-
-
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
index 15820835..537294df 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
@@ -359,7 +359,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
paymentTermsDescription += discount.getAsXRechnung();
}
- } else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) {
+ } else if (paymentTermsDescription == null
+ && !DocumentCodeTypeConstants.CORRECTEDINVOICE.equals(trans.getDocumentCode())
+ && !DocumentCodeTypeConstants.CREDITNOTE.equals(trans.getDocumentCode())
+ ) {
if (trans.getDueDate() != null) {
paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate());
}
@@ -434,11 +437,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml += "" + XMLTools.encodeXML(currentItem.getProduct().getName()) + "";
- if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) {
+ if (currentItem.getProduct().getDescription() != null) {
xml += "" +
XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
"";
}
+
if (currentItem.getProduct().getAttributes() != null) {
for (Entry entry : currentItem.getProduct().getAttributes().entrySet()) {
xml += "" +
@@ -447,7 +451,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
"";
}
}
- if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
+ if (currentItem.getProduct().getClassifications() != null) {
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
xml += ""
+ " 0)) {
+ if (profile == Profiles.getByName("Extended") && trans.getCashDiscounts() != null) {
for (IZUGFeRDCashDiscount discount : trans.getCashDiscounts()
) {
xml += discount.getAsCII();
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
index 87137411..0909735f 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
@@ -564,10 +564,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
// iterate over all pdf pages
- for (Object object : doc.getPages()) {
- if (object instanceof PDPage) {
+ for (PDPage page : doc.getPages()) {
+ if (page != null) {
- PDPage page = (PDPage) object;
PDResources res = page.getResources();
// Check for fonts in PDXObjects:
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
index cbf08b7b..7de7415e 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
@@ -14,6 +14,7 @@ package org.mustangproject.ZUGFeRD;
* @author jstaerk
*/
import java.io.*;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
@@ -351,6 +352,16 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
public String getHolder() {
+ if (importedInvoice!=null && importedInvoice.getTradeSettlement()!=null) {
+ for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) {
+ if (settlement instanceof IZUGFeRDTradeSettlementPayment) {
+ String s = ((IZUGFeRDTradeSettlementPayment) settlement).getAccountName();
+ if ( s != null ) {
+ return s;
+ }
+ }
+ }
+ }
return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']");
}
@@ -452,7 +463,11 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
* @throws IOException if raw can not be set
*/
public void setMeta(String meta) throws IOException {
- setRawXML(meta.getBytes());
+ try {
+ setRawXML(meta.getBytes());
+ } catch (ParseException e) {
+ LOGGER.error("Failed to parse", e);
+ }
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
index 8b4ab8d7..be49d7ec 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
@@ -10,7 +10,6 @@ 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.*;
-import org.mustangproject.Exceptions.ArithmetricException;
import org.mustangproject.Exceptions.StructureException;
import org.mustangproject.util.NodeMap;
import org.slf4j.Logger;
@@ -127,8 +126,7 @@ public class ZUGFeRDInvoiceImporter {
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
- try {
- PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream));
+ try(PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
// PDDocumentInformation info = doc.getDocumentInformation();
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start
@@ -174,7 +172,12 @@ public class ZUGFeRDInvoiceImporter {
} else {
// no PDF probably XML
containsMeta = true;
- setRawXML(XMLTools.getBytesFromStream(pdfStream));
+ try {
+ setRawXML(XMLTools.getBytesFromStream(pdfStream));
+ } catch(ParseException e) {
+ LOGGER.error("Failed to parse PDF", e);
+ }
+
}
}
@@ -209,7 +212,16 @@ public class ZUGFeRDInvoiceImporter {
*/
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
- if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
+ Set validFilenames = Set.of(
+ "ZUGFeRD-invoice.xml",
+ "zugferd-invoice.xml",
+ "factur-x.xml",
+ "xrechnung.xml",
+ "order-x.xml",
+ "cida.xml"
+ );
+
+ if (validFilenames.contains(filename)) {
containsMeta = true;
// String embeddedFilename = filePath + filename;
@@ -219,8 +231,11 @@ public class ZUGFeRDInvoiceImporter {
// ByteArrayOutputStream();
// FileOutputStream fos = new FileOutputStream(file);
- setRawXML(embeddedFile.toByteArray());
-
+ try {
+ setRawXML(embeddedFile.toByteArray());
+ } catch (ParseException e) {
+ LOGGER.error("Failed to parse XML", e);
+ }
// fos.write(embeddedFile.getByteArray());
// fos.close();
}
@@ -237,7 +252,7 @@ public class ZUGFeRDInvoiceImporter {
* @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter)
* @throws IOException if parsing xml throws it (unlikely its string based)
*/
- public void setRawXML(byte[] rawXML, boolean doParse) throws IOException {
+ public void setRawXML(byte[] rawXML, boolean doParse) throws IOException, ParseException {
this.containsMeta = true;
this.rawXML = rawXML;
this.version = null;
@@ -245,7 +260,7 @@ public class ZUGFeRDInvoiceImporter {
try {
setDocument();
- } catch (ParserConfigurationException | SAXException | ParseException e) {
+ } catch (ParserConfigurationException | SAXException e) {
LOGGER.error("Failed to parse XML", e);
throw new ZUGFeRDExportException(e);
}
@@ -257,7 +272,7 @@ public class ZUGFeRDInvoiceImporter {
* @param rawXML the cii(?) as a string
* @throws IOException if parsing xml throws it (unlikely its string based)
*/
- public void setRawXML(byte[] rawXML) throws IOException {
+ public void setRawXML(byte[] rawXML) throws IOException, ParseException {
setRawXML(rawXML, true);
}
@@ -353,39 +368,31 @@ public class ZUGFeRDInvoiceImporter {
delivery.addGlobalID(sID);
}
});
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("StreetName").ifPresent(t -> delivery.setStreet(t));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("CityName").ifPresent(t -> delivery.setLocation(t));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("PostalZone").ifPresent(t -> delivery.setZIP(t));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsNodeMap("Country").ifPresent(t -> t.getAsString("IdentificationCode").ifPresent(u -> delivery.setCountry(u)));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsNodeMap("AddressLine").ifPresent(t -> t.getAsString("Line").ifPresent(u -> delivery.setAdditionalAddressExtension(u)));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t));
- });
- deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
- s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t));
- });
+ Optional addressNodeMapp = deliveryLocationNodeMap.getAsNodeMap("Address");
+ addressNodeMapp.flatMap(s -> s.getAsString("StreetName"))
+ .ifPresent(delivery::setStreet);
+ addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName"))
+ .ifPresent(delivery::setAdditionalAddress);
+ addressNodeMapp.flatMap(s -> s.getAsString("CityName"))
+ .ifPresent(delivery::setLocation);
+ addressNodeMapp.flatMap(s -> s.getAsString("PostalZone"))
+ .ifPresent(delivery::setZIP);
+ addressNodeMapp.flatMap(s -> s.getAsNodeMap("Country")).flatMap(t -> t.getAsString("IdentificationCode"))
+ .ifPresent(delivery::setCountry);
+ addressNodeMapp.flatMap(s -> s.getAsNodeMap("AddressLine")).flatMap(t -> t.getAsString("Line"))
+ .ifPresent(delivery::setAdditionalAddressExtension);
+ addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName"))
+ .ifPresent(delivery::setAdditionalAddress);
+ addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName"))
+ .ifPresent(delivery::setAdditionalAddress);
});
- new NodeMap(deliveryNode).getAsNodeMap("DeliveryParty").ifPresent(partyMap -> {
- partyMap.getAsNodeMap("PartyName").ifPresent(s -> {
- s.getAsString("Name").ifPresent(t -> delivery.setName(t));
- });
- });
- String street, name, additionalStreet, city, postal, countrySubentity, line, country = null;
+ new NodeMap(deliveryNode).getAsNodeMap("DeliveryParty")
+ .flatMap(partyMap -> partyMap.getAsNodeMap("PartyName"))
+ .flatMap(s -> s.getAsString("Name"))
+ .ifPresent(delivery::setName);
+
zpp.setDeliveryAddress(delivery);
}
@@ -424,7 +431,7 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
-
+
xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"TaxInclusiveAmount\"]");
BigDecimal expectedGrandTotal = null;
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
@@ -572,11 +579,11 @@ public class ZUGFeRDInvoiceImporter {
}
String creditorReferenceID = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"CreditorReferenceID\"]").trim();//BT-90
- if ((creditorReferenceID == null)||(creditorReferenceID.length()==0)) {
+ if (creditorReferenceID == null || creditorReferenceID.isEmpty()) {
//maybe it's there in UBL?
creditorReferenceID = extractString("//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyIdentification\"]/*[local-name()=\"ID\"]").trim();
}
- if ((creditorReferenceID != null)&&(creditorReferenceID.length()>0)) {
+ if (creditorReferenceID != null && !creditorReferenceID.isEmpty()) {
zpp.setCreditorReferenceID(creditorReferenceID);
}
@@ -1088,7 +1095,7 @@ public class ZUGFeRDInvoiceImporter {
.collect(Collectors.joining(" + "));
} catch (Exception ignored) {
}
- throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
+ throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
}
}
}
@@ -1205,7 +1212,7 @@ public class ZUGFeRDInvoiceImporter {
* sets the XML for the importer to parse
* @param XML the UBL or CII
*/
- public void fromXML(String XML) {
+ public void fromXML(String XML) throws ParseException{
try {
containsMeta = true;
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
index 7fc94f5f..f386d655 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
@@ -12,12 +12,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.xpath.XPathExpressionException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Date;
/***
* tests the linecalculator and transactioncalculator classes
@@ -79,6 +78,56 @@ public class CalculationTest extends ResourceCase {
assertEquals(valueOf(287.9408).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros());
}
+ @Test
+ public void testAllowanceAndChargeEx4() {
+ /** numbers from en16931 example 4 */
+ 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"));
+ } catch (Exception e) {
+ LOGGER.error("Failed to set dates", e);
+
+ }
+
+ /* trade party (sender) */
+ TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE");
+ sender.addVATID("DE2222222222");
+ invoice.setSender(sender);
+
+ /* trade party (recipient) */
+ TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE");
+ invoice.setRecipient(recipient);
+
+ /* item */
+ Product product;
+ Item item;
+
+ product = new Product("Pens", "", "H87", new BigDecimal(25));
+ 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);
+ 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);
+ 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"));
+ invoice.addCharge(new Charge(new BigDecimal(15)).setReasonCode("ZZZ").setReason("Frachtkosten"));
+
+ TransactionCalculator calculator = new TransactionCalculator(invoice);
+ assertEquals(valueOf(286.62).stripTrailingZeros(), calculator.getTotal());// interestingly, EN16931-1 has 286.63 here?
+ assertEquals(valueOf(272.96).stripTrailingZeros(), calculator.getTaxBasis()); // and 272.97 here
+ assertEquals(valueOf(337.45).stripTrailingZeros(), calculator.getDuePayable()); // and 337.46 here???
+ }
+
@Test
public void testLineCalculatorForeignCurrencyExample() {
/*** xml of official fx sample with allowances and charges
@@ -88,19 +137,19 @@ public class CalculationTest extends ResourceCase {
*/
File inputCII = getResourceAsFile("Extended_fremdwaehrung.xml");
- ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter();
- Invoice invoice=null;
+ ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
+ Invoice invoice = null;
zii.doIgnoreCalculationErrors();
- boolean hasExceptions=false;
+ boolean hasExceptions = false;
try {
zii.setInputStream(new FileInputStream(inputCII));
- invoice=zii.extractInvoice();
+ invoice = zii.extractInvoice();
} catch (XPathExpressionException | ParseException e) {
// handle Exceptions
- hasExceptions=true;
+ hasExceptions = true;
} catch (FileNotFoundException e) {
- hasExceptions=true;
+ hasExceptions = true;
}
assertFalse(hasExceptions);
// Reading ZUGFeRD
@@ -152,7 +201,7 @@ public class CalculationTest extends ResourceCase {
Product product;
Item item;
- product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA");
+ product = new Product("AAA", "", "H87", sales_tax_percent1).setSellerAssignedID("1AAA");
item = new Item(product, new BigDecimal("4.750"), new BigDecimal(5.00));
// set values for additional charge and discount used for next lines
@@ -168,54 +217,23 @@ public class CalculationTest extends ResourceCase {
}
invoice.addItem(item);
- // reset values for additional charge and discount used for next lines
- item_increase = BigDecimal.ZERO;
- item_discount = BigDecimal.ZERO;
- product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB");
+ product = new Product("BBB", "", "H87", sales_tax_percent1).setSellerAssignedID("2BBB");
item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00));
- if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
- item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
- }
- if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
- item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
- }
invoice.addItem(item);
- product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC");
+ product = new Product("CCC", "", "H87", sales_tax_percent1).setSellerAssignedID("3CCC");
item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00));
- if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
- item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
- }
- if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
- item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
- }
invoice.addItem(item);
- product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD");
+ product = new Product("DDD", "", "H87", sales_tax_percent1).setSellerAssignedID("4DDD");
item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00));
- if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
- item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
- }
- if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
- item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
- }
invoice.addItem(item);
- product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE");
+ product = new Product("EEE", "", "H87", sales_tax_percent1).setSellerAssignedID("5EEE");
item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00));
- if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
- item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
- }
- if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
- item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
-
- }
invoice.addItem(item);
- // reset values for additional charge and discount used on invoice level
- item_increase = BigDecimal.valueOf(3.50);
- item_discount = BigDecimal.valueOf(10.00);
if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) {
invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschläge"));
@@ -224,7 +242,7 @@ public class CalculationTest extends ResourceCase {
invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte"));
}
TransactionCalculator calculator = new TransactionCalculator(invoice);
- assertEquals(valueOf(307.18).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
+ assertEquals(valueOf(101.85).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
}
public void testSimpleItemPercentAllowance() {
@@ -259,14 +277,64 @@ public class CalculationTest extends ResourceCase {
Product product;
Item item;
- product = new Product("AAA", "", "H84", BigDecimal.ZERO);
+ product = new Product("AAA", "", "H87", BigDecimal.ZERO);
item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00));
item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO));
invoice.addItem(item);
TransactionCalculator calculator = new TransactionCalculator(invoice);
- assertEquals(new BigDecimal(5), calculator.getGrandTotal().stripTrailingZeros());
+ assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros());
+ }
+
+ public void testSimpleDocumentPercentCharge() {
+
+ String orgname = "Test company";
+ String number = "123";
+ String priceStr = "3.00";
+ BigDecimal price = new BigDecimal(priceStr);
+
+
+ // similar, but slightly less complicated to whats later testted in testRelativeChargesAllowancesExport
+ Invoice i = new Invoice().setCurrency("CHF").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"))
+ .setNumber(number)
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"));
+ // 9+50%=>13,50 expected net
+ // .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt"))
+ TransactionCalculator tc = new TransactionCalculator(i);
+ assertEquals(new BigDecimal("13.50"), tc.getTaxBasis());
+
+ assertEquals(new BigDecimal("16.07"), tc.getDuePayable());
+ }
+
+ public void testSimpleDocumentPercentAllowance() {
+
+ String orgname = "Test company";
+ String number = "123";
+ String priceStr = "3.00";
+ BigDecimal price = new BigDecimal(priceStr);
+
+
+ // similar, but slightly less complicated to whats later testted in testRelativeChargesAllowancesExport
+ Invoice i = new Invoice().setCurrency("CHF").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"))
+ .setNumber(number)
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
+ .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"));
+ // 9-50%=>4,50 expected net
+ // .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt"))
+ TransactionCalculator tc = new TransactionCalculator(i);
+ assertEquals(new BigDecimal("4.50"), tc.getTaxBasis());
+
+ assertEquals(new BigDecimal("5.36"), tc.getDuePayable());
}
public void testSimpleItemTotalAllowance() {
@@ -301,7 +369,7 @@ public class CalculationTest extends ResourceCase {
Product product;
Item item;
- product = new Product("AAA", "", "H84", BigDecimal.ZERO);
+ product = new Product("AAA", "", "H87", BigDecimal.ZERO);
item = new Item(product, new BigDecimal("1.00"), new BigDecimal(5.00));
item.addAllowance(new Allowance(new BigDecimal(1)).setTaxPercent(BigDecimal.ZERO));
@@ -314,7 +382,7 @@ public class CalculationTest extends ResourceCase {
/**
* LineCalculator should not throw an exception when calculating a non-terminating decimal expansion
- * */
+ */
@Test
public void testNonTerminatingDecimalExpansion() {
final Product product = new Product();
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
index fd0e7b97..6f0a910c 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java
@@ -21,6 +21,13 @@
*/
package org.mustangproject.ZUGFeRD;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+import org.mustangproject.*;
+import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants;
+
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
@@ -29,28 +36,11 @@ import java.nio.file.Files;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.TimeZone;
import javax.xml.xpath.XPathExpressionException;
-import org.junit.FixMethodOrder;
-import org.junit.experimental.theories.FromDataPoints;
-import org.junit.runners.MethodSorters;
-import org.mustangproject.Allowance;
-import org.mustangproject.BankDetails;
-import org.mustangproject.CalculatedInvoice;
-import org.mustangproject.CashDiscount;
-import org.mustangproject.Charge;
-import org.mustangproject.Contact;
-import org.mustangproject.Invoice;
-import org.mustangproject.Item;
-import org.mustangproject.Product;
-import org.mustangproject.SchemedID;
-import org.mustangproject.TradeParty;
-import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants;
+import static org.assertj.core.api.Assertions.assertThat;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DeSerializationTest extends ResourceCase {
@@ -71,6 +61,29 @@ public class DeSerializationTest extends ResourceCase {
}
+ public void testProduct() throws IOException, XPathExpressionException, ParseException {
+ File inputCII = getResourceAsFile("Extended_fremdwaehrung.xml");
+ var zii = new ZUGFeRDInvoiceImporter();
+ zii.doIgnoreCalculationErrors();
+ zii.fromXML(Files.readString(inputCII.toPath()));
+ var product = zii.extractInvoice()
+ .getZFItems()[0]
+ .getProduct();
+
+ assertThat(product.getCountryOfOrigin()).as("Product Country of origin")
+ .isEqualTo("DE");
+ assertThat(product.getSellerAssignedID()).as("Product Seller assigned ID")
+ .isEqualTo("CO-123/V2A");
+ assertThat(product.getBuyerAssignedID()).as("Product Buyer assigned ID")
+ .isEqualTo("Toolbox 0815");
+ assertThat(product.getName()).as("Name")
+ .isEqualTo("Stahlcoil");
+
+ assertThat(product.getAttributes()).as("Product attributes")
+ .containsKey("LeoID")
+ .containsValue("704310.0105636504");
+ }
+
public void testInvoiceLine() throws JsonProcessingException {
File inputCII = getResourceAsFile("factur-x.xml");
boolean hasExceptions = false;
@@ -79,7 +92,7 @@ public class DeSerializationTest extends ResourceCase {
try {
zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8));
- } catch (IOException e) {
+ } catch (IOException | ParseException e) {
hasExceptions = true;
}
@@ -395,7 +408,7 @@ public class DeSerializationTest extends ResourceCase {
try {
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
TransactionCalculator tc=new TransactionCalculator(newInvoiceFromJSON);
- assertEquals(new BigDecimal("18.92"),tc.getGrandTotal());
+ assertEquals(new BigDecimal("18.33"),tc.getGrandTotal());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
@@ -414,10 +427,12 @@ public class DeSerializationTest extends ResourceCase {
String number = "123";
String priceStr = "1.00";
String taxID = "9990815";
+
BigDecimal price = new BigDecimal(priceStr);
Invoice newInvoiceFromJSON = null;
boolean hasExceptions = false;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ String json = "";
try {
SchemedID gtin = new SchemedID("0160", "2001015001325");
SchemedID gln = new SchemedID("0088", "4304171000002");
@@ -435,7 +450,7 @@ public class DeSerializationTest extends ResourceCase {
.addCashDiscount(new CashDiscount(new BigDecimal(2), 14))
.setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE);
ObjectMapper mapper = new ObjectMapper();
- String json = mapper.writeValueAsString(i);
+ json = mapper.writeValueAsString(i);
newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
} catch (ParseException e) {
hasExceptions = true;
@@ -444,8 +459,34 @@ public class DeSerializationTest extends ResourceCase {
}
assertEquals(newInvoiceFromJSON.getBuyerOrderReferencedDocumentID(), "28934");
assertFalse(hasExceptions);
+ }
+ public void testFromJSON() throws JsonProcessingException {
+ String globalID = "4000001123452";
+ String globalIDScheme = "0088";
+ String itemDeliveryFrom="2022-01-28T23:00:00.000+00:00";
+ String itemDeliveryTo="2022-01-30T23:00:00.000+00:00";
+ String json="{\"number\":\"123\",\"buyerOrderReferencedDocumentID\":\"28934\",\"currency\":\"CHF\",\"issueDate\":1752744199178,\"dueDate\":1752744199178,\"deliveryDate\":1604271600000,\"sender\":{\"name\":\"Test company\",\"zip\":\"55232\",\"street\":\"teststr\",\"location\":\"teststadt\",\"country\":\"DE\",\"taxID\":\"9990815\",\"vatID\":\"DE0815\",\"id\":\"0009845\",\"globalID\":\""+globalID+"\",\"globalIDScheme\":\""+globalIDScheme+"\",\"email\":\"sender@test.org\",\"vatid\":\"DE0815\"},\"recipient\":{\"name\":\"Franz Müller\",\"zip\":\"55232\",\"street\":\"teststr.12\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"vatID\":\"DE4711\",\"additionalAddress\":\"Hinterhaus 3\",\"contact\":{\"name\":\"Franz Müller\",\"phone\":\"01779999999\",\"email\":\"franz@mueller.de\",\"zip\":\"55232\",\"street\":\"teststr. 12\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"fax\":\"++49555123456\"},\"globalID\":\"4304171000002\",\"globalIDScheme\":\"0088\",\"email\":\"recipient@test.org\",\"vatid\":\"DE4711\"},\"deliveryAddress\":{\"name\":\"just the other side of the street\",\"zip\":\"55232\",\"street\":\"teststr.12a\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"vatID\":\"DE47110\",\"vatid\":\"DE47110\"},\"cashDiscounts\":[{\"percent\":2,\"days\":14}],\"notes\":[\"document level 1/2\",\"document level 2/2\"],\"sellerOrderReferencedDocumentID\":\"9384\",\"contractReferencedDocument\":\"376zreurzu0983\",\"valid\":true,\"vatdueDateTypeCode\":\"72\",\"zfitems\":[{\"price\":1.00,\"quantity\":1,\"basisQuantity\":1,\"detailedDeliveryPeriodFrom\":\""+itemDeliveryFrom+"\",\"detailedDeliveryPeriodTo\":\""+itemDeliveryTo+"\",\"id\":\"a123\",\"buyerOrderReferencedDocumentLineID\":\"xxx\",\"product\":{\"unit\":\"H87\",\"name\":\"Testprodukt\",\"sellerAssignedID\":\"4711\",\"taxCategoryCode\":\"S\",\"globalID\":\"2001015001325\",\"globalIDScheme\":\"0160\",\"intraCommunitySupply\":false,\"reverseCharge\":false,\"vatpercent\":16},\"notes\":[\"item level 1/1\"],\"notesWithSubjectCode\":[{\"content\":\"item level 1/1\"}],\"itemAllowances\":[{\"totalAmount\":0.0200000000000000004163336342344337026588618755340576171875,\"taxPercent\":16,\"reason\":\"item discount\",\"categoryCode\":\"S\"}],\"value\":1.00}],\"ownVATID\":\"DE0815\",\"detailedDeliveryPeriodFrom\":1601503200000,\"detailedDeliveryPeriodTo\":1601848800000,\"ownTaxID\":\"9990815\",\"ownZIP\":\"55232\",\"ownLocation\":\"teststadt\",\"zfallowances\":[{\"totalAmount\":0.200000000000000011102230246251565404236316680908203125,\"taxPercent\":16,\"reason\":\"discount\",\"categoryCode\":\"S\"}],\"ownStreet\":\"teststr\",\"zfcharges\":[{\"totalAmount\":0.5,\"taxPercent\":16,\"reason\":\"quick delivery charge\",\"categoryCode\":\"S\"}],\"ownCountry\":\"DE\"}";
+
+ ObjectMapper mapper = new ObjectMapper();
+ Invoice fromJSON = mapper.readValue(json, Invoice.class);
+ assertEquals(globalID, fromJSON.getSender().getGlobalID());
+ assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme());
+ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
+ assertEquals("2022-01-28", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodFrom()));
+ assertEquals("2022-01-30", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodTo()));
+ assertEquals("sender@test.org", fromJSON.getSender().getEmail());
+ }
+
+ 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\"}";
+
+ ObjectMapper mapper = new ObjectMapper();
+ CalculatedInvoice fromJSON = mapper.readValue(json, CalculatedInvoice.class);
+ fromJSON.calculate();
+ assertEquals(new BigDecimal("34.51"),fromJSON.getDuePayable());
}
public void testDueDateRoundtrip() throws JsonProcessingException {
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
index e7a2a951..3a211610 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java
@@ -189,8 +189,7 @@ public class XRTest extends TestCase {
zf2p.setProfile(Profiles.getByName("XRechnung"));
zf2p.generateXML(i);
- final String xmlGen = new String(zf2p.getXML());
- System.out.println(xmlGen);
+
final Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new ByteArrayInputStream(zf2p.getXML()));
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
index fc000301..72a4127f 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
@@ -53,6 +53,7 @@ public class ZF2PushTest extends TestCase {
final String TARGET_ALLOWANCESPDF = "./target/testout-ZF2PushAllowances.pdf";
final String TARGET_CREDITNOTEPDF = "./target/testout-ZF2PushCreditNote.pdf";
final String TARGET_CORRECTIONPDF = "./target/testout-ZF2PushCorrection.pdf";
+ final String TARGET_ITEMGROSS = "./target/testout-ZF2PushGross.pdf";
final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf";
final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.pdf";
final String TARGET_RELATIVECHARGESALLOWANCESPDF = "./target/testout-ZF2PushRelativeChargesAllowances.pdf";
@@ -114,8 +115,8 @@ public class ZF2PushTest extends TestCase {
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF);
assertTrue(zi.getUTF8().contains("DE88200800000970375700")); //the iban
assertTrue(zi.getUTF8().contains("Max Mustermann")); //account holder
- assertTrue(zi.getUTF8().contains("DueDateDateTime")); //account holder
- assertTrue(zi.getUTF8().contains("20201212")); //account holder
+ assertTrue(zi.getUTF8().contains("DueDateDateTime"));
+ assertTrue(zi.getUTF8().contains("20201212"));
assertTrue(zi.getUTF8().contains("
CO-123/V2A
Toolbox 0815
Stahlcoil
+
+ LeoID
+ 704310.0105636504
+
DE
diff --git a/pom.xml b/pom.xml
index 252cdccf..b5c94c61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.mustangproject
core
- 2.17.1-SNAPSHOT pom
+ 2.18.1-SNAPSHOT pom
Mustang
@@ -70,28 +70,27 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.13
+ 1.7.0
true
ossrh
- https://s01.oss.sonatype.org/
+ https://ossrh-staging-api.central.sonatype.com/
true
org.apache.maven.plugins
maven-surefire-plugin
+ 3.5.3
alphabetical
+ -Duser.timezone=UTC
org.apache.maven.plugins
maven-source-plugin
- 3.2.1
-
- UTF-8
-
+ 3.3.1
attach-javadoc
diff --git a/validator/pom.xml b/validator/pom.xml
index 19cc2e97..57866cba 100644
--- a/validator/pom.xml
+++ b/validator/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
4.0.0
org.mustangproject
@@ -11,7 +11,7 @@
Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung)
jar
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
@@ -38,7 +38,7 @@
${project.groupId}
library
- 2.17.1-SNAPSHOT
+ 2.18.1-SNAPSHOT
org.dom4j
@@ -129,8 +129,10 @@
org.apache.maven.plugins
maven-surefire-plugin
+ 3.5.3
alphabetical
+ -Duser.timezone=UTC
diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
index e79d3f29..0e35b714 100644
--- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
@@ -12,7 +12,6 @@ import java.util.Calendar;
import java.util.EnumSet;
import java.util.HashMap;
-import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -130,136 +129,137 @@ public class PDFValidator extends Validator {
final Document docXMP;
- if (xmp == null || xmp.length() == 0) {
+ if (xmp == null || xmp.isEmpty()) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Invalid XMP Metadata not found")
.setSection(17).setPart(EPart.pdf));
}
- else
- /*
- * checking for sth like EXTENDED
- * INVOICE
- * ZUGFeRD-invoice.xml
- * 1.0
- */
- try {
- final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
- // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
- factory.setXIncludeAware(false);
+ else {
+ /*
+ * checking for sth like EXTENDED
+ * INVOICE
+ * ZUGFeRD-invoice.xml
+ * 1.0
+ */
+ try {
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
+ factory.setXIncludeAware(false);
- final DocumentBuilder builder = factory.newDocumentBuilder();
- final InputSource is = new InputSource(new StringReader(xmp));
- docXMP = builder.parse(is);
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ final InputSource is = new InputSource(new StringReader(xmp));
+ docXMP = builder.parse(is);
- final XPathFactory xpathFactory = XPathFactory.newInstance();
+ final XPathFactory xpathFactory = XPathFactory.newInstance();
- // Create XPath object XPath xpath = xpathFactory.newXPath(); XPathExpression
+ // Create XPath object XPath xpath = xpathFactory.newXPath(); XPathExpression
- final XPath xpath = xpathFactory.newXPath();
- // xpath.compile("//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/[local-name()=\"ID\"]");
- // evaluate expression result on XML document ndList = (NodeList)
+ final XPath xpath = xpathFactory.newXPath();
+ // xpath.compile("//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/[local-name()=\"ID\"]");
+ // evaluate expression result on XML document ndList = (NodeList)
- // get the first element
- XPathExpression xpr = xpath.compile(
- "//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel");
- NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
+ // get the first element
+ XPathExpression xpr = xpath.compile(
+ "//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel");
+ NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
- if (nodes.getLength() == 0) {
- context.addResultItem(
- new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found")
- .setSection(11).setPart(EPart.pdf));
- }
-
- boolean conformanceLevelValid = false;
- for (int i = 0; i < nodes.getLength(); i++) {
-
- final String[] valueArray = {"BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG"};
- if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
- conformanceLevelValid = true;
- }
- }
- if (!conformanceLevelValid) {
- context.addResultItem(new ValidationResultItem(
- ESeverity.error,
- "XMP Metadata: ConformanceLevel contains invalid value"
- ).setSection(12).setPart(EPart.pdf));
-
- }
- xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType");
- nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
-
- if (nodes.getLength() == 0) {
- context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found")
- .setSection(13).setPart(EPart.pdf));
- }
-
- boolean documentTypeValid = false;
- for (int i = 0; i < nodes.getLength(); i++) {
- if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER")
- || nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent()
- .equals("ORDER_CHANGE")) {
- documentTypeValid = true;
- }
- }
- if (!documentTypeValid) {
- context.addResultItem(
- new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid")
- .setSection(14).setPart(EPart.pdf));
-
- }
- xpr = xpath.compile(
- "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName");
- nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
-
- if (nodes.getLength() == 0) {
- context.addResultItem(
- new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found")
- .setSection(21).setPart(EPart.pdf));
- }
- boolean documentFilenameValid = false;
- for (int i = 0; i < nodes.getLength(); i++) {
- final String[] valueArray = {"factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml", "order-x.xml"};
- if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
- documentFilenameValid = true;
+ if (nodes.getLength() == 0) {
+ context.addResultItem(
+ new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found")
+ .setSection(11).setPart(EPart.pdf));
}
- // e.g. ZUGFeRD-invoice.xml
- }
- if (!documentFilenameValid) {
+ boolean conformanceLevelValid = false;
+ for (int i = 0; i < nodes.getLength(); i++) {
- context.addResultItem(new ValidationResultItem(
- ESeverity.error,
- "XMP Metadata: DocumentFileName contains invalid value"
- ).setSection(19).setPart(EPart.pdf));
- }
- xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version");
- nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
+ final String[] valueArray = {"BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG"};
+ if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
+ conformanceLevelValid = true;
+ }
+ }
+ if (!conformanceLevelValid) {
+ context.addResultItem(new ValidationResultItem(
+ ESeverity.error,
+ "XMP Metadata: ConformanceLevel contains invalid value"
+ ).setSection(12).setPart(EPart.pdf));
- // get all child nodes
- // NodeList nodes = element.getChildNodes();
- // expr.evaluate(docXMP, XPathConstants.NODESET);
- // print the text content of each child
- if (nodes.getLength() == 0) {
- context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found")
- .setSection(15).setPart(EPart.pdf));
- }
+ }
+ xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType");
+ nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
- boolean versionValid = false;
- for (int i = 0; i < nodes.getLength(); i++) {
- final String[] valueArray = {"1.0", "1p0", "2p0", "1.2", "2.0", "2.1", "2.2", "2.3", "3.0"}; //1.2, 2.0, 2.1, 2.2, 2.3 and 3.0 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1
+ if (nodes.getLength() == 0) {
+ context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found")
+ .setSection(13).setPart(EPart.pdf));
+ }
- if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
- versionValid = true;
- } // e.g. 1.0
- }
- if (!versionValid) {
- context.addResultItem(
- new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value")
- .setSection(16).setPart(EPart.pdf));
+ boolean documentTypeValid = false;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER")
+ || nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent()
+ .equals("ORDER_CHANGE")) {
+ documentTypeValid = true;
+ }
+ }
+ if (!documentTypeValid) {
+ context.addResultItem(
+ new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid")
+ .setSection(14).setPart(EPart.pdf));
+ }
+ xpr = xpath.compile(
+ "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName");
+ nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
+
+ if (nodes.getLength() == 0) {
+ context.addResultItem(
+ new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found")
+ .setSection(21).setPart(EPart.pdf));
+ }
+ boolean documentFilenameValid = false;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final String[] valueArray = {"factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml", "order-x.xml"};
+ if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
+ documentFilenameValid = true;
+ }
+
+ // e.g. ZUGFeRD-invoice.xml
+ }
+ if (!documentFilenameValid) {
+
+ context.addResultItem(new ValidationResultItem(
+ ESeverity.error,
+ "XMP Metadata: DocumentFileName contains invalid value"
+ ).setSection(19).setPart(EPart.pdf));
+ }
+ xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version");
+ nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET);
+
+ // get all child nodes
+ // NodeList nodes = element.getChildNodes();
+ // expr.evaluate(docXMP, XPathConstants.NODESET);
+ // print the text content of each child
+ if (nodes.getLength() == 0) {
+ context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found")
+ .setSection(15).setPart(EPart.pdf));
+ }
+
+ boolean versionValid = false;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final String[] valueArray = {"1.0", "1p0", "2p0", "1.2", "2.0", "2.1", "2.2", "2.3", "3.0"}; //1.2, 2.0, 2.1, 2.2, 2.3 and 3.0 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1
+
+ if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
+ versionValid = true;
+ } // e.g. 1.0
+ }
+ if (!versionValid) {
+ context.addResultItem(
+ new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value")
+ .setSection(16).setPart(EPart.pdf));
+
+ }
+ } catch (final SAXException | IOException | ParserConfigurationException | XPathExpressionException e) {
+ LOGGER.error(e.getMessage(), e);
}
- } catch (final SAXException | IOException | ParserConfigurationException | XPathExpressionException e) {
- LOGGER.error(e.getMessage(), e);
}
zfXML = zi.getUTF8();
@@ -306,7 +306,7 @@ public class PDFValidator extends Validator {
final HashMap additionalData = zi.getAdditionalData();
for (final String filename : additionalData.keySet()) {
// validating xml in byte[] additionalData.get(filename)
- LOGGER.info("validating additionalData " + filename);
+ LOGGER.info("validating additionalData {}", filename);
validateSchema(additionalData.get(filename), "ad/basic/additional_data_base_schema.xsd", 2, EPart.pdf);
}
diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
index ce3cde48..4c5edb88 100644
--- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
+++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
@@ -32,13 +32,13 @@ public class ValidationContext {
}
if (logger != null) {
if ((vr.getSeverity() == ESeverity.fatal) || (vr.getSeverity() == ESeverity.exception)) {
- logger.error("Fatal Error " + vr.getSection() + ": " + vr.getMessage());
+ logger.error("Fatal Error {}: {}", vr.getSection(), vr.getMessage());
} else if ((vr.getSeverity() == ESeverity.error)) {
- logger.error("Error " + vr.getSection() + ": " + vr.getMessage());
+ logger.error("Error {}: {}", vr.getSection(), vr.getMessage());
} else if (vr.getSeverity() == ESeverity.warning) {
- logger.warn("Warning " + vr.getSection() + ": " + vr.getMessage());
+ logger.warn("Warning {}: {}", vr.getSection(), vr.getMessage());
} else if (vr.getSeverity() == ESeverity.notice) {
- logger.info("Notice " + vr.getSection() + ": " + vr.getMessage());
+ logger.info("Notice {}: {}", vr.getSection(), vr.getMessage());
}
}
@@ -106,20 +106,17 @@ public class ValidationContext {
}
public String getXMLResult() {
- String res = getCustomXML();
- if (results.size() > 0) {
- res += "";
+ StringBuilder res = new StringBuilder(getCustomXML());
+ if (results != null && !results.isEmpty()) {
+ res.append("");
+ for (final ValidationResultItem validationResultItem : results) {
+ // xml and pdf are handled in their respective sections
+ res.append(validationResultItem.getXMLOnce()).append("\n");
+ }
+ res.append("");
}
-
- for (final ValidationResultItem validationResultItem : results) {
- // xml and pdf are handled in their respective sections
- res += validationResultItem.getXMLOnce() + "\n";
- }
- if (results.size() > 0) {
- res += "";
- }
- res += "";
- return res;
+ res.append("");
+ return res.toString();
}
/***
diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
index 6f364751..345300a9 100644
--- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
@@ -8,6 +8,7 @@ import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.text.ParseException;
import java.util.Calendar;
import javax.xml.XMLConstants;
@@ -20,7 +21,9 @@ import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
+import org.mustangproject.CalculatedInvoice;
import org.mustangproject.XMLTools;
+import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -222,13 +225,7 @@ public class XMLValidator extends Validator {
isExtended = context.getProfile().contains("extended");
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox);
xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt";
-
- } else if (root.getLocalName().equalsIgnoreCase("SCRDMCCBDACIOMessageStructure")) {
- context.setGeneration("1");
- isOrderX = true;
- validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox);
- xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt";
-
+
} else if (root.getLocalName().equalsIgnoreCase("CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X
context.setGeneration("2");
@@ -306,7 +303,7 @@ public class XMLValidator extends Validator {
if (!xrVersion.equals("12") && !xrVersion.equals("20") && !xrVersion.equals("21") && !xrVersion.equals("22") && !xrVersion.equals("23") && !xrVersion.equals("30")) {
throw new Exception("Unsupported XR version");
}
- LOGGER.debug("is XRechnung v" + xrVersion);
+ LOGGER.debug("is XRechnung v{}", xrVersion);
xsltFilename = "/xslt/XR_" + xrVersion + "/XRechnung-UBL-validation.xslt";
XrechnungSeverity = ESeverity.error;
mainSchematronSectionErrorTypeCode = 27;
@@ -387,6 +384,7 @@ public class XMLValidator extends Validator {
}
}
}
+ checkArithmetics(context);
} catch (final IrrecoverableValidationError er) {
@@ -410,6 +408,28 @@ public class XMLValidator extends Validator {
}
+ private void checkArithmetics(ValidationContext context) {
+ ZUGFeRDInvoiceImporter zi=new ZUGFeRDInvoiceImporter();
+ try {
+ zi.fromXML(zfXML);
+ CalculatedInvoice ci=new CalculatedInvoice();
+ zi.extractInto(ci);
+
+ } catch ( ArithmeticException e) {
+ try {
+ context.addResultItem(new ValidationResultItem(ESeverity.warning, "Arithmetical issue:"+e.getMessage()).setSection(10));
+
+ } catch (IrrecoverableValidationError ie) {
+ LOGGER.error(ie.getMessage(), ie);
+ }
+ } catch (XPathExpressionException e) {
+ LOGGER.error(e.getMessage(), e);
+ } catch (ParseException e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+
+ }
+
public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError {
//Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or
@@ -507,7 +527,7 @@ public class XMLValidator extends Validator {
}
}
- LOGGER.info("FailedAssert ", thisFailText);
+ LOGGER.info("FailedAssert {}", thisFailText);
context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailIDStr + " from " + xsltFilename + ")")
.setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID)
diff --git a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java
index 621da5ef..589d72e3 100644
--- a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java
+++ b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java
@@ -17,8 +17,7 @@ public class ResourceCase extends TestCase {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCase.class.getCanonicalName()); // log output is
public static File getResourceAsFile(String resourcePath) {
- try {
- InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
+ try(InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) {
if (in == null) {
return null;
}
@@ -42,8 +41,7 @@ public class ResourceCase extends TestCase {
}
public static byte[] getResourceAsByteArray(String resourcePath) {
- try {
- InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
+ try(InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) {
if (in == null) {
return null;
}
diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java
index 2fda10ef..5bb8d1d9 100644
--- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java
+++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java
@@ -8,6 +8,8 @@ import org.xmlunit.builder.Input;
import org.xmlunit.xpath.JAXPXPathEngine;
import org.xmlunit.xpath.XPathEngine;
+import static org.xmlunit.assertj.XmlAssert.assertThat;
+
public class XMLValidatorTest extends ResourceCase {
public void testZF2XMLValidation() {
@@ -282,6 +284,30 @@ public class XMLValidatorTest extends ResourceCase {
}
+ public void testArithmetics() {
+ final ValidationContext ctx = new ValidationContext(null);
+ final XMLValidator xv = new XMLValidator(ctx);
+ final XPathEngine xpath = new JAXPXPathEngine();
+
+ File tempFile = getResourceAsFile("invalidArithmetics.xml");
+ try {
+ xv.setFilename(tempFile.getAbsolutePath());
+ xv.validate();
+
+ String s="" + xv.getXMLResult() + "";
+ Source source = Input.fromString(s).build();
+ String content = xpath.evaluate("/validation/summary/@status", source);
+ assertEquals("valid", content);
+ assertThat(s).valueByXPath("count(//warning)")
+ .asInt()
+ .isEqualTo(1);
+
+ } catch (final IrrecoverableValidationError e) {
+ // ignore, will be in XML output anyway
+ }
+
+ }
+
public void testXRValidationUBL() {
ValidationContext ctx = new ValidationContext(null);
XMLValidator xv = new XMLValidator(ctx);
diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
index 55affaa9..ec938490 100644
--- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
+++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java
@@ -219,7 +219,7 @@ public class ZUGFeRDValidatorTest extends ResourceCase {
.isEqualTo(2);
assertThat(res).valueByXPath("count(//warning)")
.asInt()
- .isEqualTo(2);
+ .isEqualTo(3);
assertThat(res).valueByXPath("count(//notice)")
.asInt()
diff --git a/validator/src/test/resources/invalidArithmetics.xml b/validator/src/test/resources/invalidArithmetics.xml
new file mode 100644
index 00000000..3986c2d3
--- /dev/null
+++ b/validator/src/test/resources/invalidArithmetics.xml
@@ -0,0 +1,186 @@
+
+
+
+
+ urn:cen.eu:en16931:2017
+
+
+
+ RE-20171118/506
+ 380
+ 20171118
+
+
+
+
+ 1
+
+
+ Künstlerische Gestaltung (Stunde): Einer Beispielrechnung
+
+
+
+
+ 160.0000
+ 1.0000
+
+
+ 160.0000
+ 1.0000
+
+
+
+ 2.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 160.00
+
+
+
+
+
+ 2
+
+
+ Luftballon: Bunt, ca. 500ml
+
+
+
+
+ 0.7900
+ 1.0000
+
+
+ 0.7900
+ 1.0000
+
+
+
+ 400.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 316.00
+
+
+
+
+
+ 3
+
+
+ Heiße Luft pro Liter
+
+
+
+
+ 0.1000
+ 1.0000
+
+
+ 0.1000
+ 1.0000
+
+
+
+ 200.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 20.00
+
+
+
+
+
+ Bei Spiel GmbH
+
+ 12345
+ Ecke 12
+ Stadthausen
+ DE
+
+
+ 22/815/0815/4
+
+
+ DE136695976
+
+
+
+ Theodor Est
+
+ 88802
+ Bahnstr. 42
+ Spielkreis
+ DE
+
+
+ DE999999999
+
+
+
+
+
+ 20171117
+
+
+
+ RE-20171118/506
+ EUR
+
+ 42
+ Überweisung
+
+ DE88 2008 0000 0970 3757 00
+
+
+ COBADEFFXXX
+
+
+
+ 11.20
+ VAT
+ 160.00
+ S
+ 7.00
+
+
+ 63.84
+ VAT
+ 336.00
+ S
+ 19.00
+
+
+ Zahlbar ohne Abzug bis 09.12.2017
+ 20171209
+
+
+ 496.00
+ 0.00
+ 0.00
+ 496.00
+ 75.04
+ 571.04
+ 571.04
+
+
+
+