diff --git a/History.md b/History.md
index 701106d89..21dc49d36 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,30 @@
+#705
+#707
+-#708
+-#709
+607
+649
+650
+665
+684
+703
+701
+691
+
+
+
+
+
+2.16.1
+=======
+2025-01-21
+- #678 some ubl creditnote attributes are not parsed
+- #679 validation of a XR does not ignore whitespace
+- #681 IBAN assigned to invoice sender not recipient on direct debit
+- #689 incorrect element order when both charge reason and reasoncode are specified
+- be able to set detailedDeliveryPeriodFrom, detailedDeliveryPeriodTo MS188
+- updated verapdf from 1.26.1 to 1.26.2
+- cashDiscount JSON now corrently ignores values for cii and xr methods
2.16.0
=======
diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml
index 9b5e3bc9c..d5b924e99 100644
--- a/Mustang-CLI/pom.xml
+++ b/Mustang-CLI/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
4.0.0
org.mustangproject
@@ -12,7 +12,7 @@
should also work for XRechnung/CII.
jar
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
UTF-8
11
@@ -23,7 +23,7 @@
org.mustangproject
validator
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
diff --git a/library/pom.xml b/library/pom.xml
index 073661c1f..916957913 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -3,13 +3,13 @@
org.mustangproject
core
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
4.0.0
org.mustangproject
library
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
jar
Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)
FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
@@ -59,6 +59,12 @@
slf4j-api
2.0.9
+
+ net.sf.offo
+ fop-hyph
+ 2.0
+ runtime
+
net.sf.saxon
diff --git a/library/src/main/java/org/mustangproject/CashDiscount.java b/library/src/main/java/org/mustangproject/CashDiscount.java
index 2151489aa..b1993a020 100644
--- a/library/src/main/java/org/mustangproject/CashDiscount.java
+++ b/library/src/main/java/org/mustangproject/CashDiscount.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.IZUGFeRDCashDiscount;
@@ -63,6 +64,7 @@ public CashDiscount setDays(Integer days) {
/***
* @return this particular cash discount as cross industry invoice XML
*/
+ @JsonIgnore
public String getAsCII() {
return ""+
"Cash Discount"+
@@ -78,6 +80,7 @@ public String getAsCII() {
* XRechnung CIUS defined it's own proprietary format for a freetext field
* @return this particular cash discount in proprietary xrechnung format
*/
+ @JsonIgnore
public String getAsXRechnung() {
return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n";
}
diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java
index 04c8b5dc3..a03487ae6 100644
--- a/library/src/main/java/org/mustangproject/Invoice.java
+++ b/library/src/main/java/org/mustangproject/Invoice.java
@@ -69,6 +69,7 @@ public class Invoice implements IExportableTransaction {
protected String vatDueDateTypeCode = null;
protected String creditorReferenceID; // required when direct debit is used.
private BigDecimal roundingAmount=null;
+ private String paymentReference; // Remittance information / Verwendungszweck, BT-83
public Invoice() {
ZFItems = new ArrayList<>();
@@ -650,6 +651,16 @@ public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerms) {
return this;
}
+ @Override
+ public String getPaymentReference() {
+ return paymentReference;
+ }
+
+ public Invoice setPaymentReference(String paymentReference) {
+ this.paymentReference = paymentReference;
+ return this;
+ }
+
@Override
public TradeParty getDeliveryAddress() {
return deliveryAddress;
@@ -783,11 +794,20 @@ public Date getDetailedDeliveryPeriodFrom() {
return detailedDeliveryDateStart;
}
+ public Invoice setDetailedDeliveryPeriodFrom(Date dt) {
+ detailedDeliveryDateStart=dt;
+ return this;
+ }
+
@Override
public Date getDetailedDeliveryPeriodTo() {
return detailedDeliveryPeriodEnd;
}
+ public Invoice setDetailedDeliveryPeriodTo(Date dt) {
+ detailedDeliveryPeriodEnd=dt;
+ return this;
+ }
/**
* adds a free text paragraph, which will become an includedNote element
diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java
index 321199a53..4084e4c0a 100644
--- a/library/src/main/java/org/mustangproject/Item.java
+++ b/library/src/main/java/org/mustangproject/Item.java
@@ -174,6 +174,10 @@ public Item(NodeList itemChilds, boolean recalcPrice) {
.flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent"))
.ifPresent(product::setVATPercent);
+ icnm.getAsNodeMap("ApplicableTradeTax")
+ .flatMap(cnm -> cnm.getAsString("ExemptionReason"))
+ .ifPresent(product::setTaxExemptionReason);
+
if (recalcPrice && !BigDecimal.ZERO.equals(quantity)) {
icnm.getAsNodeMap("SpecifiedTradeSettlementLineMonetarySummation")
.flatMap(cnm -> cnm.getAsBigDecimal("LineTotalAmount"))
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
index 0a013ba34..686afd441 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java
@@ -314,6 +314,10 @@ default IZUGFeRDPaymentTerms getPaymentTerms() {
return null;
}
+ default String getPaymentReference() {
+ return null;
+ }
+
/**
* returns if a rebate agreements exists
*
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
index 2903d81d4..6958bb618 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
@@ -288,8 +288,8 @@ protected String getAllowanceChargeStr(IZUGFeRDAllowanceCharge allowance, IAbsol
final String allowanceChargeStr = "" +
chargeIndicator + "" + percentage +
"" + priceFormat(allowance.getTotalAmount(item)) + "" +
- reason +
reasonCode +
+ reason +
"";
return allowanceChargeStr;
}
@@ -323,8 +323,8 @@ protected String getItemTotalAllowanceChargeStr(IZUGFeRDAllowanceCharge allowanc
final String itemTotalAllowanceChargeStr = "" +
chargeIndicator + "" + percentage +
"" + currencyFormat(allowance.getTotalAmount(item)) + "" +
- reason +
reasonCode +
+ reason +
"";
return itemTotalAllowanceChargeStr;
}
@@ -388,14 +388,13 @@ public void generateXML(IExportableTransaction trans) {
+ ""
+ ""
+ ""
- + "" + XMLTools.encodeXML(trans.getNumber()) + ""
- // + "RECHNUNG"
- // + "380"
- + "" + typecode + ""
- + ""
- + DATE.udtFormat(trans.getIssueDate()) + "" // date
+ + "" + XMLTools.encodeXML(trans.getNumber()) + "";
+ if (profile == Profiles.getByName("Extended") && trans.getDocumentName() != null) {
+ xml += "" + XMLTools.encodeXML(trans.getDocumentName()) + "";
+ }
+ xml += "" + typecode + ""
+ + "" + DATE.udtFormat(trans.getIssueDate()) + "" // date
+ buildNotes(trans)
-
+ ""
+ "";
int lineID = 0;
@@ -662,8 +661,8 @@ public void generateXML(IExportableTransaction trans) {
if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + "";
}
- if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
- xml += "" + XMLTools.encodeXML(trans.getNumber()) + "";
+ if ((trans.getPaymentReference() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
+ xml += "" + XMLTools.encodeXML(trans.getPaymentReference()) + "";
}
xml += "" + trans.getCurrency() + "";
if (this.trans.getPayee() != null) {
@@ -744,12 +743,12 @@ public void generateXML(IExportableTransaction trans) {
"true" +
"" +
"" + currencyFormat(charge.getTotalAmount(calc)) + "";
- if (charge.getReason() != null) {
- xml += "" + XMLTools.encodeXML(charge.getReason()) + "";
- }
if (charge.getReasonCode() != null) {
xml += "" + charge.getReasonCode() + "";
}
+ if (charge.getReason() != null) {
+ xml += "" + XMLTools.encodeXML(charge.getReason()) + "";
+ }
xml += "" +
"VAT" +
"" + charge.getCategoryCode() + "";
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
index c9c448c88..323115375 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
@@ -288,6 +288,7 @@ public void setID(String id) {
public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
String number = "";
+ String documentName = null;
String typeCode = null;
String deliveryPeriodStart = null;
String deliveryPeriodEnd = null;
@@ -309,7 +310,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
List includedNotes = new ArrayList<>();
//UBL...
- XPathExpression UBLNotesEx = xpath.compile("/*[local-name()=\"Invoice\"]/*[local-name()=\"Note\"]");
+ XPathExpression UBLNotesEx = xpath.compile("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"Note\"]");
NodeList UBLNotesNd = (NodeList) UBLNotesEx.evaluate(getDocument(), XPathConstants.NODESET);
if ((UBLNotesNd != null) && (UBLNotesNd.getLength() > 0)) {
for (int nodeIndex = 0; nodeIndex < UBLNotesNd.getLength(); nodeIndex++) {
@@ -328,8 +329,8 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
deliveryLocationNodeMap.getNode("ID").ifPresent(s -> {
if (s.getAttributes().getNamedItem("schemeID") != null) {
- SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent());
- delivery.addGlobalID(sID);
+ SchemedID sID = new SchemedID().setScheme(s.getAttributes().getNamedItem("schemeID").getTextContent()).setId(s.getTextContent());
+ delivery.addGlobalID(sID);
}
});
deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> {
@@ -496,6 +497,9 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) {
number = XMLTools.trimOrNull(item);
}
+ if ((item.getLocalName() != null) && (item.getLocalName().equals("Name"))) {
+ documentName = XMLTools.trimOrNull(item);
+ }
if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) {
typeCode = XMLTools.trimOrNull(item);
}
@@ -556,15 +560,16 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
}
zpp.addNotes(includedNotes);
String rootNode = extractString("local-name(/*)");
- if (rootNode.equals("Invoice")) {
+ if (rootNode.equals("Invoice") || rootNode.equals("CreditNote")) {
// UBL...
- number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim();
- typeCode = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"InvoiceTypeCode\"]").trim();
- String issueDateStr = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim();
- if (issueDateStr.length()>0) {
+ // //*[local-name()="Invoice" or local-name()="CreditNote"]
+ number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").trim();
+ typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim();
+ String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim();
+ if (issueDateStr.length() > 0) {
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(issueDateStr);
}
- String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim();
+ String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim();
if (dueDt.length() > 0) {
dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt);
}
@@ -650,7 +655,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
zpp.setCurrency(currency);
String paymentTermsDescription = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]|//*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]");
- if ((paymentTermsDescription!=null)&&(!paymentTermsDescription.isEmpty())) {
+ if ((paymentTermsDescription != null) && (!paymentTermsDescription.isEmpty())) {
zpp.setPaymentTermDescription(paymentTermsDescription);
}
@@ -667,6 +672,12 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
+ if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
+ && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("PaymentReference"))) {
+ String paymentReference = headerTradeSettlementChilds.item(settlementChildIndex).getTextContent();
+ zpp.setPaymentReference(paymentReference);
+ }
+
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
@@ -801,7 +812,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
}
- zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
+ zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentName(documentName).setDocumentCode(typeCode);
if ((directDebitMandateID != null) && (IBAN != null)) {
DirectDebit d = new DirectDebit(IBAN, directDebitMandateID);
@@ -812,9 +823,11 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
d.setPaymentMeansInformation(paymentMeansInformation);
}
zpp.getSender().addDebitDetails(d);
+ bankDetails.forEach(bankDetail -> zpp.getRecipient().addBankDetails(bankDetail));
+ } else {
+ bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
}
- bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
if (payeeNodes.getLength() > 0) {
zpp.setPayee(new TradeParty(payeeNodes));
@@ -845,15 +858,14 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
Node currentItemNode = nodes.item(i);
ReferencedDocument doc = ReferencedDocument.fromNode(currentItemNode);
- if (doc != null
- && (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID())
- || !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime())))
- {
+ if (doc != null
+ && (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID())
+ || !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime()))) {
zpp.addInvoiceReferencedDocument(doc);
}
}
}
-
+
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
String rounding = extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"Party\"]/*[local-name()=\"PayableRoundingAmount\"]");
@@ -881,7 +893,6 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
Item it = new Item(currentItemNode.getChildNodes(), recalcPrice);
zpp.addItem(it);
-
xpr = xpath.compile(".//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
NodeList chargeNodes = (NodeList) xpr.evaluate(currentItemNode, XPathConstants.NODESET);
for (int k = 0; k < chargeNodes.getLength(); k++) {
@@ -899,7 +910,7 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) {
- FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
+ FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
zpp.embedFileInXML(fa);
// filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
@@ -921,35 +932,68 @@ public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseEx
}
}
- if (zpp.getZFItems() != null && zpp.getZFItems().length > 0) {
- TransactionCalculator tc = new TransactionCalculator(zpp);
- String calculatedPayableTotal = tc.getDuePayable().toPlainString();
- EStandard whichType;
- try {
- whichType = getStandard();
- } catch (Exception e) {
- throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0);
+ xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"SpecifiedLogisticsServiceCharge\"]");// UBL unknown
+ chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
+ for (int i = 0; i < chargeNodes.getLength(); i++) {
+ NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
+ String chargeAmount = null;
+ String taxPercent = null;
+ for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
+ String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
+ if (chargeChildName != null) {
+ if (chargeChildName.equals("AppliedAmount")) {
+ chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
+ } else if (chargeChildName.equals("AppliedTradeTax")) {
+ NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
+ for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
+ String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
+ if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent"))) {
+ taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
+ }
+ }
+ }
+ }
+ //appliedAmount
+ //AppliedTradeTax
+ }
+ if (chargeAmount != null) {
+ Charge c = new Charge(new BigDecimal(chargeAmount));
+ if (taxPercent != null) {
+ c.setTaxPercent(new BigDecimal(taxPercent));
+ }
+ zpp.addCharge(c);
}
+ }
- if (whichType != EStandard.despatchadvice && !ignoreCalculationErrors) {
- // Check calculation if document type allows it and calculation errors should not be ignored
-
- String payableTotalFromXml = XMLTools.nDigitFormat(Objects.requireNonNullElse(duePayableAmount, expectedGrandTotal), 2);
- if (!calculatedPayableTotal.equals(payableTotalFromXml)) {
- String moreDetails = "";
- try {
- moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
- + Stream.of(tc.trans.getZFItems())
- .map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
- .collect(Collectors.joining(" + "));
- } catch (Exception ignored) {
- }
- throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
+
+ TransactionCalculator tc = new TransactionCalculator(zpp);
+ String calculatedPayableTotal = tc.getDuePayable().toPlainString();
+ EStandard whichType;
+ try {
+ whichType = getStandard();
+ } catch (Exception e) {
+ throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0);
+ }
+
+ if (whichType != EStandard.despatchadvice && !ignoreCalculationErrors) {
+ // Check calculation if document type allows it and calculation errors should not be ignored
+
+ String payableTotalFromXml = XMLTools.nDigitFormat(Objects.requireNonNullElse(duePayableAmount, expectedGrandTotal), 2);
+ if (!calculatedPayableTotal.equals(payableTotalFromXml)) {
+ String moreDetails = "";
+ try {
+ moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
+ + Stream.of(tc.trans.getZFItems())
+ .map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
+ .collect(Collectors.joining(" + "));
+ } catch (Exception ignored) {
}
+ throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
}
}
}
return zpp;
+
}
/**
@@ -1060,7 +1104,7 @@ public EStandard getStandard() throws Exception {
} else if (rootNode.equals("Invoice")) {
return EStandard.ubl;
} else if (rootNode.equals("CreditNote")) {
- return EStandard.ubl;
+ return EStandard.ubl_creditnote;
} else if (rootNode.equals("CrossIndustryInvoice")) {
return EStandard.facturx;
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
@@ -1105,11 +1149,17 @@ public String getUTF8() {
*
* @return the file attachments embedded in XML (using base64) decoded as byte array,
* for PDF embedded files in FX use getFileAttachmentsPDF()
+ * may return empty array
* @deprecated use invoice.getAdditionalReferencedDocuments
*/
@Deprecated
public List getFileAttachmentsXML() {
- return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
+ if (importedInvoice.getAdditionalReferencedDocuments()!=null) {
+ return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
+ } else {
+ return new ArrayList<>();
+ }
+
}
/***
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java
index 9b5c1e735..8795b478d 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java
@@ -45,6 +45,9 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
public class ZUGFeRDVisualizer {
@@ -254,16 +257,61 @@ public void toPDF(String xmlFilename, String pdfFilename) {
// the writing part
File XMLinputFile = new File(xmlFilename);
- String result = null;
+ String fopInput = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
- result = this.toFOP(XMLinputFile.getAbsolutePath());
+ fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (TransformerException | IOException e) {
LOGGER.error("Failed to apply FOP", e);
}
+
+ toPDFfromFOP(fopInput, () -> {
+ try {
+ return new FileOutputStream(pdfFilename);
+ } catch (FileNotFoundException e) {
+ LOGGER.error("Failed to create PDF", e);
+ }
+ return null;
+ }, (OutputStream out) -> {});
+ }
+
+ public byte[] toPDF(String xmlContent) {
+
+ String fopInput = null;
+
+ /* remove file endings so that tests can also pass after checking
+ out from git with arbitrary options (which may include CSRF changes)
+ */
+ try {
+ ByteArrayInputStream fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));
+ EStandard theStandard = findOutStandardFromRootNode(fis);
+ fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
+
+ fopInput = toFOP(fis, theStandard);
+ } catch (TransformerException | IOException e) {
+ LOGGER.error("Failed to apply FOP", e);
+ }
+
+ AtomicReference byteHolder = new AtomicReference<>();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ toPDFfromFOP(fopInput, () -> new BufferedOutputStream(os), (OutputStream out) -> {
+
+ try {
+ out.flush();
+ } catch (IOException e) {
+ LOGGER.error("Failed to create PDF", e);
+ }
+ byteHolder.set(os.toByteArray());
+ });
+
+ return byteHolder.get();
+ }
+
+ private void toPDFfromFOP(String fopInput, Supplier outputStreamDelegate, Consumer consumerDelegate) {
+
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = null;
@@ -291,7 +339,7 @@ out from git with arbitrary options (which may include CSRF changes)
// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
- try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
+ try (OutputStream out = new BufferedOutputStream(outputStreamDelegate.get())) {
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
@@ -302,13 +350,15 @@ out from git with arbitrary options (which may include CSRF changes)
// Step 5: Setup input and output for XSLT transformation
// Setup input stream
- Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
+ Source src = new StreamSource(new ByteArrayInputStream(fopInput.getBytes(StandardCharsets.UTF_8)));
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res);
+
+ consumerDelegate.accept(out);
} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
diff --git a/library/src/main/resources/stylesheets/result-pdf.xsl b/library/src/main/resources/stylesheets/result-pdf.xsl
index 0dfa7c952..f3e2e0d40 100644
--- a/library/src/main/resources/stylesheets/result-pdf.xsl
+++ b/library/src/main/resources/stylesheets/result-pdf.xsl
@@ -28,6 +28,14 @@
red
+
+
+ Das XML ist valide.
+
+
+ Das XML ist nicht valide.
+
+
green
@@ -37,18 +45,26 @@
-
+
Das ZUGFeRD-PDF ist valide.
-
+
Das ZUGFeRD-PDF ist nicht valide.
+
+
+ green
+
+
+ red
+
+
-
+
Es wird empfohlen, das Dokument anzunehmen und es weiterzuverarbeiten.
-
+
Es wird empfohlen, das Dokument zurückzuweisen.
@@ -118,6 +134,7 @@
+
+
+
+
+
+
+
+
+ true
+
+
+ urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
+
+
+
+ R87654321012345
+ WARENRECHNUNG
+ 380
+
+ 20180806
+
+
+ ST3
+ Es bestehen Rabatt- oder Bonusvereinbarungen.
+ AAK
+
+
+ EEV
+ Der Verkäufer bleibt Eigentümer der Waren bis zu vollständigen Erfüllung der Kaufpreisforderung.
+ AAJ
+
+
+ MUSTERLIEFERANT GMBH
+BAHNHOFSTRASSE 99
+99199 MUSTERHAUSEN
+Geschäftsführung:
+Max Mustermann
+USt-IdNr: DE123456789
+Telefon: +49 932 431 0
+www.musterlieferant.de
+HRB Nr. 372876
+Amtsgericht Musterstadt
+GLN 4304171000002
+WEEE-Reg-Nr.: DE87654321
+
+ REG
+
+
+ Leergutwert: 46,50
+
+
+ Wichtige Information: Bei Bestellungen bis zum 19.12. ist die Auslieferung bis spätestens 23.12. garantiert.
+
+
+
+
+
+ 1
+
+
+ 4123456000014
+ ZS997
+ Zitronensäure 100ml
+
+ Verpackungsart
+ BO
+
+
+
+
+ 1.0000
+
+
+ 1.0000
+
+
+
+ 100.0000
+ 4.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 100.00
+
+
+
+
+
+ 2
+
+
+ 4123456000021
+ GZ250
+ Gelierzucker Extra 250g
+
+
+
+ 1.5000
+
+
+ false
+
+ 0.0300
+ Artikelrabatt 1
+
+
+
+ false
+
+ 0.0200
+ Artikelrabatt 2
+
+
+
+ 1.4500
+
+
+
+ 50.0000
+ 1.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 72.50
+
+
+
+
+
+ 3
+
+
+ 4123456000021
+ GZ250
+ Gelierzucker Extra 250g
+ Artikel wie vereinbart ohne Berechnung
+
+
+
+ 0.0000
+
+
+ 0.0000
+
+
+
+ 10.0000
+ 1.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 0.00
+
+
+
+
+
+ 4
+
+
+ 4100130013294
+ 2031
+
+ Bierbrau Pils 20/0500
+ EAN-VKE: 4100130913297
+
+ Verpackung
+ Kiste
+
+
+
+
+ 12.0000
+
+
+ 12.0000
+
+
+
+ 15.0000
+ 20.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 180.00
+
+
+
+
+
+ 5
+
+
+ 2001015001325
+ 1805
+
+ Leergutpfand 20 x 0,5l
+
+ Verpackung
+ unverpackt
+
+
+
+
+ 3.1000
+
+
+ 3.1000
+
+
+
+ 15.0000
+ 1.0000
+
+
+
+ VAT
+ S
+ 19.00
+
+
+ 46.50
+
+
+
+
+
+ 6
+
+
+ 4123456000038
+ MP107
+ Mischpalette Joghurt Karton 3 x 20
+
+ Verpackung
+ Karton
+
+
+ 4123456001035
+ JOG103
+ Erdbeer 20 x 150g Becher
+ 20.0000
+
+
+ 4123456002032
+ JOG203
+ Banane 20 x 150g Becher
+ 20.0000
+
+
+ 4123456003039
+ JOG303
+ Schoko 20 x 150g Becher
+ 20.0000
+
+
+
+
+ 30.0000
+
+
+ false
+
+ 0.9000
+ Artikelrabatt 1
+
+
+
+ 29.1000
+
+
+
+ 2.0000
+ 1.0000
+
+
+
+ VAT
+ S
+ 7.00
+
+
+ 58.20
+
+
+
+
+
+ 549910
+ 4333741000005
+ MUSTERLIEFERANT GMBH
+
+
+ +49 932 431 500
+
+
+ max.mustermann@musterlieferant.de
+
+
+
+ 99199
+ BAHNHOFSTRASSE 99
+ MUSTERHAUSEN
+ DE
+
+
+ DE123456789
+
+
+
+ 009420
+ 4304171000002
+ MUSTER-KUNDE GMBH
+
+ 40235
+ KUNDENWEG 88
+ DUESSELDORF
+ DE
+
+
+
+ B123456789
+
+
+ A456123
+ 130
+
+
+
+
+ 4304171088093
+ MUSTER-MARKT
+
+ 8211
+
+
+ 31157
+ HAUPTSTRASSE 44
+ SARSTEDT
+ DE
+
+
+
+
+ 20180805
+
+
+
+ L87654321012345
+
+
+
+ EUR
+
+ 009420
+ 4304171000002
+ MUSTER-KUNDE GMBH
+
+ 40235
+ KUNDENWEG 88
+ DUESSELDORF
+ DE
+
+
+
+ 61.07
+ VAT
+ 321.40
+ 326.50
+ -5.10
+ S
+ 19.00
+
+
+ 8.93
+ VAT
+ 127.59
+ 130.70
+ -3.11
+ S
+ 7.00
+
+
+
+ false
+
+ 2.00
+ 280.00
+ 5.60
+ Rechnungsrabatt 1
+
+ VAT
+ S
+ 19.00
+
+
+
+
+ false
+
+ 2.00
+ 130.70
+ 2.61
+ Rechnungsrabatt 1
+
+ VAT
+ S
+ 7.00
+
+
+
+
+ false
+
+ 280.00
+ 2.50
+ Rechnungsrabatt 2
+
+ VAT
+ S
+ 19.00
+
+
+
+
+ false
+
+ 130.70
+ 0.50
+ Rechnungsrabatt 2
+
+ VAT
+ S
+ 7.00
+
+
+
+ Transportkosten
+ 3.00
+
+ VAT
+ S
+ 19.00
+
+
+
+ Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto.
+
+ 14
+ 2.00
+
+
+
+ 457.20
+ 3.00
+ 11.21
+ 448.99
+ 70.00
+ 518.99
+ 0.00
+ 518.99
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9dade18f1..7346b012d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.mustangproject
core
- 2.15.3-SNAPSHOT pom
+ 2.16.2-SNAPSHOT pom
Mustang
diff --git a/validator/pom.xml b/validator/pom.xml
index 67d7ca946..6c94fd9ee 100644
--- a/validator/pom.xml
+++ b/validator/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
4.0.0
org.mustangproject
@@ -11,7 +11,7 @@
Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung)
jar
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
@@ -38,7 +38,7 @@
${project.groupId}
library
- 2.15.3-SNAPSHOT
+ 2.16.2-SNAPSHOT
org.dom4j
@@ -73,7 +73,7 @@
org.verapdf
validation-model-jakarta
- 1.26.1
+ 1.26.2
diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
index 73b95f2e7..ce3cde48a 100644
--- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
+++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java
@@ -70,7 +70,7 @@ public ValidationContext setGeneration(String version) {
}
public ValidationContext setProfile(String profile) {
- this.profile = profile;
+ this.profile = profile.trim();
return this;
}