diff --git a/History.md b/History.md
index afdc430a..9fb670dd 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,14 @@
+2.20.0
+=======
+2025-10-30
+
+- upgrade to pdfbox 3.0.6
+- #950 issues with nonshaded version: 2.19.1: ClassNotFoudException while running the PDFValidator
+- #959 Added FactoorSharp to the list of valid pdf sources
+- change return type of getCashDiscounts to CashDiscount object, not interface
+- #923 Support for BT-17 (tender or lot reference)
+- #960 incorrect calculation for product charges
+
2.19.1
=======
2025-10-09
diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml
index 8049c317..0d1cde8c 100644
--- a/Mustang-CLI/pom.xml
+++ b/Mustang-CLI/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.19.2-SNAPSHOT
+ 2.20.1-SNAPSHOT
4.0.0
Mustang-CLI
@@ -166,6 +166,7 @@
+ false
diff --git a/README.md b/README.md
index 6c4ecb6c..5cb16cb2 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ If you set up a Maven project, you can reference the mustang artifact like this:
org.mustangproject
validator
- 2.17.0
+ 2.20.0
shaded
```
diff --git a/library/pom.xml b/library/pom.xml
index b7511156..1590363f 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.19.2-SNAPSHOT
+ 2.20.1-SNAPSHOT
4.0.0
@@ -274,6 +274,7 @@
+ false
diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java
index 3bc1f4b6..9f0b4208 100644
--- a/library/src/main/java/org/mustangproject/Invoice.java
+++ b/library/src/main/java/org/mustangproject/Invoice.java
@@ -54,6 +54,7 @@ public class Invoice implements IExportableTransaction {
protected BigDecimal totalPrepaidAmount = null;
protected Date detailedDeliveryDateStart = null;
protected Date detailedDeliveryPeriodEnd = null;
+ protected IReferencedDocument tenderReference = null;
protected ArrayList Allowances = new ArrayList<>(),
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
@@ -135,8 +136,8 @@ public class Invoice implements IExportableTransaction {
}
@Override
- public IZUGFeRDCashDiscount[] getCashDiscounts() {
- return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
+ public CashDiscount[] getCashDiscounts() {
+ return cashDiscounts.toArray(new CashDiscount[0]);
}
@Override
@@ -144,6 +145,35 @@ public class Invoice implements IExportableTransaction {
return number;
}
+
+
+ @Override
+ /***
+ * BT-17
+ */
+ public IReferencedDocument getTenderReferencedDocument() {
+ return tenderReference;
+ }
+
+
+ /***
+ * BT-17
+ * @param dr
+ * @return
+ */
+ public Invoice setTenderReferencedDocument(ReferencedDocument dr) {
+ dr.setTypeCode("50");//50 is fixed for tender documents
+ tenderReference=dr;
+ return this;
+ }
+
+ public Invoice setTenderReferencedDocument(String ID) {
+ ReferencedDocument dr=new ReferencedDocument(ID);
+ setTenderReferencedDocument(dr);
+ return this;
+ }
+
+
public Invoice setNumber(String number) {
this.number = number;
return this;
diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java
index 5a3853a0..eff70131 100644
--- a/library/src/main/java/org/mustangproject/Item.java
+++ b/library/src/main/java/org/mustangproject/Item.java
@@ -3,10 +3,7 @@ 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;
-import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
-import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
-import org.mustangproject.ZUGFeRD.LineCalculator;
+import org.mustangproject.ZUGFeRD.*;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -90,6 +87,9 @@ public class Item implements IZUGFeRDExportableItem {
icnm.getAsNodeMap("ClassifiedTaxCategory")
.flatMap(m -> m.getAsBigDecimal("Percent"))
.ifPresent(product::setVATPercent);
+
+
+
});
itemMap.getAsNodeMap("AssociatedDocumentLineDocument")
.flatMap(icnm -> icnm.getAsString("LineID"))
@@ -132,6 +132,7 @@ public class Item implements IZUGFeRDExportableItem {
}
}
+
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
@@ -622,6 +623,7 @@ public class Item implements IZUGFeRDExportableItem {
return detailedDeliveryPeriodTo;
}
+
public IZUGFeRDExportableItem addNotes(Collection notes) {
if (notes == null) {
return this;
diff --git a/library/src/main/java/org/mustangproject/ReferencedDocument.java b/library/src/main/java/org/mustangproject/ReferencedDocument.java
index 7a1c04a0..b5eb239a 100644
--- a/library/src/main/java/org/mustangproject/ReferencedDocument.java
+++ b/library/src/main/java/org/mustangproject/ReferencedDocument.java
@@ -2,6 +2,7 @@ package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
+
import java.util.Date;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
@@ -17,6 +18,10 @@ public class ReferencedDocument implements IReferencedDocument {
String referenceTypeCode;
Date formattedIssueDateTime;
+ public ReferencedDocument() {
+ //bean
+ }
+
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode) {
this(issuerAssignedID);
this.typeCode = typeCode;
@@ -36,7 +41,7 @@ public class ReferencedDocument implements IReferencedDocument {
this(issuerAssingedID);
this.formattedIssueDateTime = formattedIssueDateTime;
}
-
+
public ReferencedDocument(String issuerAssignedID) {
this.issuerAssignedID = issuerAssignedID;
}
@@ -51,6 +56,7 @@ public class ReferencedDocument implements IReferencedDocument {
/**
* which type is the document? e.g. "916" for additional invoice related
+ *
* @param typeCode as String, e.g. 916
*/
public void setTypeCode(String typeCode) {
@@ -59,6 +65,7 @@ public class ReferencedDocument implements IReferencedDocument {
/**
* type of the reference of this line, a UNTDID 1153 code
+ *
* @param referenceTypeCode three uppercase character reference type code as string
*/
public void setReferenceTypeCode(String referenceTypeCode) {
@@ -90,8 +97,7 @@ public class ReferencedDocument implements IReferencedDocument {
}
@Override
- public Date getFormattedIssueDateTime()
- {
+ public Date getFormattedIssueDateTime() {
return formattedIssueDateTime;
}
@@ -100,9 +106,20 @@ public class ReferencedDocument implements IReferencedDocument {
return null;
}
NodeMap nodes = new NodeMap(node);
- return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
+ ReferencedDocument rd = new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
nodes.getAsStringOrNull("ReferenceTypeCode"),
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
+ if (nodes.getAsStringOrNull("ID") != null) {
+ //sure sign for UBL: here ReferenceTypeCode is no element but a "schemeID" attribute to ID
+ Node idNode = nodes.getNode("ID").get();
+ if (idNode != null) {
+ Node schemeIDAttr = idNode.getAttributes().getNamedItem("schemeID");
+ if (schemeIDAttr != null) {
+ rd.setReferenceTypeCode(schemeIDAttr.getNodeValue());
+ }
+ }
+ }
+ return rd;
}
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
index 5d72a517..a7cbdbd4 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
@@ -215,6 +215,15 @@ public interface IExportableTransaction {
return null;
}
+ /**
+ * BT-17 tender or lot reference
+ *
+ * @return mandatory ID, optional Date
+ */
+ default IReferencedDocument getTenderReferencedDocument() {
+ return null;
+ }
+
/**
* own name
*
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
index 05d86170..05f4d437 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java
@@ -157,11 +157,11 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
- /***
- * specifies the item level delivery period (there is also one on document level),
- * this will be included in a BillingSpecifiedPeriod element
- * @return the beginning of the delivery period
- */
+ /***
+ * specifies the item level delivery period (there is also one on document level),
+ * this will be included in a BillingSpecifiedPeriod element
+ * @return the beginning of the delivery period
+ */
default Date getDetailedDeliveryPeriodFrom() {
return null;
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
index 33f883f4..faeb5671 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java
@@ -73,7 +73,7 @@ public class LineCalculator {
}
if (currentItem.getProduct().getCharges()!=null) {
for (IZUGFeRDAllowanceCharge ccaf : currentItem.getProduct().getCharges()) {
- delta = delta.subtract(ccaf.getTotalAmount(currentItem));
+ delta = delta.add(ccaf.getTotalAmount(currentItem));
}
}
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
index b303c353..cddadf7a 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
@@ -631,6 +631,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
+ if(trans.getTenderReferencedDocument() != null){
+ xml += ""
+ + "" + XMLTools.encodeXML(trans.getTenderReferencedDocument().getIssuerAssignedID()) + ""
+ + "" + 50 + "";
+ if (trans.getTenderReferencedDocument().getFormattedIssueDateTime()!=null) {
+ final SimpleDateFormat dateFormat102 = new SimpleDateFormat("yyyyMMdd");
+ xml += ""+XMLTools.encodeXML(dateFormat102.format(trans.getTenderReferencedDocument().getFormattedIssueDateTime()))+"";
+ }
+
+
+ xml += "";
+ }
if (trans.getSpecifiedProcuringProjectID() != null) {
xml += ""
+ "" + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectID()) + "";
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
index 6eb0fc08..66787cc3 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
@@ -696,6 +696,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
if ((meta != null) && (meta.getLength() > 0)) {
try {
DomXmpParser xmpParser = new DomXmpParser();
+ xmpParser.setStrictParsing(false);
return xmpParser.parse(meta.toByteArray());
} catch (XmpParsingException e) {
throw new IOException(e);
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java
index e128f0a6..12653cdf 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java
@@ -111,6 +111,7 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
if (metadata != null) {
try {
DomXmpParser xmpParser = new DomXmpParser();
+ xmpParser.setStrictParsing(false);
XMPMetadata xmp = xmpParser.parse(metadata.createInputStream());
PDFAIdentificationSchema pdfaSchema = xmp.getPDFAIdentificationSchema();
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
index 2f7cc2f4..5535e6f2 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
@@ -612,6 +612,18 @@ public class ZUGFeRDInvoiceImporter {
if (!issueDateStr.isEmpty()) {
issueDate = parseDate(issueDateStr, "yyyy-MM-dd");
}
+
+ String tenderReference = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim();
+ String tenderReferenceDate = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim();
+ if((tenderReference != null)&&(!tenderReference.isEmpty())){
+ if((tenderReferenceDate != null)&&(!tenderReferenceDate.isEmpty())){
+ zpp.setTenderReferencedDocument(new ReferencedDocument(tenderReference, parseDate(tenderReferenceDate, "yyyy-MM-dd")));
+ } else {
+ zpp.setTenderReferencedDocument(tenderReference);
+ }
+
+ }
+
String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim();
if (!dueDt.isEmpty()) {
dueDate = parseDate(dueDt, "yyyy-MM-dd");
@@ -677,6 +689,9 @@ public class ZUGFeRDInvoiceImporter {
NodeList headerTradeAgreementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
String buyerOrderIssuerAssignedID = null;
String sellerOrderIssuerAssignedID = null;
+ String additionalReferencedDocument = null;
+ Date additionalReferencedDocumentDate = null;
+
for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) {
// XMLTools.trimOrNull(nodes.item(i)))) {
Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i);
@@ -702,12 +717,55 @@ public class ZUGFeRDInvoiceImporter {
}
}
}
+ int typeC = 0;
+ additionalReferencedDocument=null;
+ additionalReferencedDocumentDate=null;
+ //Reading BT-17
+ if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
+ NodeList additionalChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
+ for (int additionalChildIndex = 0; additionalChildIndex < additionalChilds.getLength(); additionalChildIndex++) {
+
+ if ((additionalChilds.item(additionalChildIndex).getLocalName() != null)
+ && additionalChilds.item(additionalChildIndex).getLocalName().equals("TypeCode")) {
+ typeC = Integer.parseInt(XMLTools.trimOrNull(additionalChilds.item(additionalChildIndex)));
+ }
+
+ if ((additionalChilds.item(additionalChildIndex).getLocalName() != null)
+ && (additionalChilds.item(additionalChildIndex).getLocalName().equals("IssuerAssignedID"))) {
+ additionalReferencedDocument = XMLTools.trimOrNull(additionalChilds.item(additionalChildIndex));
+ }
+ if ((additionalChilds.item(additionalChildIndex).getLocalName() != null)
+ && (additionalChilds.item(additionalChildIndex).getLocalName().equals("FormattedIssueDateTime"))) {
+
+ NodeList FormattedIssueDateTimeChilds = additionalChilds.item(additionalChildIndex).getChildNodes();
+ for (int dateChildIndex = 0; dateChildIndex < FormattedIssueDateTimeChilds.getLength(); dateChildIndex++) {
+ if ((FormattedIssueDateTimeChilds.item(dateChildIndex).getLocalName() != null)
+ && (FormattedIssueDateTimeChilds.item(dateChildIndex).getLocalName().equals("DateTimeString"))) {
+ additionalReferencedDocumentDate = XMLTools.tryDate(FormattedIssueDateTimeChilds.item(dateChildIndex));
+ }
+
+ }
+ }
+
+ }
+ if (typeC == 50) {
+ if (additionalReferencedDocument != null){
+ if (additionalReferencedDocumentDate!=null) {
+ zpp.setTenderReferencedDocument(new ReferencedDocument(additionalReferencedDocument, additionalReferencedDocumentDate));
+ } else {
+ zpp.setTenderReferencedDocument(additionalReferencedDocument);
+ }
+ }
+
+ }
+ }
}
}
-
}
+
+
String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|//*[local-name()=\"DocumentCurrencyCode\"]");
zpp.setCurrency(currency);
@@ -950,6 +1008,7 @@ public class ZUGFeRDInvoiceImporter {
zpp.setDespatchAdviceReferencedDocumentID(s);
}
}
+
String invoiceReferencedDocumentID = extractString("//*[local-name()=\"InvoiceReferencedDocument\"]/*[local-name()=\"IssuerAssignedID\"]|//*[local-name()=\"BillingReference\"]/*[local-name()=\"InvoiceDocumentReference\"]/*[local-name()=\"ID\"]");
if (!invoiceReferencedDocumentID.isEmpty()) {
zpp.setInvoiceReferencedDocumentID(invoiceReferencedDocumentID);
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
index 3af67886..530e9994 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java
@@ -249,6 +249,54 @@ public class CalculationTest extends ResourceCase {
assertEquals(valueOf(101.85).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
}
+ @Test
+ public void testNullifyingAllowancesCharges() {
+ SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd");
+
+
+ Invoice invoice = new Invoice();
+ invoice.setDocumentName("Rechnung");
+ invoice.setNumber("777777");
+ try {
+ invoice.setIssueDate(sqlDate.parse("2020-12-31"));
+ invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1]));
+ invoice.setDeliveryDate(sqlDate.parse("2020-12-31"));
+ invoice.setDueDate(sqlDate.parse("2021-01-15"));
+ } catch (Exception e) {
+ LOGGER.error("Failed to set dates", e);
+
+ }
+
+ /* 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");
+ recipient.setID("111111");
+ recipient.addVATID("DE111111111");
+ invoice.setRecipient(recipient);
+
+ /* item */
+ Product product;
+ Item item;
+ BigDecimal amount=new BigDecimal("10.00");
+
+ product = new Product("AAA", "", "H87", BigDecimal.ZERO).setSellerAssignedID("1AAA");
+ product.addCharge(new Charge(amount).setReasonCode("ZZZ").setReason("Zuschlag"));
+ product.addAllowance((Allowance) new Allowance(amount).setReasonCode("95").setReason("Rabatt"));
+ item = new Item(product, new BigDecimal("4.750"), new BigDecimal(1.00));
+
+ // set values for additional charge and discount used for next lines
+ item.addCharge(new Charge(amount).setReasonCode("ZZZ").setReason("Zuschlag"));
+ item.addAllowance((Allowance) new Allowance(amount).setReasonCode("95").setReason("Rabatt"));
+ invoice.addItem(item);
+
+ TransactionCalculator calculator = new TransactionCalculator(invoice);
+ assertEquals(valueOf(4.750).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
+ }
+
public void testSimpleItemPercentAllowance() {
/***
* a product with net 1.10 and qty 5 and relative item allowance of 10% should return 5 as line and grand total
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2EdgeTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2EdgeTest.java
index f095372b..a82b160d 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2EdgeTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2EdgeTest.java
@@ -144,6 +144,38 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
return "DE";
}
+ @Override
+ public IReferencedDocument getTenderReferencedDocument() {
+ return new IReferencedDocument() {
+ @Override
+ public String getIssuerAssignedID() {
+ return "983-jk-787";
+ }
+
+ @Override
+ public String getTypeCode() {
+ return "50";
+ }
+
+ @Override
+ public String getReferenceTypeCode() {
+ return "";
+ }
+
+ @Override
+ public Date getFormattedIssueDateTime() {
+ SimpleDateFormat sdf=new SimpleDateFormat("YYYY-mm-dd");
+ try {
+ return sdf.parse("2025-10-12");
+ } catch (ParseException e) {
+ // wont happen, I promise :-)
+ }
+ return null; // wont happen either
+ }
+
+ };
+ }
+
@Override
public String getOwnLocation() {
return "Stadthausen";
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
index 32d77d49..428d8f89 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
@@ -596,6 +596,9 @@ public class ZF2PushTest extends TestCase {
try {
SchemedID gtin = new SchemedID("0160", "2001015001325");
SchemedID gln = new SchemedID("0088", "4304171000002");
+ ReferencedDocument dr1=new ReferencedDocument("90-kl-98798-C", sdf.parse("2025-10-12"));
+ ReferencedDocument dr2=new ReferencedDocument("90-kl-98798-C1", sdf.parse("2025-10-13"));
+ dr2.setReferenceTypeCode("AAG");
ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setPaymentReference("Verwendungszweck").setDocumentName("Rechnung")
.setSellerOrderReferencedDocumentID("9384").setBuyerOrderReferencedDocumentID("28934")
.setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo))
@@ -604,12 +607,12 @@ public class ZF2PushTest extends TestCase {
.setContractReferencedDocument(contractID)
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711")
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3"))
- .setInvoicer( new TradeParty("Abweichender Rechnungssteller", "Teststr.12", "04711", "Entenhausen", "DE") )
- .setInvoicee( new TradeParty("Abweichender Rechnungsempfänger", "Teststr.42", "00815", "Entenhausen", "DE") )
- .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).setId("a123").addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
+ .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).setId("a123").
+ addAdditionalReference(dr2).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
.addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16)))
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
.addCashDiscount(new CashDiscount(new BigDecimal(2), 14))
+ .setTenderReferencedDocument(dr1)
.setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE)
.setInvoiceReferencedDocumentID("abc123").addInvoiceReferencedDocument(new ReferencedDocument("abcd1234"))
);
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
index b99f7108..e9c65968 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java
@@ -20,6 +20,7 @@
*/
package org.mustangproject.ZUGFeRD;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
@@ -178,16 +179,79 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
hasExceptions = true;
}
assertFalse(hasExceptions);
+ SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-dd");
// Reading ZUGFeRD
assertEquals("4711", invoice.getZFItems()[0].getProduct().getSellerAssignedID());
assertEquals("9384", invoice.getSellerOrderReferencedDocumentID());
+ assertEquals("90-kl-98798-C", invoice.getTenderReferencedDocument().getIssuerAssignedID());
+
+ IReferencedDocument[] rd=invoice.getZFItems()[0].getAdditionalReferences();
+ assertEquals("90-kl-98798-C1", rd[0].getIssuerAssignedID());
+ assertEquals("AAG", rd[0].getReferenceTypeCode());
+
+
+
+ assertEquals("90-kl-98798-C", invoice.getTenderReferencedDocument().getIssuerAssignedID());
+ assertEquals("2025-10-12", sdf.format(invoice.getTenderReferencedDocument().getFormattedIssueDateTime()));
assertEquals("sender@test.org", invoice.getSender().getEmail());
assertEquals("recipient@test.org", invoice.getRecipient().getEmail());
assertEquals("28934", invoice.getBuyerOrderReferencedDocumentID());
+
+
+
}
+ public void testBT17InvoiceImport() {
+ boolean hasExceptions = false;
+ Invoice invoice = null;
+
+ ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushEdge.pdf");
+
+ try {
+ invoice = zii.extractInvoice();
+ } catch (XPathExpressionException | ParseException e) {
+ hasExceptions = true;
+ }
+ assertFalse(hasExceptions);
+ SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-dd");
+ // Reading ZUGFeRD
+ assertEquals("90-kl-98798-C", invoice.getTenderReferencedDocument().getIssuerAssignedID());
+ assertNotNull(invoice.getTenderReferencedDocument().getFormattedIssueDateTime());
+ assertEquals("2025-10-12", sdf.format(invoice.getTenderReferencedDocument().getFormattedIssueDateTime()));
+ try {
+ zii.setInputStream(new FileInputStream(getResourceAsFile("cii/bt17-response_1760553749128.cii.xml")));
+ invoice = zii.extractInvoice();
+ } catch (XPathExpressionException | ParseException | FileNotFoundException e) {
+ hasExceptions = true;
+ }
+ assertFalse(hasExceptions);
+ // Reading ZUGFeRD
+ assertEquals("Testing1", invoice.getTenderReferencedDocument().getIssuerAssignedID());
+
+ }
+
+ public void testBT128InvoiceImport() {
+ boolean hasExceptions = false;
+ Invoice invoice = null;
+
+ ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
+ try {
+ zii.setInputStream(new FileInputStream(getResourceAsFile("ubl/BT-128.ubl.xml")));
+
+ invoice = zii.extractInvoice();
+ } catch (XPathExpressionException | ParseException | FileNotFoundException e) {
+ hasExceptions = true;
+ }
+ assertFalse(hasExceptions);
+ SimpleDateFormat sdf=new SimpleDateFormat("YYYY-mm-dd");
+ // Reading ZUGFeRD
+ assertEquals("90-kl-98798-C1", invoice.getZFItems()[0].getAdditionalReferences()[0].getIssuerAssignedID());
+ assertEquals("AAG", invoice.getZFItems()[0].getAdditionalReferences()[0].getReferenceTypeCode());
+
+ }
+
public void testZF1Import() {
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-MustangGnuaccountingBeispielRE-20171118_506zf1.pdf");
diff --git a/library/src/test/resources/cii/bt17-response_1760553749128.cii.xml b/library/src/test/resources/cii/bt17-response_1760553749128.cii.xml
new file mode 100644
index 00000000..a54da434
--- /dev/null
+++ b/library/src/test/resources/cii/bt17-response_1760553749128.cii.xml
@@ -0,0 +1,181 @@
+
+
+ urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
+ RE-20170509/505
+ 2017-05-09
+ 2022-02-28
+ 380
+ USD
+ AB321
+
+ 123
+
+
+ Testing1
+
+
+
+
+ Ecke 12
+ Stadthausen
+ 12345
+
+ DE
+
+
+
+ DE0815
+
+ VAT
+
+
+
+ 0815
+
+ FC
+
+
+
+ Bei Spiel GmbH
+
+
+
+
+
+
+ Bahnstr. 42
+ Hinterhaus
+ Spielkreis
+ 88802
+
+ Zweiter Stock
+
+
+ DE
+
+
+
+ DE999999999
+
+ VAT
+
+
+
+ Theodor Est
+
+
+
+
+ 2017-05-07
+
+
+ Bahnstr. 42
+ Hinterhaus
+ Spielkreis
+ 88802
+
+ Zweiter Stock
+
+
+ DE
+
+
+
+
+
+ Theodor Est
+
+
+
+
+ 54
+
+
+ 14 Tage 2% Skonto, 30 Tage rein netto
+
+
+ 0
+
+ 337.6
+ 0
+
+ K
+ 0
+ Intra-community supply
+
+ VAT
+
+
+
+
+
+ 337.6
+ 337.6
+ 337.6
+ 0
+ 0
+ 0
+ 337.6
+
+
+ 1
+ 1
+ 1.6
+
+ 1825
+ 130
+
+
+ Künstlerische Gestaltung (Stunde): Einer Beispielrechnung
+
+ K
+ 0
+
+ VAT
+
+
+
+
+ 160
+ 100
+
+
+
+ 2
+ 400
+ 316
+
+ Bestellerweiterung für E&F Umbau
+
+ K
+ 0
+
+ VAT
+
+
+
+
+ 0.79
+ 1
+
+
+
+ 3
+ 200
+ 20
+
+ Heiße Luft pro Liter
+
+ K
+ 0
+
+ VAT
+
+
+
+
+ 0.1
+ 1
+
+
+
\ No newline at end of file
diff --git a/library/src/test/resources/ubl/BT-128.ubl.xml b/library/src/test/resources/ubl/BT-128.ubl.xml
new file mode 100644
index 00000000..fa9b13b5
--- /dev/null
+++ b/library/src/test/resources/ubl/BT-128.ubl.xml
@@ -0,0 +1,365 @@
+
+
+ urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0
+ urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
+ Test_EeISI_100
+ 2018-11-12
+ 2018-11-30
+ 380
+ #AAA#invoice note text
+ #AAA#invoice note text 2
+ EUR
+ NOK
+ uvz
+ 123
+
+ 2018-11-12
+ 2018-11-30
+
+
+ abc
+ def
+
+
+
+ abc123
+ 2018-10-04
+
+
+
+ lmn
+
+
+ ghi
+
+
+ 789
+
+
+ Supporting document ref
+ Supporting document descr
+
+ ZGVmYXVsdA==
+
+ External document location
+
+
+
+
+ rst
+ 130
+
+
+ 456
+
+
+
+ Seller electronic address
+
+ Seller identifier 1
+
+
+ Seller identifier 2
+
+
+ Seller trading name
+
+
+ Seller address line 1
+ Seller address line 2
+ Seller city
+ 12345
+ Seller country subdivision
+
+ Seller address line 3
+
+
+ DE
+
+
+
+ DE12345677
+
+ VAT
+
+
+
+ DE49294093
+
+ FC
+
+
+
+ Seller name
+ Seller additional legal information
+
+
+ Seller contact point
+ +41 345 654455
+ seller@contact.de
+
+
+
+
+
+ Buyer electronic address
+
+ Buyer identifier
+
+
+ Buyer trading name
+
+
+ Buyer address line 1
+ Buyer address line 2
+ Buyer city
+ 34562
+ Buyer country subdivision
+
+ Buyer address line 3
+
+
+ IE
+
+
+
+ IE394838894
+
+ VAT
+
+
+
+ Buyer name
+ Buyer legal registration identifier
+
+
+ Buyer contact point
+ +353 2948584
+ buyer@contact.ie
+
+
+
+
+
+ Payee identifier
+
+
+ Payee name
+
+
+
+
+ Tax representative name
+
+
+ Tax representative address line 1
+ Tax representative address line 2
+ Tax representative city
+ 23455
+ Tax representative country subdivision
+
+ Tax representative address line 3
+
+
+ DE
+
+
+
+ DE3949053
+
+ VAT
+
+
+
+
+ 2018-12-04
+
+ deliver location identifier
+
+ Deliver to address line 1
+ Deliver to address line 2
+ Deliver to city
+ 98765
+ Deliver to country subdivision
+
+ Deliver to address line 3
+
+
+ IE
+
+
+
+
+
+ Deliver to party name
+
+
+
+
+ total amount
+
+
+ false
+ 95
+ Doc allowance reason text
+ 1.00
+ 10
+ 1000
+
+ S
+ 5
+
+ VAT
+
+
+
+
+ true
+ AAA
+ Doc charge reason text
+ 1.00
+ 10
+ 1000
+
+ S
+ 5
+
+ VAT
+
+
+
+
+ 50
+
+ 1000
+ 50
+
+ S
+ 5
+
+ VAT
+
+
+
+
+ 1000
+ 0
+
+ E
+ 0
+ VATEX-EU-O
+ Exemtion reason text
+
+ VAT
+
+
+
+
+
+ 46
+
+
+ 200
+ 200
+ 205
+ 10
+ 10
+ 0
+ 205
+
+
+ 1a
+ Invoice line note
+ 10
+ 1000
+ 6789
+
+ 2018-11-12
+ 2018-11-30
+
+
+ 12345
+
+
+ 90-kl-98798-C1
+ 130
+
+
+ false
+ 95
+ Invoice line allowance reason
+ 1.00
+ 10
+ 1000
+
+
+ true
+ AAA
+ Invoice line charge reason
+ 1.00
+ 10
+ 1000
+
+
+ Item description
+ Item name
+
+ Item buyer's identifier
+
+
+ Item seller's identifier
+
+
+ Item standar identifier
+
+
+ IT
+
+
+ Item classification identifier0
+
+
+ S
+ 5
+
+ VAT
+
+
+
+ Color
+ Red
+
+
+ Size
+ L
+
+
+
+ 10
+ 1
+
+ false
+ 1
+ 11
+
+
+
+
+ 1b
+ 10
+ 1000
+
+ Item name 2
+
+ E
+ 0
+
+ VAT
+
+
+
+
+ 10
+
+
+
diff --git a/pom.xml b/pom.xml
index a2af2828..f7a14780 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.mustangproject
core
- 2.19.2-SNAPSHOT
+ 2.20.1-SNAPSHOT
pom
Mustang
@@ -71,7 +71,7 @@
2.20.0
4.0.2
12.8
- 3.0.5
+ 3.0.6
2.11
3.1.12
2.1.5
@@ -163,11 +163,11 @@
ossrh
- https://s01.oss.sonatype.org/content/repositories/snapshots
+ https://central.sonatype.com/repository/maven-snapshots/
ossrh
- https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
+ https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/
diff --git a/validator/pom.xml b/validator/pom.xml
index c0e260a7..42fbcadf 100644
--- a/validator/pom.xml
+++ b/validator/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.19.2-SNAPSHOT
+ 2.20.1-SNAPSHOT
4.0.0
validator
@@ -185,6 +185,7 @@
+ false
diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
index f8fe6156..d1ba1afc 100644
--- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
@@ -283,6 +283,7 @@ public class PDFValidator extends Validator {
final byte[] cibpdfbrewerSignature = "CIB pdf brewer".getBytes(StandardCharsets.UTF_8);
final byte[] lexofficeSignature = "lexoffice".getBytes(StandardCharsets.UTF_8);
final byte[] s2IndustriesSignature = "s2industries.ZUGFeRD.PDF".getBytes(StandardCharsets.UTF_8); // https://github.com/stephanstapel/ZUGFeRD-csharp
+ final byte[] factoorSharpSignature = "FactoorSharp".getBytes(StandardCharsets.UTF_8); // https://github.com/S2-Industries/FactoorSharp
final byte[] sevdeskSignature = "sevdesk".getBytes(StandardCharsets.UTF_8);
if (ByteArraySearcher.contains(fileContents, symtraxSignature)) {
@@ -305,6 +306,8 @@ public class PDFValidator extends Validator {
Signature = "Lexware office";
} else if (ByteArraySearcher.contains(fileContents, s2IndustriesSignature)) {
Signature = "ZUGFeRD.PDF-csharp";
+ } else if (ByteArraySearcher.contains(fileContents, factoorSharpSignature)) {
+ Signature = "FactoorSharp";
} else if (ByteArraySearcher.contains(fileContents, sevdeskSignature)) {
Signature = "sevdesk";
}