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:
jstaerk
2024-10-09 14:01:18 +02:00
238 changed files with 98710 additions and 1044808 deletions

View File

@@ -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();
}

View File

@@ -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");
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 &amp; 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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"));
}
}

View File

@@ -31,4 +31,9 @@ public class SchemedID {
setId(id);
}
@Override
public String toString() {
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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; }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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; }
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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>";

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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());
}

View File

@@ -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;
}
}

View 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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>

View File

@@ -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"/>