Merge branch 'master' into issues/435-importer-ubl
# Conflicts: # library/src/main/java/org/mustangproject/Item.java # library/src/main/java/org/mustangproject/XMLTools.java # library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java # library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java # library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
* the "name" of the bank account (holder)
|
||||
*/
|
||||
protected String accountName=null;
|
||||
/***
|
||||
* bean constructor
|
||||
*/
|
||||
public BankDetails() { }
|
||||
|
||||
/***
|
||||
* constructor for IBAN only :-)
|
||||
@@ -80,12 +86,14 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
* */
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public String getOwnBIC() {
|
||||
return getBIC();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public String getOwnIBAN() {
|
||||
return getIBAN();
|
||||
}
|
||||
|
||||
@@ -116,12 +116,20 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
|
||||
@Override
|
||||
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) {
|
||||
if (percent!=null) {
|
||||
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
|
||||
} else if(totalAmount != null) {
|
||||
return totalAmount;
|
||||
} else {
|
||||
throw new RuntimeException("percent must be set");
|
||||
}
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
if (totalAmount!=null) {
|
||||
return totalAmount;
|
||||
} else if (percent!=null) {
|
||||
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
|
||||
} else {
|
||||
throw new RuntimeException("Either totalAmount or percent must be set");
|
||||
throw new RuntimeException("totalAmount must be set");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
104
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
104
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* A schemed classification for products. The scheme can be anything defined in UNTDID 7143.
|
||||
*/
|
||||
public class ClassCode {
|
||||
private final String listID;
|
||||
private final String code;
|
||||
private String listVersionID;
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
* @param listVersionID an (optional) version of the scheme
|
||||
*/
|
||||
public ClassCode(String listID, String code, String listVersionID) {
|
||||
this.listID = listID;
|
||||
this.code = code;
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
*/
|
||||
public ClassCode(String listID, String code) {
|
||||
this(listID, code, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* Set the version for the scheme returned by {@link #getListID()}
|
||||
* @param listVersionID the scheme version
|
||||
*/
|
||||
public void setListVersionID(String listVersionID) {
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheme (according to UNTDID 7143) that describes the value returned by {@link #getCode()},
|
||||
* potentially versioned by {@link #getListVersionID()}
|
||||
*
|
||||
* @return the scheme
|
||||
*/
|
||||
public String getListID() {
|
||||
return listID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code that (following the scheme returned by {@link #getListID()}) describes the product
|
||||
*
|
||||
* @return the classification code itself
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (optional) version for the scheme returned by {@link #getListID()}
|
||||
*
|
||||
* @return the version or {@code null} if not set
|
||||
*/
|
||||
public String getListVersionID() {
|
||||
return listVersionID;
|
||||
}
|
||||
|
||||
public static ClassCode fromNode(Node node) {
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
if (attrs != null && attrs.getNamedItem("listID") != null) {
|
||||
ClassCode classCode = new ClassCode(attrs.getNamedItem("listID").getNodeValue(), node.getTextContent());
|
||||
if (attrs.getNamedItem("listVersionID") != null) {
|
||||
classCode.setListVersionID(attrs.getNamedItem("listVersionID").getNodeValue());
|
||||
}
|
||||
return classCode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
|
||||
/**
|
||||
* An implementation of {@link IDesignatedProductClassification} for describing a {@link org.mustangproject.Product}
|
||||
*
|
||||
*/
|
||||
public class DesignatedProductClassification implements IDesignatedProductClassification {
|
||||
private final ClassCode classCode;
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
* @param className a verbal description of the class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode, String className) {
|
||||
this.classCode = classCode;
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode) {
|
||||
this(classCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassCode getClassCode() {
|
||||
return classCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the human-readable name of the class code
|
||||
*
|
||||
* @param className the name of the class code (can be {@code null})
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.mustangproject;
|
||||
|
||||
public enum EStandard {
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl, ubl_creditnote
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected Date issueDate = null, dueDate = null, deliveryDate = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null;
|
||||
protected ArrayList<CashDiscount> cashDiscounts = null;
|
||||
@JsonDeserialize(contentAs = Item.class)
|
||||
protected ArrayList<IZUGFeRDExportableItem> ZFItems = null;
|
||||
@@ -577,6 +577,22 @@ public class Invoice implements IExportableTransaction {
|
||||
this.deliveryAddress = deliveryAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradeParty getPayee() {
|
||||
return this.payee;
|
||||
}
|
||||
|
||||
/***
|
||||
* if the payee is not the seller, it can be specified here
|
||||
* @param payee the payment receiving organisation
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setPayee(TradeParty payee) {
|
||||
this.payee = payee;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* Adds a cash discount (skonto)
|
||||
* @param c the CashDiscount percent/period combination
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
@@ -18,16 +19,22 @@ import java.util.Date;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Item implements IZUGFeRDExportableItem {
|
||||
protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount;
|
||||
protected BigDecimal price = BigDecimal.ZERO;
|
||||
protected BigDecimal quantity;
|
||||
protected BigDecimal tax;
|
||||
protected BigDecimal grossPrice;
|
||||
protected BigDecimal lineTotalAmount;
|
||||
protected BigDecimal basisQuantity = BigDecimal.ONE;
|
||||
protected Date detailedDeliveryPeriodFrom = null, detailedDeliveryPeriodTo = null;
|
||||
protected Date detailedDeliveryPeriodFrom = null;
|
||||
protected Date detailedDeliveryPeriodTo = null;
|
||||
protected String id;
|
||||
protected String referencedLineID = null;
|
||||
protected Product product;
|
||||
protected ArrayList<String> notes = null;
|
||||
protected ArrayList<ReferencedDocument> referencedDocuments = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
|
||||
Charges = new ArrayList<>();
|
||||
protected ArrayList<ReferencedDocument> additionalReference = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -41,7 +48,6 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
* do not use, but might be used e.g. by jackson
|
||||
@@ -50,234 +56,75 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
}
|
||||
|
||||
public Item(NodeList itemChilds, boolean recalcPrice) {
|
||||
String price = "0";
|
||||
String basisQuantity = "1";
|
||||
String name = "";
|
||||
String sellerAssignedID = null;
|
||||
String description = "";
|
||||
SchemedID gid = null;
|
||||
String quantity = "0";
|
||||
String vatPercent = null;
|
||||
String lineTotal = "0";
|
||||
String unitCode = "0";
|
||||
String referencedLineID = null;
|
||||
NodeMap itemMap = new NodeMap(itemChilds);
|
||||
|
||||
ArrayList<ReferencedDocument> rdocs = null;
|
||||
itemMap.getAsNodeMap("Item").ifPresent(icnm -> {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
setProduct(new Product());
|
||||
icnm.getAsString("Name").ifPresent(product::setName);
|
||||
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
});
|
||||
|
||||
// nodes.item(i).getTextContent())) {
|
||||
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
||||
// ubl
|
||||
// PriceAmount with currencyID and BaseQuantity with unitCode
|
||||
icnm.getAsBigDecimal("PriceAmount").ifPresent(this::setPrice);
|
||||
icnm.getAsBigDecimal("BaseQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) {
|
||||
String lineTrade = itemChilds.item(itemChildIndex).getLocalName();
|
||||
if ((lineTrade != null) && (lineTrade.equals("Item"))) {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
NodeList UBLitemChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLItemChildNode : XMLTools.asList(UBLitemChilds)) {
|
||||
itemMap.getNode("InvoicedQuantity").ifPresent(icn -> {
|
||||
// ubl
|
||||
setQuantity(new BigDecimal(icn.getTextContent().trim()));
|
||||
product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
|
||||
});
|
||||
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("Name"))) {
|
||||
name = currentUBLItemChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("ClassifiedTaxCategory"))) {
|
||||
for (Node currentUBLTaxChildNode : XMLTools.asList(currentUBLItemChildNode.getChildNodes())) {
|
||||
if ((currentUBLTaxChildNode.getLocalName() != null) && (currentUBLTaxChildNode.getLocalName().equals("Percent"))) {
|
||||
vatPercent = currentUBLTaxChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
|
||||
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
|
||||
.ifPresent(this::addReferencedLineID);
|
||||
|
||||
icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
|
||||
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
|
||||
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode)
|
||||
.forEach(this::addReferencedDocument);
|
||||
});
|
||||
|
||||
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);
|
||||
|
||||
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery")
|
||||
.flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity"))
|
||||
.ifPresent(bq -> {
|
||||
setQuantity(new BigDecimal(bq.getTextContent().trim()));
|
||||
if (bq.hasAttributes()) {
|
||||
Node unitAttr = bq.getAttributes().getNamedItem("unitCode");
|
||||
if (unitAttr != null) {
|
||||
product.setUnit(unitAttr.getNodeValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeSettlement", "SpecifiedSupplyChainTradeSettlement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("ApplicableTradeTax")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
|
||||
if (recalcPrice && !BigDecimal.ZERO.equals(quantity)) {
|
||||
icnm.getAsNodeMap("SpecifiedTradeSettlementLineMonetarySummation")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("LineTotalAmount"))
|
||||
.ifPresent(lineTotal -> setPrice(lineTotal.divide(quantity, 4, RoundingMode.HALF_UP)));
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("Price"))) {
|
||||
// ubl
|
||||
// PriceAmount with currencyID and BaseQuantity with unitCode
|
||||
NodeList UBLpriceChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLPriceChildNode : XMLTools.asList(UBLpriceChilds)) {
|
||||
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("PriceAmount"))) {
|
||||
price = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("BaseQuantity"))) {
|
||||
basisQuantity = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("InvoicedQuantity"))) {
|
||||
// ubl
|
||||
quantity = itemChilds.item(itemChildIndex).getTextContent();
|
||||
unitCode = itemChilds.item(itemChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeAgreement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeAgreement"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
|
||||
String IssuerAssignedID = "";
|
||||
String TypeCode = "";
|
||||
String ReferenceTypeCode = "";
|
||||
|
||||
NodeList refDocChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int refDocIndex = 0; refDocIndex < refDocChilds.getLength(); refDocIndex++) {
|
||||
String localName = refDocChilds.item(refDocIndex).getLocalName();
|
||||
if ((localName != null) && (localName.equals("IssuerAssignedID"))) {
|
||||
IssuerAssignedID = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("TypeCode"))) {
|
||||
TypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("ReferenceTypeCode"))) {
|
||||
ReferenceTypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
|
||||
ReferenceTypeCode);
|
||||
if (rdocs == null) {
|
||||
rdocs = new ArrayList<>();
|
||||
}
|
||||
rdocs.add(rd);
|
||||
|
||||
}
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds.item(tradeLineChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
|
||||
NodeList docChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int docIndex = 0; docIndex < docChilds.getLength(); docIndex++) {
|
||||
String localName = docChilds.item(docIndex).getLocalName();
|
||||
if ((localName != null) && (localName.equals("LineID"))) {
|
||||
referencedLineID = docChilds.item(docIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("NetPriceProductTradePrice")) {
|
||||
NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) {
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& (netChilds.item(netIndex).getLocalName().equals("ChargeAmount"))) {
|
||||
price = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
|
||||
}
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& ((netChilds.item(netIndex).getLocalName().equals("BasisQuantity")) || (netChilds.item(netIndex).getLocalName().equals("InvoicedQuantity")))) {
|
||||
basisQuantity = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeDelivery")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeDelivery"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
String tradeName = tradeLineChilds.item(tradeLineChildIndex).getLocalName();
|
||||
if ((tradeName != null)
|
||||
&& (tradeName.equals("BilledQuantity") || tradeName.equals("RequestedQuantity")
|
||||
|| tradeName.equals("DespatchedQuantity"))) {
|
||||
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
|
||||
quantity = tradeLineChilds.item(tradeLineChildIndex).getTextContent();
|
||||
unitCode = tradeLineChilds.item(tradeLineChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedTradeProduct"))) {
|
||||
NodeList tradeProductChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeProductChildIndex = 0; tradeProductChildIndex < tradeProductChilds
|
||||
.getLength(); tradeProductChildIndex++) {
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("Name"))) {
|
||||
name = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("SellerAssignedID"))) {
|
||||
sellerAssignedID = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("GlobalID"))) {
|
||||
if (tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID") != null) {
|
||||
gid = new SchemedID()
|
||||
.setScheme(tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue())
|
||||
.setId(tradeProductChilds.item(tradeProductChildIndex).getTextContent());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeSettlement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeSettlement"))) {
|
||||
NodeList tradeSettlementChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeSettlementChildIndex = 0; tradeSettlementChildIndex < tradeSettlementChilds
|
||||
.getLength(); tradeSettlementChildIndex++) {
|
||||
|
||||
String tradeSettlementName = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getLocalName();
|
||||
if (tradeSettlementName != null) {
|
||||
if (tradeSettlementName.equals("ApplicableTradeTax")) {
|
||||
NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds
|
||||
.getLength(); taxChildIndex++) {
|
||||
String taxChildName = taxChilds.item(taxChildIndex).getLocalName();
|
||||
if ((taxChildName != null) && (taxChildName.equals("RateApplicablePercent")
|
||||
|| taxChildName.equals("ApplicablePercent"))) {
|
||||
vatPercent = taxChilds.item(taxChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradeSettlementName.equals("SpecifiedTradeSettlementLineMonetarySummation") || tradeSettlementName.equals("SpecifiedTradeSettlementMonetarySummation")) {
|
||||
NodeList totalChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int totalChildIndex = 0; totalChildIndex < totalChilds
|
||||
.getLength(); totalChildIndex++) {
|
||||
if ((totalChilds.item(totalChildIndex).getLocalName() != null) && (totalChilds
|
||||
.item(totalChildIndex).getLocalName().equals("LineTotalAmount"))) {
|
||||
lineTotal = totalChilds.item(totalChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BigDecimal prc = new BigDecimal(price.trim());
|
||||
BigDecimal qty = new BigDecimal(quantity.trim());
|
||||
if ((recalcPrice) && (!qty.equals(BigDecimal.ZERO))) {
|
||||
prc = new BigDecimal(lineTotal.trim()).divide(qty, 18, RoundingMode.HALF_UP);
|
||||
}
|
||||
Product p = new Product(name, description, unitCode,
|
||||
vatPercent == null ? null : new BigDecimal(vatPercent.trim()));
|
||||
if (gid != null) {
|
||||
p.addGlobalID(gid);
|
||||
}
|
||||
if (sellerAssignedID != null) {
|
||||
p.setSellerAssignedID(sellerAssignedID);
|
||||
}
|
||||
setProduct(p);
|
||||
setPrice(prc);
|
||||
setQuantity(qty);
|
||||
setBasisQuantity(new BigDecimal(basisQuantity));
|
||||
if (rdocs != null) {
|
||||
for (ReferencedDocument rdoc : rdocs) {
|
||||
addReferencedDocument(rdoc);
|
||||
}
|
||||
}
|
||||
addReferencedLineID( referencedLineID );
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public Item addReferencedLineID(String s) {
|
||||
referencedLineID = s;
|
||||
return this;
|
||||
@@ -465,6 +312,29 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
return referencedDocuments.toArray(new IReferencedDocument[0]);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
|
||||
* @param doc the ReferencedDocument to add
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Item addAdditionalReference(ReferencedDocument doc) {
|
||||
if (additionalReference == null) {
|
||||
additionalReference = new ArrayList<>();
|
||||
}
|
||||
additionalReference.add(doc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IReferencedDocument[] getAdditionalReferences() {
|
||||
if (additionalReference == null) {
|
||||
return null;
|
||||
}
|
||||
return additionalReference.toArray(new IReferencedDocument[0]);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* specify a item level delivery period
|
||||
* (apart from the document level delivery period, and the document level
|
||||
@@ -485,6 +355,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the beginning of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
return detailedDeliveryPeriodFrom;
|
||||
}
|
||||
@@ -494,6 +365,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the end of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return detailedDeliveryPeriodTo;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.mustangproject.ZUGFeRD.*;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/***
|
||||
* A organisation, i.e. usually a company
|
||||
@@ -9,35 +11,68 @@ import org.mustangproject.ZUGFeRD.*;
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
|
||||
|
||||
protected String ID = null;
|
||||
protected String SchemeID = null;
|
||||
protected SchemedID schemedID = null;
|
||||
protected String tradingBusinessName = null;
|
||||
|
||||
public LegalOrganisation() {
|
||||
}
|
||||
|
||||
public LegalOrganisation(String ID, String scheme) {
|
||||
this.ID=ID;
|
||||
this.SchemeID=scheme;
|
||||
this.schemedID = new SchemedID(scheme, ID);
|
||||
}
|
||||
|
||||
public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) {
|
||||
this.schemedID = schemedID;
|
||||
this.tradingBusinessName=tradingBusinessName;
|
||||
}
|
||||
|
||||
/***
|
||||
* XML parsing constructor
|
||||
* @param nodes the nodelist returned e.g. from xpath
|
||||
*/
|
||||
public LegalOrganisation(NodeList nodes) {
|
||||
if (nodes.getLength() > 0) {
|
||||
/*
|
||||
will parse sth like
|
||||
<ram:SpecifiedLegalOrganization>
|
||||
<ram:ID schemeID="0002">4711</ram:ID>
|
||||
<ram:TradingBusinessName>Test GmbH & Co.KG</ram:TradingBusinessName>
|
||||
</ram:SpecifiedLegalOrganization>
|
||||
*/
|
||||
for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
|
||||
Node currentItemNode = nodes.item(nodeIndex);
|
||||
if (currentItemNode.getLocalName() != null) {
|
||||
if (currentItemNode.getLocalName().equals("GlobalID")) {
|
||||
if (currentItemNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
SchemedID gid = new SchemedID().setScheme(currentItemNode.getAttributes().getNamedItem("schemeID").getNodeValue()).setId(currentItemNode.getTextContent());
|
||||
this.setSchemedID(gid);
|
||||
}
|
||||
}
|
||||
if (currentItemNode.getLocalName().equals("TradingBusinessName")) {
|
||||
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return ID;
|
||||
public SchemedID getSchemedID() {
|
||||
return this.schemedID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemeID() {
|
||||
return SchemeID;
|
||||
public String getTradingBusinessName() {
|
||||
return this.tradingBusinessName;
|
||||
}
|
||||
|
||||
public LegalOrganisation setID(String id) {
|
||||
this.ID=id;
|
||||
public LegalOrganisation setSchemedID(SchemedID schemedID) {
|
||||
this.schemedID = schemedID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LegalOrganisation setSchemeID(String scheme) {
|
||||
SchemeID=scheme;
|
||||
public LegalOrganisation setTradingBusinessName(String tradingBusinessName) {
|
||||
this.tradingBusinessName = tradingBusinessName;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* describes a product, good or service used in an invoice item line
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Product implements IZUGFeRDExportableProduct {
|
||||
protected String unit, name, description, sellerAssignedID, buyerAssignedID;
|
||||
protected String unit, name, sellerAssignedID, buyerAssignedID;
|
||||
protected String description="";
|
||||
protected String taxExemptionReason=null;
|
||||
protected String taxCategoryCode=null;
|
||||
protected BigDecimal VATPercent;
|
||||
protected boolean isReverseCharge = false;
|
||||
protected boolean isIntraCommunitySupply = false;
|
||||
protected SchemedID globalId = null;
|
||||
protected String countryOfOrigin = null;
|
||||
protected HashMap<String, String> attributes = null;
|
||||
protected HashMap<String, String> attributes = new HashMap<>();
|
||||
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -33,6 +44,42 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
this.VATPercent = VATPercent;
|
||||
}
|
||||
|
||||
public Product(Node node) {
|
||||
NodeMap nodeMap = new NodeMap(node);
|
||||
|
||||
nodeMap.getNode("GlobalID").ifPresent(idNode -> {
|
||||
if (idNode.hasAttributes()
|
||||
&& idNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
globalId = new SchemedID()
|
||||
.setScheme(idNode.getAttributes().getNamedItem("schemeID").getNodeValue())
|
||||
.setId(idNode.getTextContent());
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
if (key != null && value != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
|
||||
}
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
@@ -65,6 +112,47 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. intra-commnunity supply or small business
|
||||
*/
|
||||
@Override
|
||||
public String getTaxExemptionReason() {
|
||||
return taxExemptionReason;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param taxExemptionReasonText String e.g. Kleinunternehmer gemäß §19 UStG https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxExemptionReason(String taxExemptionReasonText) {
|
||||
taxExemptionReason = taxExemptionReasonText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply)
|
||||
*/
|
||||
@Override
|
||||
public String getTaxCategoryCode() {
|
||||
if (taxCategoryCode == null) {
|
||||
return IZUGFeRDExportableProduct.super.getTaxCategoryCode();
|
||||
}
|
||||
return taxCategoryCode;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param code e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply) see also https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxCategoryCode(String code) {
|
||||
taxCategoryCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getSellerAssignedID() {
|
||||
@@ -124,6 +212,8 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
public Product setIntraCommunitySupply() {
|
||||
isIntraCommunitySupply = true;
|
||||
setVATPercent(BigDecimal.ZERO);
|
||||
setTaxExemptionReason("Intra-community supply");
|
||||
setTaxCategoryCode("K");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -201,19 +291,57 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
|
||||
@Override
|
||||
public HashMap<String, String> getAttributes() {
|
||||
return this.attributes;
|
||||
if (attributes.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public Product setAttributes(HashMap<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
public Product setAttributes(Map<String, String> attributes) {
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addAttribute(String name, String value ) {
|
||||
if ( this.attributes == null ) {
|
||||
this.attributes = new HashMap<>();
|
||||
}
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDesignatedProductClassification[] getClassifications() {
|
||||
if (classifications.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return classifications.toArray(new IDesignatedProductClassification[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the current set of {@link IDesignatedProductClassification}s with a new set
|
||||
*
|
||||
* @param classifications the new set of classifications
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product setClassifications(IDesignatedProductClassification[] classifications) {
|
||||
this.classifications.clear();
|
||||
if (classifications != null) {
|
||||
this.classifications.addAll(Arrays.asList(classifications));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link IDesignatedProductClassification} classification
|
||||
*
|
||||
* @param classification the classification
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product addClassification(IDesignatedProductClassification classification) {
|
||||
this.classifications.add(classification);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
public class ReferencedDocument implements IReferencedDocument {
|
||||
|
||||
@@ -58,4 +60,14 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
public String getReferenceTypeCode() {
|
||||
return referenceTypeCode;
|
||||
}
|
||||
|
||||
public static ReferencedDocument fromNode(Node node) {
|
||||
if (!node.hasChildNodes()) {
|
||||
return null;
|
||||
}
|
||||
NodeMap nodes = new NodeMap(node);
|
||||
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID"),
|
||||
nodes.getAsStringOrNull("TypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,9 @@ public class SchemedID {
|
||||
setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/***
|
||||
@@ -326,6 +327,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
addGlobalID(gid);
|
||||
}
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("SpecifiedLegalOrganization")) {
|
||||
NodeList organization = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
setLegalOrganisation(new LegalOrganisation(organization));
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("DefinedTradeContact")) {
|
||||
NodeList contact = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
setContact(new Contact(contact));
|
||||
@@ -420,6 +425,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationID() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getID();
|
||||
@@ -429,6 +435,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationIDScheme() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getScheme();
|
||||
|
||||
@@ -5,13 +5,12 @@ import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
@@ -22,36 +21,11 @@ public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
public String escapeElementEntities(String s) {
|
||||
return super.escapeElementEntities(s);
|
||||
|
||||
}
|
||||
|
||||
public static List<Node> asList(NodeList n) {
|
||||
return n.getLength() == 0 ?
|
||||
Collections.<Node>emptyList() : new NodeListWrapper(n);
|
||||
}
|
||||
|
||||
static final class NodeListWrapper extends AbstractList<Node>
|
||||
implements RandomAccess {
|
||||
private final NodeList list;
|
||||
|
||||
NodeListWrapper(NodeList l) {
|
||||
list = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node get(int index) {
|
||||
return list.item(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
public static String nDigitFormat(BigDecimal value, int scale) {
|
||||
/*
|
||||
* I needed 123,45, locale independent.I tried
|
||||
* I needed 123.45, locale independent.I tried
|
||||
* NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is locale
|
||||
* specific.I also tried DecimalFormat df = new DecimalFormat( "0,00" );
|
||||
* df.setDecimalSeparatorAlwaysShown(true); df.setGroupingUsed(false);
|
||||
@@ -115,6 +89,26 @@ public class XMLTools extends XMLWriter {
|
||||
}
|
||||
return XMLTools.tryBigDecimal(nodeValue);
|
||||
}
|
||||
/***
|
||||
* formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e.
|
||||
* cuts potential 0s off the end until minDecimals
|
||||
* @param value
|
||||
* @param maxDecimals number of maximal scale
|
||||
* @param minDecimals number of minimal scale
|
||||
* @return value as String with decimals in the specified range
|
||||
*/
|
||||
public static String nDigitFormatDecimalRange(BigDecimal value, int maxDecimals, int minDecimals) {
|
||||
if ((maxDecimals<minDecimals)||(maxDecimals<0)||(minDecimals<0)) {
|
||||
throw new IllegalArgumentException("Invalid scale range provided");
|
||||
}
|
||||
int curDecimals=maxDecimals;
|
||||
while ( (curDecimals>minDecimals) && (value.setScale(curDecimals, RoundingMode.HALF_UP).compareTo(value.setScale(curDecimals-1, RoundingMode.HALF_UP))==0)) {
|
||||
curDecimals--;
|
||||
}
|
||||
return value.setScale(curDecimals, RoundingMode.HALF_UP).toPlainString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns a util.Date from a 102 String yyyymmdd in a node
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.ClassCode;
|
||||
|
||||
/**
|
||||
* A product classification that allows to describe a product. Classification codes are schemed by the available systems in UNTDID 7143.
|
||||
*/
|
||||
public interface IDesignatedProductClassification {
|
||||
/**
|
||||
* Classification code
|
||||
*
|
||||
* @return the classification code
|
||||
*/
|
||||
ClassCode getClassCode();
|
||||
|
||||
/**
|
||||
* an optional, human-readable description of the classifcation code
|
||||
*
|
||||
* @return the name or {@code null} if not set
|
||||
*/
|
||||
default String getClassName() { return null; }
|
||||
}
|
||||
@@ -450,11 +450,19 @@ public interface IExportableTransaction {
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty delivery address
|
||||
*/
|
||||
|
||||
default IZUGFeRDExportableTradeParty getDeliveryAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* payee / payment receiver, if different from seller, ram:Payee (only supported for zf2)
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty payment receiver, if different from sellver
|
||||
*/
|
||||
default IZUGFeRDExportableTradeParty getPayee() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* specifies the document level delivery period, will be included in a
|
||||
* BillingSpecifiedPeriod element
|
||||
|
||||
@@ -92,12 +92,22 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
|
||||
/***
|
||||
* the ID of an additionally referenced document for this item
|
||||
* @deprecated use {@link #getAdditionalReferences()} instead.
|
||||
* @return the id as string
|
||||
*/
|
||||
@Deprecated
|
||||
default String getAdditionalReferencedDocumentID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* allows to specify multiple references (billing information)
|
||||
* @return the referenced documents
|
||||
*/
|
||||
default IReferencedDocument[] getAdditionalReferences() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* allows to specify multiple(!) referenced documents along with e.g. their typecodes
|
||||
|
||||
@@ -165,4 +165,11 @@ public interface IZUGFeRDExportableProduct {
|
||||
default HashMap<String, String> getAttributes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed information about the product
|
||||
*
|
||||
* @return an array containing the product classifications or {@code null} if not set
|
||||
*/
|
||||
default IDesignatedProductClassification[] getClassifications() { return null; }
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
import org.mustangproject.FileAttachment;
|
||||
|
||||
public interface IZUGFeRDExporter extends Closeable, IExporter {
|
||||
/**
|
||||
@@ -64,6 +65,8 @@ public interface IZUGFeRDExporter extends Closeable, IExporter {
|
||||
public String getNamespaceForVersion(int ver);
|
||||
public String getPrefixForVersion(int ver) ;
|
||||
public IZUGFeRDExporter disableAutoClose(boolean disableAutoClose);
|
||||
public void attachFile(FileAttachment file);
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation);
|
||||
public IXMLProvider getProvider();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,17 +18,19 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
public interface IZUGFeRDLegalOrganisation {
|
||||
import org.mustangproject.SchemedID;
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the ID of the legal organisation
|
||||
*/
|
||||
public String getID();
|
||||
public interface IZUGFeRDLegalOrganisation {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the scheme attribute of the legal organization=the type of the identification, e.g. 0002=Siren
|
||||
* @return the scheme attribute of the legal organization=the type of the identification, e.g. 0002=Siren, and its value
|
||||
*/
|
||||
public String getSchemeID();
|
||||
public SchemedID getSchemedID();
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the TradingBusinessName of the legal organisation
|
||||
*/
|
||||
public String getTradingBusinessName();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public interface IZUGFeRDTradeSettlement {
|
||||
|
||||
/***
|
||||
* gets the applicableHeaderTradeSettlement
|
||||
* @return zf2 xml
|
||||
*/
|
||||
@JsonIgnore
|
||||
String getSettlementXML();
|
||||
|
||||
|
||||
@@ -31,6 +34,7 @@ public interface IZUGFeRDTradeSettlement {
|
||||
* gets the applicableHeaderTradePayment
|
||||
* @return zf2 xml
|
||||
*/
|
||||
@JsonIgnore
|
||||
default String getPaymentXML() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement {
|
||||
@@ -27,6 +28,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
|
||||
*
|
||||
* @return payment information text
|
||||
*/
|
||||
@JsonIgnore
|
||||
default String getOwnPaymentInfoText() {
|
||||
return null;
|
||||
}
|
||||
@@ -59,6 +61,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
default String getSettlementXML() {
|
||||
String accountNameStr="";
|
||||
if (getAccountName()!=null) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LineCalculator {
|
||||
BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100));
|
||||
priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159
|
||||
price = priceGross.subtract(allowance).add(charge);
|
||||
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity())
|
||||
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity(), 18, RoundingMode.HALF_UP)
|
||||
.subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
}
|
||||
|
||||
/***
|
||||
* the invoice total with VAT, corrected by prepaid amount, allowances and
|
||||
* charges
|
||||
* the invoice total with VAT, allowances and
|
||||
* charges, WITHOUT considering prepaid amount
|
||||
*
|
||||
* @return the invoice total including taxes
|
||||
*/
|
||||
@@ -172,6 +172,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
LineCalculator lc = new LineCalculator(currentItem);
|
||||
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
|
||||
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
|
||||
String reasonText=currentItem.getProduct().getTaxExemptionReason();
|
||||
if (reasonText!=null) {
|
||||
itemVATAmount.setVatExemptionReasonText(reasonText);
|
||||
}
|
||||
VATAmount current = hm.get(percent.stripTrailingZeros());
|
||||
if (current == null) {
|
||||
hm.put(percent.stripTrailingZeros(), itemVATAmount);
|
||||
|
||||
@@ -32,6 +32,16 @@ import java.math.RoundingMode;
|
||||
*/
|
||||
public class VATAmount {
|
||||
|
||||
|
||||
protected BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
protected String categoryCode;
|
||||
|
||||
protected String vatExemptionReasonText;
|
||||
|
||||
protected String dueDateTypeCode;
|
||||
|
||||
|
||||
public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode) {
|
||||
super();
|
||||
this.basis = basis;
|
||||
@@ -48,35 +58,41 @@ public class VATAmount {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
}
|
||||
|
||||
BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
String categoryCode;
|
||||
|
||||
String dueDateTypeCode;
|
||||
|
||||
public BigDecimal getApplicablePercent() {
|
||||
return applicablePercent;
|
||||
}
|
||||
|
||||
public void setApplicablePercent(BigDecimal applicablePercent) {
|
||||
public VATAmount setApplicablePercent(BigDecimal applicablePercent) {
|
||||
|
||||
this.applicablePercent = applicablePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getBasis() {
|
||||
return basis;
|
||||
}
|
||||
|
||||
public void setBasis(BigDecimal basis) {
|
||||
public VATAmount setBasis(BigDecimal basis) {
|
||||
this.basis = basis.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getCalculated() {
|
||||
return calculated;
|
||||
}
|
||||
|
||||
public void setCalculated(BigDecimal calculated) {
|
||||
public VATAmount setCalculated(BigDecimal calculated) {
|
||||
this.calculated = calculated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVatExemptionReasonText() {
|
||||
return vatExemptionReasonText;
|
||||
}
|
||||
|
||||
public VATAmount setVatExemptionReasonText(String theText) {
|
||||
this.vatExemptionReasonText = theText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,34 +108,38 @@ public class VATAmount {
|
||||
/**
|
||||
* @param documentCode as String
|
||||
* @deprecated Use {@link #setCategoryCode(String)} instead
|
||||
* @return fluent setter
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDocumentCode(String documentCode) {
|
||||
public VATAmount setDocumentCode(String documentCode) {
|
||||
this.categoryCode = documentCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCategoryCode() {
|
||||
return categoryCode;
|
||||
}
|
||||
|
||||
public void setCategoryCode(String categoryCode) {
|
||||
public VATAmount setCategoryCode(String categoryCode) {
|
||||
this.categoryCode = categoryCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDueDateTypeCode() {
|
||||
return dueDateTypeCode;
|
||||
}
|
||||
|
||||
public void setDueDateTypeCode(String dueDateTypeCode) {
|
||||
public VATAmount setDueDateTypeCode(String dueDateTypeCode) {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VATAmount add(VATAmount v) {
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText());
|
||||
}
|
||||
|
||||
public VATAmount subtract(VATAmount v) {
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -387,9 +387,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
|
||||
+ "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount())
|
||||
+ "</ram:LineTotalAmount>"
|
||||
+ "</ram:SpecifiedTradeSettlementMonetarySummation>";
|
||||
if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
if (currentItem.getAdditionalReferences() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferences()[0].getIssuerAssignedID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
} else if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedSupplyChainTradeSettlement>"
|
||||
+ "<ram:SpecifiedTradeProduct>";
|
||||
|
||||
@@ -51,7 +51,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (ZUGFeRD2PullProvider.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRD2PullProvider.class);
|
||||
|
||||
protected byte[] zugferdData;
|
||||
protected IExportableTransaction trans;
|
||||
@@ -76,11 +76,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String priceFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
// 18 decimals are max for price and qty due to xml restrictions,
|
||||
// see Chapter 3.2.3 of https://www.w3.org/TR/xmlschema-2/
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
protected String quantityFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,7 +95,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
try {
|
||||
document = DocumentHelper.parseText(new String(zugferdData));
|
||||
} catch (final DocumentException e1) {
|
||||
LOGGER.error ("Failed to parse ZUGFeRD data", e1);
|
||||
LOGGER.error("Failed to parse ZUGFeRD data", e1);
|
||||
}
|
||||
try {
|
||||
final OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
@@ -103,7 +105,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
res = sw.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error ("Failed to write ZUGFeRD data", e);
|
||||
LOGGER.error("Failed to write ZUGFeRD data", e);
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -143,18 +145,27 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemeID()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getID()) + "</ram:ID>";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
if (profile == Profiles.getByName("Minimum")) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
} else {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
}
|
||||
if (party.getLegalOrganisation().getTradingBusinessName() != null) {
|
||||
xml += "<ram:TradingBusinessName>" + XMLTools.encodeXML(party.getLegalOrganisation().getTradingBusinessName()) + "</ram:TradingBusinessName>";
|
||||
}
|
||||
xml += "</ram:SpecifiedLegalOrganization>";
|
||||
}
|
||||
|
||||
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("EN16931") || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
xml += "<ram:DefinedTradeContact>";
|
||||
if (party.getContact().getName() != null) {
|
||||
xml += "<ram:PersonName>" + XMLTools.encodeXML(party.getContact().getName())
|
||||
xml += "<ram:PersonName>"
|
||||
+ XMLTools.encodeXML(party.getContact().getName())
|
||||
+ "</ram:PersonName>";
|
||||
}
|
||||
if (party.getContact().getPhone() != null) {
|
||||
|
||||
xml += "<ram:TelephoneUniversalCommunication><ram:CompleteNumber>"
|
||||
+ XMLTools.encodeXML(party.getContact().getPhone()) + "</ram:CompleteNumber>"
|
||||
+ "</ram:TelephoneUniversalCommunication>";
|
||||
@@ -195,7 +206,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "</ram:CityName>";
|
||||
}
|
||||
|
||||
//country IS mandatory
|
||||
//country IS mandatory
|
||||
xml += "<ram:CountryID>" + XMLTools.encodeXML(party.getCountry())
|
||||
+ "</ram:CountryID>"
|
||||
+ "</ram:PostalTradeAddress>";
|
||||
@@ -223,6 +234,30 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
}
|
||||
|
||||
protected String getTradePartyPayeeAsXML(IZUGFeRDExportableTradeParty party) {
|
||||
String xml = "";
|
||||
// According EN16931 either GlobalID or seller assigned ID might be present for a Payee
|
||||
if (party.getID() != null) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getID()) + "</ram:ID>";
|
||||
}
|
||||
if ((party.getGlobalIDScheme() != null) && (party.getGlobalID() != null)) {
|
||||
xml += "<ram:GlobalID schemeID=\"" + XMLTools.encodeXML(party.getGlobalIDScheme()) + "\">"
|
||||
+ XMLTools.encodeXML(party.getGlobalID())
|
||||
+ "</ram:GlobalID>";
|
||||
}
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
xml += "</ram:SpecifiedLegalOrganization>";
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns the XML for a charge or allowance on item level
|
||||
@@ -242,8 +277,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
|
||||
// only in extended profile
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
@@ -278,8 +312,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
|
||||
// only in extended profile
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
@@ -320,7 +353,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
paymentTermsDescription += discount.getAsXRechnung();
|
||||
}
|
||||
} else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) {
|
||||
if ( trans.getDueDate() != null ) {
|
||||
if (trans.getDueDate() != null) {
|
||||
paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate());
|
||||
}
|
||||
}
|
||||
@@ -418,8 +451,22 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
|
||||
"</ram:Description>";
|
||||
}
|
||||
if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
|
||||
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
|
||||
xml += "<ram:DesignatedProductClassification>"
|
||||
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
|
||||
if (classification.getClassCode().getListVersionID() != null) {
|
||||
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
|
||||
}
|
||||
xml += ">" + classification.getClassCode().getCode() + "</ram:ClassCode>";
|
||||
if (classification.getClassName() != null) {
|
||||
xml += "<ram:ClassName>" + XMLTools.encodeXML(classification.getClassName()) + "</ram:ClassName>";
|
||||
}
|
||||
xml += "</ram:DesignatedProductClassification>";
|
||||
}
|
||||
}
|
||||
if (currentItem.getProduct().getAttributes() != null) {
|
||||
for ( Entry<String, String> entry : currentItem.getProduct().getAttributes().entrySet() ) {
|
||||
for (Entry<String, String> entry : currentItem.getProduct().getAttributes().entrySet()) {
|
||||
xml += "<ram:ApplicableProductCharacteristic>" +
|
||||
"<ram:Description>" + XMLTools.encodeXML(entry.getKey()) + "</ram:Description>" +
|
||||
"<ram:Value>" + XMLTools.encodeXML(entry.getValue()) + "</ram:Value>" +
|
||||
@@ -428,8 +475,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
if (currentItem.getProduct().getCountryOfOrigin() != null) {
|
||||
xml += "<ram:OriginTradeCountry><ram:ID>" +
|
||||
XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) +
|
||||
"</ram:ID></ram:OriginTradeCountry>";
|
||||
XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) +
|
||||
"</ram:ID></ram:OriginTradeCountry>";
|
||||
}
|
||||
xml += "</ram:SpecifiedTradeProduct>"
|
||||
|
||||
@@ -500,9 +547,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
|
||||
+ "</ram:LineTotalAmount>" // currencyID=\"EUR\"
|
||||
+ "</ram:SpecifiedTradeSettlementLineMonetarySummation>";
|
||||
if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
if (currentItem.getAdditionalReferences() != null) {
|
||||
for (final IReferencedDocument currentReference : currentItem.getAdditionalReferences()) {
|
||||
xml += "<ram:AdditionalReferencedDocument>" +
|
||||
"<ram:IssuerAssignedID>" + XMLTools.encodeXML(currentReference.getIssuerAssignedID()) + "</ram:IssuerAssignedID>" +
|
||||
"<ram:TypeCode>130</ram:TypeCode>" +
|
||||
"<ram:ReferenceTypeCode>" + XMLTools.encodeXML(currentReference.getReferenceTypeCode()) + "</ram:ReferenceTypeCode>" +
|
||||
"</ram:AdditionalReferencedDocument>";
|
||||
}
|
||||
} else if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:IssuerAssignedID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:IssuerAssignedID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedLineTradeSettlement>"
|
||||
+ "</ram:IncludedSupplyChainTradeLineItem>";
|
||||
@@ -600,6 +654,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeDelivery>";
|
||||
xml += "<ram:ApplicableHeaderTradeSettlement>";
|
||||
|
||||
if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:CreditorReferenceID>" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + "</ram:CreditorReferenceID>";
|
||||
}
|
||||
@@ -607,6 +662,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>";
|
||||
}
|
||||
xml += "<ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>";
|
||||
if (this.trans.getPayee() != null) {
|
||||
xml += "<ram:PayeeTradeParty>" +
|
||||
getTradePartyPayeeAsXML(this.trans.getPayee()) +
|
||||
"</ram:PayeeTradeParty>";
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlementPayment() != null) {
|
||||
for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
|
||||
@@ -642,12 +702,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
|
||||
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
|
||||
if (getProfile() != Profiles.getByName("Minimum")) {
|
||||
String exemptionReasonTextXML = "";
|
||||
if ((displayExemptionReason) && (amount.getVatExemptionReasonText() != null)) {
|
||||
exemptionReasonTextXML = "<ram:ExemptionReason>" + XMLTools.encodeXML(amount.getVatExemptionReasonText()) + "</ram:ExemptionReason>";
|
||||
|
||||
}
|
||||
|
||||
xml += "<ram:ApplicableTradeTax>"
|
||||
+ "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated())
|
||||
+ "</ram:CalculatedAmount>" //currencyID=\"EUR\"
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>"
|
||||
+ (displayExemptionReason ? exemptionReason : "")
|
||||
+ exemptionReasonTextXML
|
||||
+ "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
|
||||
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
|
||||
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
|
||||
@@ -669,7 +734,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for(IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
|
||||
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>true</udt:Indicator>" +
|
||||
@@ -712,7 +777,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for(IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>false</udt:Indicator>" +
|
||||
@@ -852,8 +917,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String buildNotes(IExportableTransaction exportableTransaction) {
|
||||
final List<IncludedNote> includedNotes = Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode())
|
||||
.orElse(new ArrayList<>());
|
||||
final List<IncludedNote> includedNotes = new ArrayList<>();
|
||||
Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode()).ifPresent(includedNotes::addAll);
|
||||
|
||||
if (exportableTransaction.getNotes() != null) {
|
||||
for (final String currentNote : exportableTransaction.getNotes()) {
|
||||
includedNotes.add(IncludedNote.unspecifiedNote(currentNote));
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.xml.DomXmpParser;
|
||||
import org.apache.xmpbox.xml.XmpParsingException;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -72,7 +73,8 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
|
||||
|
||||
}
|
||||
protected IZUGFeRDExporter getExporter() {
|
||||
|
||||
public IZUGFeRDExporter getExporter() {
|
||||
if (theExporter==null) {
|
||||
throw new RuntimeException("In ZUGFeRDExporterFromPDFA, source must always be loaded before other operations are performed.");
|
||||
}
|
||||
@@ -247,5 +249,14 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
public void export(OutputStream output) throws IOException {
|
||||
getExporter().export(output);
|
||||
}
|
||||
|
||||
public void attachFile(FileAttachment file) {
|
||||
theExporter.attachFile(file);
|
||||
}
|
||||
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
|
||||
theExporter.attachFile(filename, data, mimetype, relation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,16 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* return the file names of all files embedded into the PDF
|
||||
* for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
|
||||
* @return a ArrayList of FileAttachments, empty if none
|
||||
*/
|
||||
public List<FileAttachment> getFileAttachmentsPDF() {
|
||||
return PDFAttachments;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Wrapper for protected method extractString
|
||||
* @param xpathStr the xpath expression to be evaluated
|
||||
|
||||
@@ -8,6 +8,11 @@ import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
@@ -309,6 +314,10 @@ public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
|
||||
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PayeeTradeParty\"]|//*[local-name()=\"PayeeParty\"]/*");
|
||||
NodeList payeeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
|
||||
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
@@ -325,6 +334,12 @@ public class ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
}
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
|
||||
NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (prepaidNodes.getLength() > 0) {
|
||||
zpp.setTotalPrepaidAmount(new BigDecimal(prepaidNodes.item(0).getTextContent()));
|
||||
}
|
||||
|
||||
Date issueDate = null;
|
||||
Date dueDate = null;
|
||||
Date deliveryDate = null;
|
||||
@@ -553,8 +568,13 @@ public class ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
|
||||
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
|
||||
|
||||
bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
|
||||
|
||||
if (payeeNodes.getLength() > 0) {
|
||||
zpp.setPayee(new TradeParty(payeeNodes));
|
||||
}
|
||||
|
||||
if (buyerOrderIssuerAssignedID != null) {
|
||||
zpp.setBuyerOrderReferencedDocumentID(buyerOrderIssuerAssignedID);
|
||||
}
|
||||
@@ -569,9 +589,9 @@ public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
|
||||
String buyerReference = null;
|
||||
totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (totalNodes.getLength() > 0) {
|
||||
buyerReference = totalNodes.item(0).getTextContent();
|
||||
prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (prepaidNodes.getLength() > 0) {
|
||||
buyerReference = prepaidNodes.item(0).getTextContent();
|
||||
}
|
||||
if (buyerReference != null) {
|
||||
zpp.setReferenceNumber(buyerReference);
|
||||
@@ -671,8 +691,8 @@ public class ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
|
||||
TransactionCalculator tc = new TransactionCalculator(zpp);
|
||||
|
||||
String expectedStringTotalGross = tc.getGrandTotal().toPlainString();
|
||||
String expectedStringTotalGross = tc.getGrandTotal()
|
||||
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
|
||||
EStandard whichType;
|
||||
try {
|
||||
whichType = getStandard();
|
||||
@@ -779,7 +799,7 @@ public class ZUGFeRDInvoiceImporter {
|
||||
/***
|
||||
*
|
||||
* @return the file attachments embedded in XML (using base64) decoded as byte array,
|
||||
* @see for PDF embedded files in FX use getFileAttachmentsPDF()
|
||||
* for PDF embedded files in FX use getFileAttachmentsPDF()
|
||||
*/
|
||||
public List<FileAttachment> getFileAttachmentsXML() {
|
||||
return fileAttachments;
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.apache.fop.configuration.ConfigurationException;
|
||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||
import org.apache.xmlgraphics.util.MimeConstants;
|
||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -96,6 +97,7 @@ public class ZUGFeRDVisualizer {
|
||||
private TransformerFactory mFactory = null;
|
||||
private Templates mXsltXRTemplate = null;
|
||||
private Templates mXsltUBLTemplate = null;
|
||||
private Templates mXsltCIOTemplate = null;
|
||||
private Templates mXsltHTMLTemplate = null;
|
||||
private Templates mXsltPDFTemplate = null;
|
||||
private Templates mXsltZF1HTMLTemplate = null;
|
||||
@@ -106,14 +108,46 @@ public class ZUGFeRDVisualizer {
|
||||
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
||||
}
|
||||
|
||||
/***
|
||||
* returns which standard is used, CII or UBL
|
||||
* @param fis inputstream (will be consumed)
|
||||
* @return (facturx = cii)
|
||||
*/
|
||||
public EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
String ublSignature = "Invoice";
|
||||
String ublCreditNoteSignature = "CreditNote";
|
||||
String cioSignature = "SCRDMCCBDACIOMessageStructure";
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
try {
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(new InputSource(fis));
|
||||
Element root = doc.getDocumentElement();
|
||||
if (root.getLocalName().equals(zf1Signature)) {
|
||||
return EStandard.zugferd;
|
||||
} else if (root.getLocalName().equals(zf2Signature)) {
|
||||
return EStandard.facturx;
|
||||
} else if (root.getLocalName().equals(ublSignature)) {
|
||||
return EStandard.ubl;
|
||||
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
|
||||
return EStandard.ubl_creditnote;
|
||||
} else if (root.getLocalName().equals(cioSignature)) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to recognize standard", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
@@ -147,32 +181,31 @@ public class ZUGFeRDVisualizer {
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
String ublSignature = "Invoice";
|
||||
String ublCreditNoteSignature = "CreditNote";
|
||||
boolean doPostProcessing = false;
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(new InputSource(fis));
|
||||
Element root = doc.getDocumentElement();
|
||||
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
if (root.getLocalName().equals(zf1Signature)) {
|
||||
EStandard thestandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
|
||||
if (thestandard == EStandard.zugferd) {
|
||||
applyZF1XSLT(fis, baos);
|
||||
} else if (root.getLocalName().equals(zf2Signature)) {
|
||||
} else if (thestandard == EStandard.facturx) {
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (root.getLocalName().equals(ublSignature)) {
|
||||
} else if (thestandard == EStandard.ubl) {
|
||||
//zf2 or fx
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
|
||||
} else if (thestandard == EStandard.ubl_creditnote) {
|
||||
//zf2 or fx
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (thestandard == EStandard.orderx) {
|
||||
//zf2 or fx
|
||||
applyCIO2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else {
|
||||
throw new IllegalArgumentException("File does not look like CII or UBL");
|
||||
}
|
||||
@@ -212,11 +245,11 @@ public class ZUGFeRDVisualizer {
|
||||
protected String toFOP(String xmlFilename)
|
||||
throws FileNotFoundException, TransformerException {
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename);//rewind :-(
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
@@ -225,12 +258,18 @@ public class ZUGFeRDVisualizer {
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
if (theStandard == EStandard.facturx) {
|
||||
applyZF2XSLT(fis, iaos);
|
||||
} else if (theStandard == EStandard.ubl) {
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
} else if (theStandard == EStandard.ubl_creditnote) {
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
}
|
||||
|
||||
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
@@ -265,7 +304,7 @@ public class ZUGFeRDVisualizer {
|
||||
public void toPDF(String xmlFilename, String pdfFilename) {
|
||||
|
||||
// the writing part
|
||||
File CIIinputFile = new File(xmlFilename);
|
||||
File XMLinputFile = new File(xmlFilename);
|
||||
|
||||
String result = null;
|
||||
|
||||
@@ -273,24 +312,10 @@ public class ZUGFeRDVisualizer {
|
||||
out from git with arbitrary options (which may include CSRF changes)
|
||||
*/
|
||||
try {
|
||||
result = this.toFOP(CIIinputFile.getAbsolutePath());
|
||||
result = this.toFOP(XMLinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException | TransformerException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
/*
|
||||
FopConfParser parser = null;
|
||||
try {
|
||||
//parsing configuration
|
||||
parser = new FopConfParser(CLASS_LOADER.getResourceAsStream("fop-config.xconf"), new URI("file:///"));
|
||||
|
||||
} catch (SAXException e) {
|
||||
throw new UncheckedIOException(new IOException(e));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} catch (URISyntaxException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
}*/
|
||||
// FopFactoryBuilder builder = parser.getFopFactoryBuilder();
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
|
||||
Configuration cfg = null;
|
||||
@@ -337,8 +362,6 @@ public class ZUGFeRDVisualizer {
|
||||
// Step 6: Start XSLT transformation and FOP processing
|
||||
transformer.transform(src, res);
|
||||
|
||||
//Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
} catch (FOPException | IOException | TransformerException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
@@ -346,11 +369,27 @@ public class ZUGFeRDVisualizer {
|
||||
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
|
||||
}
|
||||
Transformer transformer = mXsltXRTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
}
|
||||
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltCIOTemplate == null) {
|
||||
mXsltCIOTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cio-xr.xsl")));
|
||||
}
|
||||
Transformer transformer = mXsltCIOTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
}
|
||||
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
|
||||
@@ -30,5 +30,5 @@ public class TaxCategoryCodeTypeConstants {
|
||||
public static final String UNTAXEDSERVICE = "O";
|
||||
public static final String INTRACOMMUNITY = "K";
|
||||
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE).collect(Collectors.toSet());
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.mustangproject.util;
|
||||
|
||||
public final class ByteArraySearcher {
|
||||
|
||||
private ByteArraySearcher() {
|
||||
}
|
||||
|
||||
public static int indexOf(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= haystack.length - needle.length; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[i + j] != needle[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean contains(byte[] haystack, byte[] needle) {
|
||||
return indexOf(haystack, needle) >= 0;
|
||||
}
|
||||
|
||||
public static boolean startsWith(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[j] != needle[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
144
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
144
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.util;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The {@link NodeMap} contains a {@link Map} representation of DOM object
|
||||
* It can be constructed either from the children of a single {@link Node} or a {@link NodeList}.
|
||||
*/
|
||||
public class NodeMap {
|
||||
private final Map<String, List<Node>> map = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link NodeMap}
|
||||
* The {@link Node} that is passed to the constructor must not be null.
|
||||
* The {@link NodeMap} will be empty when no child nodes are present.
|
||||
*
|
||||
* @param node the node that shall be represented by this {@link NodeMap}
|
||||
* @throws IllegalArgumentException when argument is null
|
||||
*/
|
||||
public NodeMap(Node node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node cannot be null");
|
||||
}
|
||||
if (node.hasChildNodes()) {
|
||||
mapNodeList(node.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
public NodeMap(NodeList nodeList) {
|
||||
if (nodeList == null) {
|
||||
throw new IllegalArgumentException("nodeList cannot be null");
|
||||
}
|
||||
mapNodeList(nodeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the matching node
|
||||
*/
|
||||
public Optional<Node> getNode(String... localNames) {
|
||||
return getAllNodes(localNames).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link NodeMap} of the child of a matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the {@link NodeMap} representation of the matching node
|
||||
*/
|
||||
public Optional<NodeMap> getAsNodeMap(String... localNames) {
|
||||
return getNode(localNames).filter(Node::hasChildNodes).map(NodeMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node
|
||||
*/
|
||||
public Optional<String> getAsString(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node, converted to BigDecimal
|
||||
*/
|
||||
public Optional<BigDecimal> getAsBigDecimal(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent).map(s->new BigDecimal(s.trim()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node or {@code null} when no matching node could be found
|
||||
*/
|
||||
public String getAsStringOrNull(String... localNames) {
|
||||
return getAsString(localNames).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all matching nodes
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return a {@code Stream} that contains all matching nodes (can be empty if nothing matches)
|
||||
*/
|
||||
public Stream<Node> getAllNodes(String... localNames) {
|
||||
List<String> localNamesList = Arrays.asList(localNames);
|
||||
return map.entrySet().stream().filter(e -> localNamesList.contains(e.getKey())).flatMap(e -> e.getValue().stream());
|
||||
}
|
||||
|
||||
private void mapNodeList(NodeList nodeList) {
|
||||
IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item)
|
||||
.filter(node -> node != null && node.getLocalName() != null)
|
||||
.forEach(node -> map.computeIfAbsent(node.getLocalName(), k -> new ArrayList<>()).add(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
}
|
||||
2225
library/src/main/resources/stylesheets/cio-xr.xsl
Normal file
2225
library/src/main/resources/stylesheets/cio-xr.xsl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@
|
||||
<entry key="xr:Third_party_payment_type" id="BT-DEX-001">Art der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_amount" id="BT-DEX-002">Betrag der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_description" id="BT-DEX-003">Beschreibung der Fremdforderung</entry>
|
||||
<entry key="uebersicht">Übersicht</entry>
|
||||
<entry key="uebersicht">Daten der E-Rechnung</entry>
|
||||
<entry key="uebersichtKaeufer" id="BG-7">Informationen zum Käufer</entry>
|
||||
<entry key="uebersichtVerkaeufer" id="BG-4">Informationen zum Verkäufer</entry>
|
||||
<entry key="uebersichtRechnungsInfo" id="invoice-data">Rechnungsdaten</entry>
|
||||
@@ -214,7 +214,7 @@
|
||||
<entry key="uebersichtZahlungLastschrift" id="BG-19">Lastschrift</entry>
|
||||
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Überweisung</entry>
|
||||
<entry key="uebersichtBemerkungen" id="BG-1">Bemerkungen zur Rechnung</entry>
|
||||
<entry key="details">Details</entry>
|
||||
<entry key="details">Rechnungspositionen</entry>
|
||||
<entry key="detailsPositionAbrechnungszeitraum" id="BG-26">Abrechnungszeitraum</entry>
|
||||
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Preiseinzelheiten</entry>
|
||||
<entry key="detailsPositionNachlaesse" id="BG-27">Nachlässe auf Ebene der Rechnungsposition</entry>
|
||||
|
||||
@@ -807,10 +807,11 @@
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="zusaetzeVertrag">
|
||||
<xsl:call-template name="box">
|
||||
<xsl:call-template name="spanned-box">
|
||||
<xsl:with-param name="identifier" select="'zusaetzeVertrag'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:call-template name="list">
|
||||
<xsl:with-param name="layout" select="'einspaltig'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Tender_or_lot_reference"/>
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Receiving_advice_reference"/>
|
||||
|
||||
Reference in New Issue
Block a user