Merge branch 'master' into buyerorderid

This commit is contained in:
rexfox147
2025-06-11 14:24:20 +02:00
313 changed files with 107946 additions and 1080582 deletions

View File

@@ -1,10 +1,18 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
import java.math.BigDecimal;
import java.math.RoundingMode;
/***
* (absolute) allowances on item and document level
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Allowance extends Charge {
/***
@@ -23,11 +31,28 @@ public class Allowance extends Charge {
}
@Override
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) {
if(totalAmount != null) {
return totalAmount;
} else if (percent!=null) {
BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice);
return singlePriceDiff;
} else {
throw new RuntimeException("percent must be set");
}
}
/***
* Always to return false for IZUGFeRDAllowanceCharge
* @return false since its not supposed to be calculated negatively
*/
@Override
@JsonIgnore
public boolean isCharge() {
return false;
}

View File

@@ -2,11 +2,14 @@ package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
/**
* provides e.g. the IBAN to transfer money to :-)
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class BankDetails implements IZUGFeRDTradeSettlementPayment {
/**
* the bank account number
@@ -15,15 +18,17 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
/**
* BIC, I believe it's optional
*/
protected String BIC=null;
protected String BIC = null;
/**
* the "name" of the bank account (holder)
*/
protected String accountName=null;
protected String accountName = null;
/***
* bean constructor
*/
public BankDetails() { }
public BankDetails() {
}
/***
* constructor for IBAN only :-)
@@ -32,6 +37,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
public BankDetails(String IBAN) {
this.IBAN = IBAN;
}
/***
* constructor for normal use :-)
* @param IBAN the IBAN as string
@@ -55,6 +61,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
* identify the IBAN. Of course you will specify your own IBAN in full length but
* if you deduct from a customer's account you may e.g. leave out the first or last
* digits so that nobody spying on the invoice gets to know the complete number
*
* @param IBAN the "IBAN ID", i.e. the IBAN or parts of it
* @return fluent setter
*/
@@ -81,9 +88,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
return this;
}
/***
* getOwn... methods will be removed in the future in favor of Tradeparty (e.g. Sender) class
* */
/*
I'd really like to get rid of all those getOwn... methods some time but in this case they are in the interface :-(
*/
@Override
@Deprecated
@JsonIgnore
@@ -101,11 +109,12 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
/**
* set Holder
*
* @param name account name (usually account holder if != sender)
* @return fluent setter
*/
public BankDetails setAccountName(String name) {
accountName=name;
accountName = name;
return this;
}
@@ -115,5 +124,4 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
}
}

View File

@@ -0,0 +1,109 @@
// Copyright (c) 2023 Jochen Stärk, see LICENSE file
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.TransactionCalculator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.math.BigDecimal;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class CalculatedInvoice extends Invoice implements Serializable {
protected BigDecimal lineTotalAmount=null;
protected BigDecimal duePayable=null;
protected BigDecimal grandTotal=null;
protected BigDecimal taxBasis=null;
protected TransactionCalculator tc=null;
public void calculate() {
tc=new TransactionCalculator(this);
grandTotal=tc.getGrandTotal();
lineTotalAmount=tc.getValue();
duePayable=tc.getDuePayable();
taxBasis= tc.getTaxBasis();
}
public BigDecimal getGrandTotal() {
if (grandTotal==null) {
calculate();
}
return grandTotal;
}
/***
* usually one would use calculate, use only if the invoice is parsed
* @param grand the gross total
* @return fluent setter
*/
public CalculatedInvoice setGrandTotal(BigDecimal grand) {
grandTotal=grand;
return this;
}
public BigDecimal getTaxBasis() {
if (taxBasis==null) {
calculate();
}
return taxBasis;
}
/***
* usually one would use calculate, use only if the invoice is parsed
* @param basis taxable net total
* @return fluent setter
*/
public CalculatedInvoice setTaxBasis(BigDecimal basis) {
taxBasis=basis;
return this;
}
public BigDecimal getDuePayable() {
if (duePayable==null) {
calculate();
}
return duePayable;
}
/***
* usually one would use calculate, use only if the invoice is parsed
* @param due the gross total minus prepaid
* @return fluent setter
*/
public CalculatedInvoice setDuePayable(BigDecimal due) {
duePayable=due;
return this;
}
public BigDecimal getLineTotalAmount() {
if (lineTotalAmount==null) {
calculate();
}
return lineTotalAmount;
}
/***
* usually one would use calculate, use only if the invoice is parsed
* @param total the net total
* @return fluent setter
*/
public CalculatedInvoice setLineTotalAmount(BigDecimal total) {
lineTotalAmount=total;
return this;
}
/**
* this can be used to additionally e.g. access the VAT amounts
* @return a updated transactioncalulator
*/
public TransactionCalculator getCalculation() {
return tc;
}
}

View File

@@ -1,5 +1,8 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDCashDiscount;
import java.math.BigDecimal;
@@ -7,6 +10,8 @@ import java.math.BigDecimal;
/***
* A class to represent discounts for early payments ("Skonto")
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class CashDiscount implements IZUGFeRDCashDiscount {
/***
@@ -31,9 +36,35 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
this.days = days;
}
/***
* bean contructor
*/
public CashDiscount() {
}
public BigDecimal getPercent() {
return percent;
}
public CashDiscount setPercent(BigDecimal percent) {
this.percent = percent;
return this;
}
public Integer getDays() {
return days;
}
public CashDiscount setDays(Integer days) {
this.days = days;
return this;
}
/***
* @return this particular cash discount as cross industry invoice XML
*/
@JsonIgnore
public String getAsCII() {
return "<ram:SpecifiedTradePaymentTerms>"+
"<ram:Description>Cash Discount</ram:Description>"+
@@ -49,6 +80,7 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
* XRechnung CIUS defined it's own proprietary format for a freetext field
* @return this particular cash discount in proprietary xrechnung format
*/
@JsonIgnore
public String getAsXRechnung() {
return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n";
}

View File

@@ -1,13 +1,19 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import java.math.BigDecimal;
import java.math.RoundingMode;
/***
* Absolute and relative charges for document and item level
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Charge implements IZUGFeRDAllowanceCharge {
/**
@@ -18,6 +24,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
* the absolute value if not percentage
*/
protected BigDecimal totalAmount;
/**
* the value the percentage is applied upon
*/
protected BigDecimal basisAmount;
/**
* the tax rate the charge belongs to
*/
@@ -98,6 +108,22 @@ public class Charge implements IZUGFeRDAllowanceCharge {
}
@Override
public BigDecimal getBasisAmount() {
return basisAmount;
}
/***
* sets a potential basis for the potential percentage
* @param basis the basis amount
* @return fluid setter
*/
public Charge setBasisAmount(BigDecimal basis) {
this.basisAmount = basis;
return this;
}
@Override
public String getReasonCode() {
return reasonCode;
@@ -116,10 +142,13 @@ 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) {
if(totalAmount != null) {
return totalAmount;
} else if (percent!=null) {
BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice);
return singlePriceDiff;
} else {
throw new RuntimeException("percent must be set");
}
@@ -129,7 +158,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
if (totalAmount!=null) {
return totalAmount;
} else {
throw new RuntimeException("totalAmount must be set");
if (percent==null) {
throw new RuntimeException("totalAmount must be set");
}
return null;
}
}
@@ -151,6 +183,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
* @return true since it is supposed to be calculated negatively
*/
@Override
@JsonIgnore
public boolean isCharge() {
return true;
}

View File

@@ -0,0 +1,115 @@
/**
* *********************************************************************
* <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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* A schemed classification for products. The scheme can be anything defined in UNTDID 7143.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ClassCode {
private String listID;
private 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);
}
/***
* bean constructor
*/
public ClassCode() {
}
/***
* 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

@@ -1,6 +1,7 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -11,6 +12,7 @@ import org.w3c.dom.NodeList;
* @see TradeParty
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Contact implements IZUGFeRDExportableContact {
/**
@@ -112,7 +114,9 @@ public class Contact implements IZUGFeRDExportableContact {
if (currentItemNode.getLocalName() != null) {
if (currentItemNode.getLocalName().equals("PersonName")/*CII*/||currentItemNode.getLocalName().equals("Name")/*UBL*/) {
setName(currentItemNode.getFirstChild().getNodeValue());
if (currentItemNode.getFirstChild()!=null) {
setName(currentItemNode.getFirstChild().getNodeValue());
}
}
if (currentItemNode.getLocalName().equals("TelephoneUniversalCommunication")) { /*CII*/
NodeList tel = currentItemNode.getChildNodes();

View File

@@ -0,0 +1,82 @@
/**
* *********************************************************************
* <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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
/**
* An implementation of {@link IDesignatedProductClassification} for describing a {@link org.mustangproject.Product}
*
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class DesignatedProductClassification implements IDesignatedProductClassification {
private 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);
}
/**
* Bean constructor for schemed product descriptor
*/
public DesignatedProductClassification() {
classCode = null;// we need this constructor for jackson, i.e. to be able to JSON
}
@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,20 +1,42 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit;
/**
* provides e.g. the IBAN to transfer money to :-)
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
/**
* Debited account identifier (BT-91)
*/
protected final String IBAN;
protected String IBAN;
/**
* Mandate reference identifier (BT-89)
*/
protected final String mandate;
protected String mandate;
/**
* payment means code (BT-81 / UNTDID 4461)
*/
protected String paymentMeansCode = "59";
/**
* Payment means description (BT-82)
*/
protected String paymentMeansInformation = "SEPA direct debit";
/**
* bean constructor
*/
public DirectDebit() {
this.IBAN = "";
this.mandate = "";
}
/***
* constructor for normal use :-)
@@ -26,6 +48,16 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
this.mandate = mandate;
}
public DirectDebit setPaymentMeansCode(String paymentMeansCode) {
this.paymentMeansCode = paymentMeansCode;
return this;
}
public DirectDebit setPaymentMeansInformation(String paymentMeansInformation) {
this.paymentMeansInformation = paymentMeansInformation;
return this;
}
/***
* getter for the IBAN
* @return IBAN
@@ -35,8 +67,28 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
return this.IBAN;
}
public DirectDebit setIBAN(String iBAN) {
this.IBAN = iBAN;
return this;
}
@Override
public String getMandate() {
return this.mandate;
}
@Override
public String getPaymentMeansCode() {
return this.paymentMeansCode;
}
@Override
public String getPaymentMeansInformation() {
return this.paymentMeansInformation;
}
public DirectDebit setMandate(String mandate) {
this.mandate = mandate;
return this;
}
}

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

@@ -0,0 +1,16 @@
package org.mustangproject.Exceptions;
import java.text.ParseException;
/***
* will be thrown if an invoice cant be reproduced numerically
*/
public class ArithmetricException extends ParseException {
public ArithmetricException() {
this("");
}
public ArithmetricException(String details) {
super("Could not reproduce the invoice. " + details, 0);
}
}

View File

@@ -0,0 +1,12 @@
package org.mustangproject.Exceptions;
import java.text.ParseException;
/***
* will be thrown if a invoice cant be read
*/
public class StructureException extends ParseException {
public StructureException(String message, int line) {
super(message, line);
}
}

View File

@@ -1,19 +1,39 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class FileAttachment {
protected String filename;
protected String mimetype;
protected String relation;
protected String relation = "Unspecified";
protected String description;
protected byte[] data;
/***
* bean contructor
*/
public FileAttachment() {
}
public FileAttachment(String filename, String mimetype, String relation, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;
this.relation = relation;
this.data = data;
description = "Additional file attachment";
this.description = "Additional file attachment";
}
public FileAttachment(String filename, String mimetype, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;
this.data = data;
this.description = "Additional file attachment";
}
public String getDescription() {
@@ -47,6 +67,28 @@ public class FileAttachment {
return relation;
}
/***
* only needed when embedded in PDF described
*
* values
* - Source shall be used if this file specification is the original
* source material for the associated content.
* - Data shall be used if this file specification represents information
* used to derive a visual presentation, such as for a table or a
* graph.
* - Alternative shall be used if this file specification is an alternative
* representation of content, for example audio.
* - Supplement shall be used if this file specification represents a
* supplemental representation of the original source or data that
* may be more easily consumable (e.g. A MathML version of an
* equation).
* - Unspecified shall be used when the relationship is not known
* or cannot be described using one of the other values.
* @param relation String: either : Source, Data or Alternative. Usually Data, except source if the file attachment
* is the basis for the pdf (xrechnung2fx) or Alternative if it contains the same content (e.g. the
* factur-x.xml file in a factur-x PDF)
* @return fluent setter
*/
public FileAttachment setRelation(String relation) {
this.relation = relation;
return this;
@@ -60,8 +102,4 @@ public class FileAttachment {
this.data = data;
return this;
}
}

View File

@@ -1,68 +1,100 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class IncludedNote {
private final String content;
private final SubjectCode subjectCode;
private String content;
private static final String INCLUDE_START = "<ram:IncludedNote>";
private static final String INCLUDE_END = "</ram:IncludedNote>";
private static final String CONTENT_START = "<ram:Content>";
private static final String CONTENT_END = "</ram:Content>";
private static final String SUBJECT_CODE_START = "<ram:SubjectCode>";
private static final String SUBJECT_CODE_END = "</ram:SubjectCode>";
private SubjectCode subjectCode;
private IncludedNote(String content, SubjectCode subjectCode) {
this.content = content;
this.subjectCode = subjectCode;
}
private static final String INCLUDE_START = "<ram:IncludedNote>";
private static final String INCLUDE_END = "</ram:IncludedNote>";
private static final String CONTENT_START = "<ram:Content>";
private static final String CONTENT_END = "</ram:Content>";
private static final String SUBJECT_CODE_START = "<ram:SubjectCode>";
private static final String SUBJECT_CODE_END = "</ram:SubjectCode>";
public static IncludedNote generalNote(String content) {
return new IncludedNote(content, SubjectCode.AAI);
}
public static IncludedNote regulatoryNote(String content) {
return new IncludedNote(content, SubjectCode.REG);
}
public static IncludedNote legalNote(String content) {
return new IncludedNote(content, SubjectCode.ABL);
}
public static IncludedNote customsNote(String content) {
return new IncludedNote(content, SubjectCode.CUS);
}
public static IncludedNote sellerNote(String content) {
return new IncludedNote(content, SubjectCode.SUR);
}
public static IncludedNote taxNote(String content) {
return new IncludedNote(content, SubjectCode.TXD);
}
public static IncludedNote introductionNote(String content) {
return new IncludedNote(content, SubjectCode.ACY);
}
public static IncludedNote discountBonusNote(String content) {
return new IncludedNote(content, SubjectCode.AAK);
}
public IncludedNote(String content, SubjectCode subjectCode) {
this.content = content;
this.subjectCode = subjectCode;
}
public static IncludedNote unspecifiedNote(String content) {
return new IncludedNote(content, null);
}
/**
* bean constructor
*/
public IncludedNote() {
public String getContent() {
return content;
}
}
public static IncludedNote generalNote(String content) {
return new IncludedNote(content, SubjectCode.AAI);
}
public static IncludedNote regulatoryNote(String content) {
return new IncludedNote(content, SubjectCode.REG);
}
public static IncludedNote legalNote(String content) {
return new IncludedNote(content, SubjectCode.ABL);
}
public static IncludedNote customsNote(String content) {
return new IncludedNote(content, SubjectCode.CUS);
}
public static IncludedNote sellerNote(String content) {
return new IncludedNote(content, SubjectCode.SUR);
}
public static IncludedNote taxNote(String content) {
return new IncludedNote(content, SubjectCode.TXD);
}
public static IncludedNote introductionNote(String content) {
return new IncludedNote(content, SubjectCode.ACY);
}
public static IncludedNote discountBonusNote(String content) {
return new IncludedNote(content, SubjectCode.AAK);
}
public static IncludedNote unspecifiedNote(String content) {
return new IncludedNote(content, null);
}
public String getContent() {
return content;
}
public SubjectCode getSubjectCode() {
return subjectCode;
}
public IncludedNote setSubjectCode(SubjectCode subjectCode) {
this.subjectCode = subjectCode;
return this;
}
public IncludedNote setContent(String content) {
this.content = content;
return this;
}
public String toCiiXml() {
String result = INCLUDE_START + CONTENT_START +
XMLTools.encodeXML(getContent()) + CONTENT_END;
if (getSubjectCode() != null) {
result += SUBJECT_CODE_START + getSubjectCode() + SUBJECT_CODE_END;
}
return result + INCLUDE_END;
}
public SubjectCode getSubjectCode() {
return subjectCode;
}
public String toCiiXml(){
String result = INCLUDE_START + CONTENT_START +
XMLTools.encodeXML(getContent() )+ CONTENT_END;
if (getSubjectCode() != null) {
result += SUBJECT_CODE_START + getSubjectCode() + SUBJECT_CODE_END;
}
return result + INCLUDE_END;
}
}

View File

@@ -21,11 +21,9 @@
package org.mustangproject;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.*;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
@@ -37,11 +35,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see IExportableTransaction if you want to implement an interface instead
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
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 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, 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;
@@ -57,13 +56,20 @@ public class Invoice implements IExportableTransaction {
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
protected IZUGFeRDPaymentTerms paymentTerms = null;
protected ArrayList<IZUGFeRDPaymentTerms> paymentTerms = new ArrayList<>();
protected String invoiceReferencedDocumentID = null;
protected Date invoiceReferencedIssueDate;
// New field for storing Invoiced Object Identifier (BG-3)
protected ArrayList<ReferencedDocument> invoiceReferencedDocuments = null;
protected String specifiedProcuringProjectID = null;
protected String specifiedProcuringProjectName = null;
protected String despatchAdviceReferencedDocumentID = null;
protected String vatDueDateTypeCode = null;
protected String creditorReferenceID; // required when direct debit is used.
private BigDecimal roundingAmount=null;
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
public Invoice() {
ZFItems = new ArrayList<>();
@@ -113,6 +119,20 @@ public class Invoice implements IExportableTransaction {
}
/***
* setter in case e.g. jackson tries to map attachments (normal use embedFileInXML)
* @param fileArr Array of FileAttachments
* @return fluent setter
*/
public Invoice setAdditionalReferencedDocuments(FileAttachment[] fileArr) {
if (fileArr!=null) {
xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
} else {
xmlEmbeddedFiles = new ArrayList<>();
}
return this;
}
@Override
public IZUGFeRDCashDiscount[] getCashDiscounts() {
return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
@@ -410,6 +430,11 @@ public class Invoice implements IExportableTransaction {
return includedNotes;
}
public Invoice setNotesWithSubjectCode(List<IncludedNote> theList) {
includedNotes=theList;
return this;
}
@Override
public String getCurrency() {
return currency;
@@ -465,6 +490,26 @@ public class Invoice implements IExportableTransaction {
return sender;
}
/***
* for currency rounding differences to 5ct e.g. in Netherlands ("Rappenrundung")
* @return null if not set, otherwise BigDecimal of Euros
*/
@Override
public BigDecimal getRoundingAmount() {
return roundingAmount;
}
/***
* set the cent e.g. to reach the next 5ct mark for currencies in certain countries
* e.g. in the Netherlands ("Rappenrundung")
* @param amount
* @return fluent setter
*/
public Invoice setRoundingAmount(BigDecimal amount) {
roundingAmount=amount;
return this;
}
/***
* sets a named sender contact
* @deprecated use setSender
@@ -511,6 +556,26 @@ public class Invoice implements IExportableTransaction {
return this;
}
// Getter for BG-3
@Override
public ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
return invoiceReferencedDocuments;
}
// Setter for BG-3
public void setInvoiceReferencedDocuments(ArrayList<ReferencedDocument> invoiceReferencedDocuments) {
this.invoiceReferencedDocuments = invoiceReferencedDocuments;
}
// Method to add a single ReferencedDocument for BG-3
public Invoice addInvoiceReferencedDocument(ReferencedDocument doc) {
if (invoiceReferencedDocuments == null) {
invoiceReferencedDocuments = new ArrayList<>();
}
invoiceReferencedDocuments.add(doc);
return this;
}
@Override
public IZUGFeRDAllowanceCharge[] getZFAllowances() {
if (Allowances.isEmpty()) {
@@ -520,6 +585,20 @@ public class Invoice implements IExportableTransaction {
}
}
/***
* this is wrong and only used from jackson
* @param iza the Array of allowances/charges
* @return fluent setter
*/
public Invoice setZFAllowances(Allowance[] iza) {
Allowances=new ArrayList<>();
for (IZUGFeRDAllowanceCharge cz:iza) {
Allowances.add(cz);
}
return this;
}
@Override
public IZUGFeRDAllowanceCharge[] getZFCharges() {
@@ -530,6 +609,18 @@ public class Invoice implements IExportableTransaction {
}
}
/***
* this is wrong and only used from jackson
* @param iza the array of charges
* @return fluent setter
*/
public Invoice setZFCharges(Charge[] iza) {
Charges=new ArrayList<>();
for (IZUGFeRDAllowanceCharge cz:iza) {
Charges.add(cz);
}
return this;
}
@Override
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
@@ -555,11 +646,49 @@ public class Invoice implements IExportableTransaction {
@Override
public IZUGFeRDPaymentTerms getPaymentTerms() {
return paymentTerms;
if (!paymentTerms.isEmpty()) {
return paymentTerms.get(0);
}
return null;
}
public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerms) {
this.paymentTerms = paymentTerms;
public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
if (paymentTerms.isEmpty()) {
paymentTerms.add(paymentTerm);
}
else {
paymentTerms.set(0, paymentTerm);
}
return this;
}
@Override
public IZUGFeRDPaymentTerms[] getExtendedPaymentTerms() {
return paymentTerms.toArray(new IZUGFeRDPaymentTerms[0]);
}
public Invoice setExtendedPaymentTerms(IZUGFeRDPaymentTerms[] paymentTerms) {
this.paymentTerms.clear();
this.paymentTerms.addAll(Arrays.asList(paymentTerms));
return this;
}
/**
* Set multiple payment terms when using the EXTENDED profile.
* @return
*/
public Invoice addPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
paymentTerms.add(paymentTerm);
return this;
}
public String getPaymentReference() {
return paymentReference;
}
public Invoice setPaymentReference(String paymentReference) {
this.paymentReference = paymentReference;
return this;
}
@@ -577,6 +706,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
@@ -680,11 +825,20 @@ public class Invoice implements IExportableTransaction {
return detailedDeliveryDateStart;
}
public Invoice setDetailedDeliveryPeriodFrom(Date dt) {
detailedDeliveryDateStart=dt;
return this;
}
@Override
public Date getDetailedDeliveryPeriodTo() {
return detailedDeliveryPeriodEnd;
}
public Invoice setDetailedDeliveryPeriodTo(Date dt) {
detailedDeliveryPeriodEnd=dt;
return this;
}
/**
* adds a free text paragraph, which will become an includedNote element

View File

@@ -1,26 +1,38 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
/***
* describes any invoice line
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
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 buyerOrderReferencedDocumentLineID = null;
protected String buyerOrderReferencedDocumentID = null;
@@ -28,8 +40,11 @@ public class Item implements IZUGFeRDExportableItem {
protected ArrayList<String> notes = null;
protected ArrayList<ReferencedDocument> referencedDocuments = null;
protected ArrayList<ReferencedDocument> additionalReference = null;
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
Charges = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
protected List<IncludedNote> includedNotes = null;
protected String accountingReference;
//protected HashMap<String, String> attributes = new HashMap<>();
/***
* default constructor
@@ -43,7 +58,6 @@ public class Item implements IZUGFeRDExportableItem {
this.product = product;
}
/***
* empty constructor
* do not use, but might be used e.g. by jackson
@@ -52,100 +66,67 @@ 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 buyerOrderReferencedDocumentLineID = null;
String buyerOrderReferencedDocumentID = null;
NodeMap itemMap = new NodeMap(itemChilds);
ArrayList<ReferencedDocument> rdocs = null;
ArrayList<ReferencedDocument> addRefs = null;
itemMap.getAsNodeMap("Item").ifPresent(icnm -> {
// ubl
//we need: name description unitcode
//and we additionally have vat%
// nodes.item(i).getTextContent())) {
setProduct(new Product(itemMap.getNode("Item").get()));
icnm.getAsString("Name").ifPresent(product::setName);
icnm.getAsString("Description").ifPresent(product::setDescription);
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)) {
icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> {
SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID);
});
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();
}
}
}
}
}
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)) {
icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> {
BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID);
});
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("PriceAmount"))) {
price = currentUBLPriceChildNode.getTextContent();
}
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("BaseQuantity"))) {
basisQuantity = currentUBLPriceChildNode.getTextContent();
}
}
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
.ifPresent(product::setVATPercent);
});
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> {
icnm.getAsString("LineID").ifPresent(this::setId);
});
}
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++) {
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);
});
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
.item(tradeLineChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
String IssuerAssignedID = "";
String TypeCode = "";
String ReferenceTypeCode = "";
itemMap.getNode("InvoicedQuantity").ifPresent(icn -> {
// ubl
setQuantity(new BigDecimal(icn.getTextContent().trim()));
product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
});
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();
}
}
itemMap.getAllNodes("DocumentReference").map(ReferencedDocument::fromNode)
.forEach(this::addAdditionalReference);
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
ReferenceTypeCode);
if (rdocs == null) {
rdocs = new ArrayList<>();
}
rdocs.add(rd);
// ubl
}
itemMap.getAsNodeMap("OrderLineReference")
// ubl
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
.ifPresent(this::addReferencedLineID);
itemMap.getAsString("ID")
.ifPresent(this::setId);
itemMap.getAsString("Note")
.ifPresent(this::addNote);
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
.ifPresent(this::addReferencedLineID);
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds.item(tradeLineChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
NodeList docChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
@@ -159,166 +140,155 @@ public class Item implements IZUGFeRDExportableItem {
}
}
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
icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
});
}
if ((netChilds.item(netIndex).getLocalName() != null)
&& ((netChilds.item(netIndex).getLocalName().equals("BasisQuantity")) || (netChilds.item(netIndex).getLocalName().equals("InvoicedQuantity")))) {
basisQuantity = netChilds.item(netIndex).getTextContent();// ChargeAmount
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).
forEach(this::addReferencedDocument);
});
}
}
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//CII
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//UBL
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery")
.flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity"))
.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);
icnm.getAsNodeMap("ApplicableTradeTax")
.flatMap(cnm -> cnm.getAsString("ExemptionReason"))
.ifPresent(product::setTaxExemptionReason);
icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> {
stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> {
String isChargeString = ci.getAsString("Indicator").get();
String percentString = stac.getAsStringOrNull("CalculationPercent");
String amountString = stac.getAsStringOrNull("ActualAmount");
String basisAmountString = stac.getAsStringOrNull("BasisAmount");
String reason = stac.getAsStringOrNull("Reason");
Charge izac = new Charge();
if (isChargeString.equalsIgnoreCase("false")) {
izac = new Allowance();
} else {
izac = new Charge();
}
if (amountString != null) {
izac.setTotalAmount(new BigDecimal(amountString));
if (percentString!=null&&(percentString!="0")) {
izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity()));
}
}
if (basisAmountString != null) {
izac.setBasisAmount(new BigDecimal(basisAmountString));
}
if (percentString != null) {
izac.setPercent(new BigDecimal(percentString));
}
if (reason != null) {
izac.setReason(reason);
}
if (isChargeString.equalsIgnoreCase("false")) {
addAllowance(izac);
} else {
addCharge(izac);
}
});
});
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("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();
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim());
icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> {
Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
setDetailedDeliveryPeriod(start, end);
});
});
itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //UBL
String isChargeString = stac.getAsString("ChargeIndicator").get();
String percentString = stac.getAsStringOrNull("MultiplierFactorNumeric");
String amountString = stac.getAsStringOrNull("Amount");
String reason = stac.getAsStringOrNull("AllowanceChargeReason");
Charge izac = new Charge();
if (isChargeString.equalsIgnoreCase("false")) {
izac = new Allowance();
} else {
izac = new Charge();
}
if (amountString != null) {
izac.setTotalAmount(new BigDecimal(amountString));
}
if (percentString != null) {
izac.setPercent(new BigDecimal(percentString));
}
if (reason != null) {
izac.setReason(reason);
}
if (isChargeString.equalsIgnoreCase("false")) {
addAllowance(izac);
} else {
addCharge(izac);
}
});
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> {
List<IncludedNote> includedNotes = new ArrayList<>();
adld.getAllNodes("IncludedNote").forEach(item -> {
String subjectCode = "";
String content = null;
NodeList includedNodeChilds = item.getChildNodes();
for (int issueDateChildIndex = 0; issueDateChildIndex < includedNodeChilds.getLength(); issueDateChildIndex++) {
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("Content"))) {
content = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
}
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("SubjectCode"))) {
subjectCode = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
}
}
}
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());
}
boolean foundCode = false;
for (SubjectCode code : SubjectCode.values()) {
if (code.toString().equals(subjectCode)) {
includedNotes.add(new IncludedNote(content, code));
foundCode = true;
break;
}
}
}
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")) {
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();
}
}
}
if (tradeSettlementName.equals("AdditionalReferencedDocument")) {
String IssuerAssignedID = "";
String TypeCode = "";
String ReferenceTypeCode = "";
NodeList refDocChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).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 (addRefs == null) {
addRefs = new ArrayList<>();
}
addRefs.add(rd);
}
}
if (!foundCode) {
includedNotes.add(new IncludedNote(content, null));
}
}
}
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, 4, 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);
}
}
if (addRefs != null) {
for (ReferencedDocument rdoc : addRefs) {
addAdditionalReference(rdoc);
}
}
addBuyerOrderReferencedDocumentLineID( buyerOrderReferencedDocumentLineID );
addBuyerOrderReferencedDocumentID( buyerOrderReferencedDocumentID );
});
addNotes(includedNotes);
});
}
public Item addBuyerOrderReferencedDocumentLineID(String s) {
buyerOrderReferencedDocumentLineID = s;
return this;
@@ -339,6 +309,20 @@ public class Item implements IZUGFeRDExportableItem {
return this;
}
@Override public IZUGFeRDAllowanceCharge[] getAllowances() {
IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()];
return Allowances.toArray(izac);
}
@Override public IZUGFeRDAllowanceCharge[] getCharges() {
IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()];
return Charges.toArray(izac);
}
/***
* BT 132 (issue https://github.com/ZUGFeRD/mustangproject/issues/247)
* @return the line ID of the order (BT132)
*/
@Override
public String getBuyerOrderReferencedDocumentLineID() {
return buyerOrderReferencedDocumentLineID;
@@ -348,6 +332,11 @@ public class Item implements IZUGFeRDExportableItem {
return lineTotalAmount;
}
public Item setNotesWithSubjectCode(List<IncludedNote> theList) {
includedNotes = theList;
return this;
}
/**
* should only be set by calculator classes or maybe when reading from XML
*
@@ -438,6 +427,30 @@ public class Item implements IZUGFeRDExportableItem {
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
}
/***
* jackson convenience method
*/
public void setItemAllowances(ArrayList<Allowance> theAllowances) {
if (theAllowances != null) {
Allowances.clear();
for (Allowance theAllowance : theAllowances) {
Allowances.add(theAllowance);
}
}
}
/***
* jackson convenience method
*/
public void setItemCharges(ArrayList<Charge> theCharges) {
if (theCharges != null) {
Charges.clear();
for (Charge theCharge : theCharges) {
Charges.add(theCharge);
}
}
}
@Override
public IZUGFeRDAllowanceCharge[] getItemCharges() {
if (Charges.isEmpty()) {
@@ -463,7 +476,7 @@ public class Item implements IZUGFeRDExportableItem {
/***
* Adds a item level addition to the price (will be multiplied by quantity)
* @see org.mustangproject.Charge
* @see Charge
* @param izac a relative or absolute charge
* @return fluent setter
*/
@@ -474,11 +487,12 @@ public class Item implements IZUGFeRDExportableItem {
/***
* Adds a item level reduction the price (will be multiplied by quantity)
* @see org.mustangproject.Allowance
* @see Allowance
* @param izac a relative or absolute allowance
* @return fluent setter
*/
public Item addAllowance(IZUGFeRDAllowanceCharge izac) {
Allowances.add(izac);
return this;
}
@@ -493,6 +507,26 @@ public class Item implements IZUGFeRDExportableItem {
notes = new ArrayList<>();
}
notes.add(text);
addNote(IncludedNote.unspecifiedNote(text));
return this;
}
/***
* adds categorized item level freetext fields (includednote)
* @param theNote IncludedNote to add
* @return fluent setter
*/
public Item addNote(IncludedNote theNote) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(theNote);
return this;
}
@@ -519,7 +553,7 @@ public class Item implements IZUGFeRDExportableItem {
/***
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
* @param doc the ReferencedDocument to add
* @return fluent setter
*/
@@ -538,8 +572,8 @@ public class Item implements IZUGFeRDExportableItem {
}
return additionalReference.toArray(new IReferencedDocument[0]);
}
/***
* specify a item level delivery period
* (apart from the document level delivery period, and the document level
@@ -560,6 +594,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;
}
@@ -569,8 +604,29 @@ 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;
}
public IZUGFeRDExportableItem addNotes(Collection<IncludedNote> notes) {
if (notes == null) {
return this;
}
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.addAll(notes);
return this;
}
@Override
public List<IncludedNote> getNotesWithSubjectCode() {
return includedNotes;
}
@Override
public String getAccountingReference() {
return accountingReference;
}
}

View File

@@ -1,6 +1,7 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.*;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -9,6 +10,7 @@ import org.w3c.dom.NodeList;
* A organisation, i.e. usually a company
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
protected SchemedID schemedID = null;
@@ -18,12 +20,16 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
}
public LegalOrganisation(String ID, String scheme) {
this.schemedID = new SchemedID(ID, scheme);
this.schemedID = new SchemedID(scheme, ID);
}
public LegalOrganisation(String ID) {
this.schemedID = new SchemedID(null, ID);
}
public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) {
this.schemedID = schemedID;
this.tradingBusinessName=tradingBusinessName;
this.tradingBusinessName = tradingBusinessName;
}
/***
@@ -48,8 +54,8 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
this.setSchemedID(gid);
}
}
if (currentItemNode.getLocalName().equals("TradingBusinessName")) {
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
if ((currentItemNode.getLocalName().equals("TradingBusinessName")) && (currentItemNode.getFirstChild() != null)) {
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
}
}
}

View File

@@ -1,24 +1,42 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.math.BigDecimal;
import java.util.ArrayList;
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)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product implements IZUGFeRDExportableProduct {
protected String unit, name, sellerAssignedID, buyerAssignedID;
protected String description="";
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 ArrayList<Charge> charges=new ArrayList<>();
protected ArrayList<Allowance> allowances=new ArrayList<>();
protected HashMap<String, String> attributes = new HashMap<>();
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
/***
* default constructor
@@ -34,6 +52,63 @@ 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.getAllNodes("ApplicableProductCharacteristic").map(NodeMap::new).forEach(apcNodes -> { // ApplicableProductCharacteristic is 0 .. unbounded
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);
}
});
//UBL
nodeMap.getAsNodeMap("AdditionalItemProperty").ifPresent(aipNodes -> {
String name = aipNodes.getAsStringOrNull("Name");
String val = aipNodes.getAsStringOrNull("Value");
if (name != null && val != null) {
if (attributes == null) {
attributes = new HashMap<>();
}
attributes.put(name, val);
}
});
nodeMap.getAsNodeMap("CommodityClassification").ifPresent(dpcNodes -> {
String className = dpcNodes.getAsStringOrNull("ClassName");
dpcNodes.getNode("ItemClassificationCode").map(ClassCode::fromNode).ifPresent(classCode ->
classifications.add(new DesignatedProductClassification(classCode, className)));
});
//UBL
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
@@ -67,6 +142,48 @@ public class Product implements IZUGFeRDExportableProduct {
}
/***
*
* @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() {
return sellerAssignedID;
@@ -125,6 +242,8 @@ public class Product implements IZUGFeRDExportableProduct {
public Product setIntraCommunitySupply() {
isIntraCommunitySupply = true;
setVATPercent(BigDecimal.ZERO);
setTaxExemptionReason("Intra-community supply");
setTaxCategoryCode("K");
return this;
}
@@ -186,35 +305,132 @@ public class Product implements IZUGFeRDExportableProduct {
* @return fluent setter
*/
public Product setVATPercent(BigDecimal VATPercent) {
this.VATPercent = VATPercent;
if (VATPercent == null) {
this.VATPercent = BigDecimal.ZERO;
} else {
this.VATPercent = VATPercent;
}
return this;
}
@Override
public String getCountryOfOrigin() {
return this.countryOfOrigin;
return this.countryOfOrigin;
}
public Product setCountryOfOrigin(String countryOfOrigin) {
this.countryOfOrigin = countryOfOrigin;
return this;
this.countryOfOrigin = countryOfOrigin;
return this;
}
@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;
return this;
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;
public Product addAttribute(String name, String value) {
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;
}
/**
* Provide Jackson a hint as to use DesignatedProductClassification for the
* IDesignatedProductClassification product classifications
*
* @param classifications the new set of classifications
* @return fluent setter
*/
@JsonSetter("classifications")
public Product setClassificationsClass(DesignatedProductClassification[] 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;
}
public Product addCharge(Charge e) {
charges.add(e);
return this;
}
public Product addAllowance(Allowance a) {
allowances.add(a);
return this;
}
/***
* returns the AppliedTradeAllowanceCharges of this product which are actually Charges
* @return array of or null, if none
*/
@Override
public Charge[] getCharges() {
if (charges.size()==0) {
return null;
}
Charge[] chargeArr = new Charge[charges.size()];
return charges.toArray(chargeArr);
}
@Override
/***
* returns the AppliedTradeAllowanceCharges of this product which are actually Allowances
* @return array of or null, if none
*/
public Allowance[] getAllowances() {
if (allowances.size()==0) {
return null;
}
Allowance[] allowanceArr = new Allowance[allowances.size()];
return allowances.toArray(allowanceArr);
}
}

View File

@@ -1,23 +1,44 @@
package org.mustangproject;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Date;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ReferencedDocument implements IReferencedDocument {
String issuerAssignedID;
String typeCode;
String referenceTypeCode;
Date formattedIssueDateTime;
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode) {
this.issuerAssignedID = issuerAssignedID;
this(issuerAssignedID);
this.typeCode = typeCode;
this.referenceTypeCode = referenceTypeCode;
}
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode, Date formattedIssueDateTime) {
this(issuerAssignedID, typeCode, referenceTypeCode);
this.formattedIssueDateTime = formattedIssueDateTime;
}
public ReferencedDocument(String issuerAssignedID, String referenceTypeCode) {
this(issuerAssignedID, "916", referenceTypeCode); // additional invoice related document
}
public ReferencedDocument(String issuerAssingedID, Date formattedIssueDateTime) {
this(issuerAssingedID);
this.formattedIssueDateTime = formattedIssueDateTime;
}
public ReferencedDocument(String issuerAssignedID) {
this.issuerAssignedID = issuerAssignedID;
this.typeCode = "916"; // additional invoice related document
this.referenceTypeCode = referenceTypeCode;
}
/***
@@ -44,6 +65,15 @@ public class ReferencedDocument implements IReferencedDocument {
this.referenceTypeCode = referenceTypeCode;
}
/**
* issue date of this line
*
* @param formattedIssueDateTime as Date
*/
public void setFormattedIssueDateTime(Date formattedIssueDateTime) {
this.formattedIssueDateTime = formattedIssueDateTime;
}
@Override
public String getIssuerAssignedID() {
return issuerAssignedID;
@@ -58,4 +88,21 @@ public class ReferencedDocument implements IReferencedDocument {
public String getReferenceTypeCode() {
return referenceTypeCode;
}
@Override
public Date getFormattedIssueDateTime()
{
return formattedIssueDateTime;
}
public static ReferencedDocument fromNode(Node node) {
if (!node.hasChildNodes()) {
return null;
}
NodeMap nodes = new NodeMap(node);
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
nodes.getAsStringOrNull("ReferenceTypeCode"),
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
}
}

View File

@@ -1,5 +1,11 @@
package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class SchemedID {
protected String scheme;
protected String id;
@@ -31,4 +37,9 @@ public class SchemedID {
setId(id);
}
@Override
public String toString() {
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
}
}

View File

@@ -36,5 +36,9 @@ public enum SubjectCode {
/**
* Discount and bonus agreements
*/
AAK
AAK,
/**
* Vehicle licence number
*/
ABZ
}

View File

@@ -2,9 +2,11 @@ package org.mustangproject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation;
@@ -12,15 +14,18 @@ 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;
/***
* A organisation, i.e. usually a company
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class TradeParty implements IZUGFeRDExportableTradeParty {
protected String name, zip, street, location, country;
protected String name, zip, street, location, country, taxScheme;
protected String taxID = null, vatID = null;
protected String ID = null;
protected String description = null;
@@ -59,6 +64,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
protected void parseFromUBL(NodeList nodes) {
if (nodes.getLength() > 0) {
@@ -68,8 +74,34 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
if (currentItemNode.getLocalName() != null) {
String currentUBLChild = currentItemNode.getLocalName();
if (currentUBLChild.equals("Party")) {
// if (currentUBLChild.equals("Delivery")) {
// NodeList delivery = currentItemNode.getChildNodes();
// for (int deliveryIndex = 0; deliveryIndex < delivery.getLength(); deliveryIndex++) {
// if (delivery.item(deliveryIndex).getLocalName() != null) {
// Node currentNode = delivery.item(deliveryIndex);
// if (currentNode.getLocalName().equals("DeliveryLocation")) {
// NodeList deliveryLocation = currentNode.getChildNodes();
// for (int deliveryLocationIndex = 0; deliveryLocationIndex < deliveryLocation.getLength(); deliveryLocationIndex++) {
// if (deliveryLocation.item(deliveryLocationIndex).getLocalName() != null) {
// if (deliveryLocation.item(deliveryLocationIndex).getLocalName().equals("ID")) {
// //Node currentNode = partyID.item(partyIDIndex);
// setID(deliveryLocation.item(deliveryLocationIndex).getTextContent());
// if ((deliveryLocation.item(deliveryLocationIndex).getAttributes() != null &&
// (deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID") != null))
// ) {
// SchemedID sID = new SchemedID().setScheme(deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID").getTextContent());
// addGlobalID(sID);
// }
// }
// }
// }
// }
//
// }
// }
// }
if (currentUBLChild.equals("Party")) {
NodeList party = currentItemNode.getChildNodes();
for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) {
if (party.item(partyIndex).getLocalName() != null) {
@@ -87,19 +119,102 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
}
}
if (party.item(partyIndex).getLocalName().equals("EndpointID")) {
Node currentNode = party.item(partyIndex);
if ((currentNode.getAttributes() != null &&
(currentNode.getAttributes().getNamedItem("schemeID") != null))
&& (party.item(partyIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("EM"))
) {
setEmail(currentNode.getTextContent());
}
}
if (currentTopElementName.equals("PartyIdentification")) {
NodeList partyID = party.item(partyIndex).getChildNodes();
for (int partyIDIndex = 0; partyIDIndex < partyID.getLength(); partyIDIndex++) {
if (partyID.item(partyIDIndex).getLocalName() != null) {
if (partyID.item(partyIDIndex).getLocalName().equals("ID")) {
Node currentNode = partyID.item(partyIDIndex);
if ((currentNode.getAttributes() != null &&
(currentNode.getAttributes().getNamedItem("schemeID") != null))
) {
SchemedID sID = new SchemedID().setScheme(currentNode.getAttributes().getNamedItem("schemeID").getTextContent()).setId(currentNode.getTextContent());
addGlobalID(sID);
}
else {
setID(currentNode.getTextContent());
}
// UBL only: formally it can have a name as well but BT27 party name *should* be stored in
// so overwrite if one exists
if (currentTopElementName.equals("PartyLegalEntity")) {
NodeList legal = party.item(partyIndex).getChildNodes();
for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) {
if (legal.item(legalChildIndex).getLocalName() != null) {
if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) {
setName(legal.item(legalChildIndex).getTextContent());
}
}
}
}
if (currentTopElementName.equals("PartyTaxScheme")) {
NodeList partyTaxScheme = party.item(partyIndex).getChildNodes();
String CompanyId = null;
for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) {
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) {
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) {
CompanyId = (partyTaxScheme.item(partyTaxSchemeIndex).getTextContent());
}
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("TaxScheme")) {
NodeList taxSchemechilds = partyTaxScheme.item(partyTaxSchemeIndex).getChildNodes();
for (int taxSchemechildsIndex = 0; taxSchemechildsIndex < taxSchemechilds.getLength(); taxSchemechildsIndex++) {
if (taxSchemechilds.item(taxSchemechildsIndex).getLocalName() != null) {
if (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("FC") || (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("NOVAT"))) {
setTaxID(CompanyId);
} else {
setVATID(CompanyId);
}
}
}
}
}
}
}
/*
UBL only: formally it can have a name as well but BT27 party name *should* be stored in
so overwrite if one exists
*/
if (currentTopElementName.equals("PartyLegalEntity")) {
NodeList legal = party.item(partyIndex).getChildNodes();
LegalOrganisation lo = null;
for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) {
if (legal.item(legalChildIndex).getLocalName() != null) {
if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) {
if (lo == null) {
lo = new LegalOrganisation();
}
lo.setTradingBusinessName(legal.item(legalChildIndex).getTextContent());
}
if (legal.item(legalChildIndex).getLocalName().equals("CompanyLegalForm")) {
setDescription(legal.item(legalChildIndex).getTextContent());
}
if (legal.item(legalChildIndex).getLocalName().equals("CompanyID")) {
if (lo == null) {
lo = new LegalOrganisation();
}
if (legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID")!=null) {
SchemedID sid = new SchemedID(legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue(), legal.item(legalChildIndex).getTextContent());
lo.setSchemedID(sid);
}
}
// we dont have that attribute yet in the legalorganisation: CompanyLegalForm
if (lo != null) {
setLegalOrganisation(lo);
}
}
}
}
if (currentTopElementName.equals("PostalAddress")) {
NodeList postal = party.item(partyIndex).getChildNodes();
@@ -147,8 +262,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
}
}
if (postal.item(postalChildIndex).getLocalName().equals("Name")) {
setName(postal.item(postalChildIndex).getTextContent());
@@ -166,7 +279,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
}
}
if (currentUBLChild.equals("GlobalID")) {
@@ -180,56 +292,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
NodeList contact = nodes.item(nodeIndex).getChildNodes();
setContact(new Contact(contact));
}
if (currentUBLChild.equals("PostalTradeAddress")) {
NodeList postal = nodes.item(nodeIndex).getChildNodes();
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) {
if (postal.item(postalChildIndex).getLocalName() != null) {
if (postal.item(postalChildIndex).getLocalName().equals("LineOne")) {
setStreet(postal.item(postalChildIndex).getTextContent());
}
if (postal.item(postalChildIndex).getLocalName().equals("LineTwo")) {
setAdditionalAddress(postal.item(postalChildIndex).getTextContent());
}
if (postal.item(postalChildIndex).getLocalName().equals("LineThree")) {
setAdditionalAddressExtension(postal.item(postalChildIndex).getTextContent());
}
if (postal.item(postalChildIndex).getLocalName().equals("CityName")) {
setLocation(postal.item(postalChildIndex).getTextContent());
}
if (postal.item(postalChildIndex).getLocalName().equals("PostcodeCode")) {
setZIP(postal.item(postalChildIndex).getTextContent());
}
if (postal.item(postalChildIndex).getLocalName().equals("CountryID")) {
setCountry(postal.item(postalChildIndex).getTextContent());
}
}
}
}
if (currentUBLChild.equals("SpecifiedTaxRegistration")) {
NodeList taxChilds = nodes.item(nodeIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
if (taxChilds.item(taxChildIndex).getLocalName() != null) {
if ((taxChilds.item(taxChildIndex).getLocalName().equals("ID"))) {
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
if (firstChild != null) {
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
setVATID(firstChild.getNodeValue());
}
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
setTaxID(firstChild.getNodeValue());
}
}
}
}
}
}
}
}
}
}
@@ -424,6 +486,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
@Override
@JsonIgnore
public String getUriUniversalCommunicationID() {
if (uriUniversalCommunicationId != null) {
return uriUniversalCommunicationId.getID();
@@ -433,6 +496,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
@Override
@JsonIgnore
public String getUriUniversalCommunicationIDScheme() {
if (uriUniversalCommunicationId != null) {
return uriUniversalCommunicationId.getScheme();
@@ -497,6 +561,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this;
}
/**
* (optional)
*
@@ -508,6 +573,15 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this;
}
/**
* primarily for invoiceimporter and JSON
*
* @return the list of sepa mandates
*/
public List<DirectDebit> getDebitDetails() {
return debitDetails;
}
@Override
public IZUGFeRDLegalOrganisation getLegalOrganisation() {
return legalOrg;
@@ -667,6 +741,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return contact;
}
@JsonIgnore
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
return null;

View File

@@ -4,15 +4,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;
import java.text.SimpleDateFormat;
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
@@ -23,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);
@@ -70,7 +43,114 @@ public class XMLTools extends XMLWriter {
}
/**
* returns the value of an node
*
* @param node the Node to get the value from
* @return A String or empty String, if no value was found
*/
public static String getNodeValue(Node node) {
if (node != null && node.getFirstChild() != null) {
return node.getFirstChild().getNodeValue();
}
return "";
}
/**
* tries to convert a String to BigDecimal.
*
* @param nodeValue The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
public static BigDecimal tryBigDecimal(String nodeValue) {
try {
return new BigDecimal(nodeValue);
} catch (final Exception e) {
try {
return BigDecimal.valueOf(Float.valueOf(nodeValue));
} catch (final Exception ex) {
return new BigDecimal("0.00");
}
}
}
/**
* tries to convert a Node to a BigDecimal.
*
* @param node The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
public static BigDecimal tryBigDecimal(Node node) {
final String nodeValue = XMLTools.getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
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 the value to be formatted
* @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
* @param node the node
* @return a util.Date, or null, if not parseable
*/
public static Date tryDate(Node node) {
final String nodeValue = XMLTools.getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
return tryDate(nodeValue);
}
/***
* returns a util.Date from a 102 String yyyymmdd
* @param toParse the string
* @return a util.Date, or null, if not parseable
*/
public static Date tryDate(String toParse) {
SimpleDateFormat formatter = null;
if (toParse==null) {
return null;
}
if (toParse.contains("-")) {
// from ubl
formatter = new SimpleDateFormat("yyyy-MM-dd");
} else {
formatter = ZUGFeRDDateFormat.DATE.getFormatter();
}
try {
return formatter.parse(toParse);
} catch (final Exception e) {
return null;
}
}
/***
* relplaces some entities like &lt; , &gt; and &amp; with their escaped pendant like &amp;lt;
* @param s the string
* @return the "safe" string
*/
public static String encodeXML(CharSequence s) {
if (s == null) {
return "";
@@ -140,7 +220,20 @@ public class XMLTools extends XMLWriter {
}
public static byte[] getBytesFromStream(InputStream fileinput) throws IOException {
return IOUtils.toByteArray (fileinput);
// Stream closing responsibility is with the caller
return IOUtils.toByteArray(fileinput);
}
public static String trimOrNull(Node node) {
if (node != null) {
String textContent = node.getTextContent();
if (textContent != null) {
return textContent.trim();
}
}
return null;
}
}

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

@@ -30,11 +30,13 @@ package org.mustangproject.ZUGFeRD;
* */
import java.math.BigDecimal;
import java.util.Date;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
/***
@@ -303,8 +305,7 @@ public interface IExportableTransaction {
}
/**
* get payment terms. if set, getPaymentTermDescription() and getDueDate() are
* ignored
* get payment terms. if set, getPaymentTermDescription() and getDueDate() are ignored
*
* @return the IZUGFeRDPaymentTerms of the invoice
*/
@@ -312,6 +313,18 @@ public interface IExportableTransaction {
return null;
}
default String getPaymentReference() {
return null;
}
/**
* Get payment terms for the EXTENDED profile (multiple terms are allowed)
* @return
*/
default IZUGFeRDPaymentTerms[] getExtendedPaymentTerms() {
return new IZUGFeRDPaymentTerms[0];
}
/**
* returns if a rebate agreements exists
*
@@ -322,10 +335,20 @@ public interface IExportableTransaction {
}
/**
* get reference document number typically used for Invoice Corrections Will be
* added as IncludedNote in comfort profile
* get the rounding amount
* (only to be usef for NL whose currency requires a rounding to 5ct)
*
* @return the ID of the document this document refers to
* @return the Bigdecimal
*/
default BigDecimal getRoundingAmount() {
return null;
}
/**
* get BuyerReference (BT-10) an identifier assigned by the buyer and used
* for internal routing. Used for the Leitweg-ID.
*
* @return the BuyerReference of this document
*/
default String getReferenceNumber() {
return null;
@@ -415,13 +438,23 @@ public interface IExportableTransaction {
*
* @return the ID of the document
*/
@Deprecated
default String getInvoiceReferencedDocumentID() {
return null;
}
@Deprecated
default Date getInvoiceReferencedIssueDate() {
return null;
}
/**
* Getter for BG-3
* @return list of documents
*/
default ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
return null;
}
/**
* get the issue timestamp of the BuyerOrderReferencedDocument, which sits in
@@ -450,11 +483,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

@@ -1,5 +1,7 @@
package org.mustangproject.ZUGFeRD;
import java.util.Date;
public interface IReferencedDocument {
/***
@@ -20,4 +22,12 @@ public interface IReferencedDocument {
*/
String getReferenceTypeCode();
/***
*
* issue date of this line
*
* @return date of the issue
*/
Date getFormattedIssueDateTime();
}

View File

@@ -38,6 +38,12 @@ public interface IZUGFeRDAllowanceCharge {
*/
default BigDecimal getPercent() {return null;}
/***
* returns a basis the precentage is calculated from
* @return null or the basis
*/
default BigDecimal getBasisAmount() {return null;}
/***
* get a description for the allowance/charge
* @return the description

View File

@@ -28,7 +28,9 @@ package org.mustangproject.ZUGFeRD;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.mustangproject.IncludedNote;
import org.mustangproject.Item;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -41,7 +43,11 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
* item level discounts
* @return array of the discounts on a single item
*/
@Deprecated
default IZUGFeRDAllowanceCharge[] getItemAllowances() {
if (getProduct()!=null) {
return getProduct().getAllowances();
}
return null;
}
@@ -49,7 +55,25 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
* item level price additions
* @return array of the additional charges on the item
*/
@Deprecated
default IZUGFeRDAllowanceCharge[] getItemCharges() {
if (getProduct()!=null) {
return getProduct().getCharges();
}
return null;
}
@Deprecated
default IZUGFeRDAllowanceCharge[] getAllowances() {
return null;
}
/**
* item level price additions
* @return array of the additional charges on the item
*/
@Deprecated
default IZUGFeRDAllowanceCharge[] getCharges() {
return null;
}
@@ -90,7 +114,7 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
/**
* how many items units per price
*
*
* @return item units per price
*/
default BigDecimal getBasisQuantity() {
@@ -150,14 +174,35 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
default Date getDetailedDeliveryPeriodTo() {
return null;
}
/***
* specify allowances amount for the line item total
* get all (allowances and charges) SpecifiedTradeAllowanceCharges
*
* @return the sum of allowances for this item
* @return the real item lecel SpecifiedTradeAllowanceCharges
*/
default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() {
return null;
}
/***
*
* @return the line ID
*/
default String getId() {
return null;
}
/**
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
*
* The information are written to the same xml nodes like {@link #getNotes()} but with explicit subjectCode.
* @return list of the notes
*/
default List<IncludedNote> getNotesWithSubjectCode() {
return null;
}
default String getAccountingReference() {
return null;
}
}

View File

@@ -165,4 +165,30 @@ public interface IZUGFeRDExportableProduct {
default HashMap<String, String> getAttributes() {
return null;
}
/**
* product level discounts (AppliedTradeAllowanceCharge, will change net price)
* @return array of the discounts on a single product
*/
default IZUGFeRDAllowanceCharge[] getAllowances() {
return null;
}
/**
* product level charges (AppliedTradeAllowanceCharge, will change net price)
* @return array of the additional charges on the product
*/
default IZUGFeRDAllowanceCharge[] getCharges() {
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

@@ -25,8 +25,8 @@ public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
@Override
default String getSettlementXML() {
String xml = "<ram:SpecifiedTradeSettlementPaymentMeans>"
+ "<ram:TypeCode>59</ram:TypeCode>"
+ "<ram:Information>SEPA direct debit</ram:Information>"
+ "<ram:TypeCode>" + XMLTools.encodeXML(getPaymentMeansCode()) + "</ram:TypeCode>"
+ "<ram:Information>" + XMLTools.encodeXML(getPaymentMeansInformation()) + "</ram:Information>"
+ "<ram:PayerPartyDebtorFinancialAccount>"
+ "<ram:IBANID>" + XMLTools.encodeXML(getIBAN()) + "</ram:IBANID>"
+ "</ram:PayerPartyDebtorFinancialAccount>";
@@ -51,4 +51,14 @@ public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
*/
String getMandate();
/***
* @return payment means code (BT-81 / UNTDID 4461)
*/
String getPaymentMeansCode();
/***
* @return payment means description (BT-82) (optional)
*/
String getPaymentMeansInformation();
}

View File

@@ -2,30 +2,49 @@ package org.mustangproject.ZUGFeRD;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
/***
* the linecalculator does the math within an item line, and e.g. calculates quantity*price.
* @see TransactionCalculator
*/
public class LineCalculator {
private final BigDecimal price;
private final BigDecimal priceGross;
private final BigDecimal itemTotalNetAmount;
private final BigDecimal itemTotalVATAmount;
private BigDecimal allowance = BigDecimal.ZERO;
private BigDecimal charge = BigDecimal.ZERO;
private BigDecimal allowanceItemTotal = BigDecimal.ZERO;
protected BigDecimal price;
protected BigDecimal priceGross;
protected BigDecimal itemTotalNetAmount;
protected BigDecimal itemTotalVATAmount;
protected BigDecimal lineAllowance = BigDecimal.ZERO;
protected BigDecimal lineCharge = BigDecimal.ZERO;
protected BigDecimal itemAllowance = BigDecimal.ZERO;
protected BigDecimal itemCharge = BigDecimal.ZERO;
protected BigDecimal allowanceItemTotal = BigDecimal.ZERO;
public LineCalculator(IZUGFeRDExportableItem currentItem) {
if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
addAllowance(allowance.getTotalAmount(currentItem));
BigDecimal factor=BigDecimal.ONE;
BigDecimal singleAllowance=allowance.getTotalAmount(currentItem);
addItemAllowance(singleAllowance);
if ((allowance.getPercent()!=null)&&(allowance.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
factor=currentItem.getQuantity();
}
addAllowanceItemTotal(singleAllowance.multiply(factor));
}
}
if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
addCharge(charge.getTotalAmount(currentItem));
BigDecimal factor=BigDecimal.ONE;
BigDecimal singleCharge=charge.getTotalAmount(currentItem);
addItemCharge(singleCharge);
if ((charge.getPercent()!=null)&&(charge.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
factor=currentItem.getQuantity();
}
subtractAllowanceItemTotal(singleCharge.multiply(factor));
}
}
if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) {
@@ -34,16 +53,47 @@ public class LineCalculator {
}
}
BigDecimal vatPercent = currentItem.getProduct().getVATPercent();
if (vatPercent == null)
BigDecimal vatPercent = null;
if (currentItem.getProduct()!=null) {
vatPercent = currentItem.getProduct().getVATPercent();
}
if (vatPercent == null) {
vatPercent = BigDecimal.ZERO;
}
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())
.subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
BigDecimal quantity=BigDecimal.ZERO;
if ((currentItem!=null)&&(currentItem.getQuantity()!=null)) {
quantity=currentItem.getQuantity();
}
price=currentItem.getPrice();
priceGross=price;
// price=price.subtract(itemAllowance).add(itemCharge);
// BigDecimal delta=charge.subtract(allowanceItemTotal).subtract(allowance);
// delta=delta.divide(currentItem.getQuantity(), 18, RoundingMode.HALF_UP);
BigDecimal delta=BigDecimal.ZERO;
if (currentItem.getProduct().getAllowances()!=null) {
for (IZUGFeRDAllowanceCharge ccaf:currentItem.getProduct().getAllowances()) {
delta=delta.subtract(ccaf.getTotalAmount(currentItem));
}
}
if (currentItem.getProduct().getCharges()!=null) {
for (IZUGFeRDAllowanceCharge ccaf : currentItem.getProduct().getCharges()) {
delta = delta.subtract(ccaf.getTotalAmount(currentItem));
}
}
price=price.add(delta);
// Division/Zero occurred here.
// Used the setScale only because that's also done in getBasisQuantity
BigDecimal basisQuantity = currentItem.getBasisQuantity().compareTo(BigDecimal.ZERO) == 0
? BigDecimal.ONE.setScale(4)
: currentItem.getBasisQuantity();
itemTotalNetAmount = quantity.multiply(price).divide(basisQuantity, 18, RoundingMode.HALF_UP)
.add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);//.setScale(2, RoundingMode.HALF_UP);
}
public BigDecimal getPrice() {
@@ -66,16 +116,27 @@ public class LineCalculator {
return priceGross;
}
public void addAllowance(BigDecimal b) {
allowance = allowance.add(b);
public void addLineAllowance(BigDecimal b) {
lineAllowance = lineAllowance.add(b);
}
public void addCharge(BigDecimal b) {
charge = charge.add(b);
public void addLineCharge(BigDecimal b) {
lineCharge = lineCharge.add(b);
}
public void addItemAllowance(BigDecimal b) {
itemAllowance = itemAllowance.add(b);
}
public void addItemCharge(BigDecimal b) {
itemCharge = itemCharge.add(b);
}
public void addAllowanceItemTotal(BigDecimal b) {
allowanceItemTotal = allowanceItemTotal.add(b);
}
public void subtractAllowanceItemTotal(BigDecimal b) {
allowanceItemTotal = allowanceItemTotal.subtract(b);
}
}

View File

@@ -32,6 +32,7 @@ import java.util.Map;
import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.XMLTools;
public class OXPullProvider extends ZUGFeRD2PullProvider {
@@ -459,6 +460,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
}
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
if (doc.getFormattedIssueDateTime() != null) {
xml += "<ram:FormattedIssueDateTime>"
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
+ "</ram:FormattedIssueDateTime>";
}
xml += "</ram:InvoiceReferencedDocument>";
}
}
xml += "</ram:ApplicableHeaderTradeSettlement>";
// + " <IncludedSupplyChainTradeLineItem>"
@@ -492,36 +506,44 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
private String buildPaymentTermsXml() {
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
if (paymentTerms == null) {
final IZUGFeRDPaymentTerms[] paymentTerms = trans.getExtendedPaymentTerms();
String paymentTermsXml = "";
if (paymentTerms == null || paymentTerms.length == 0) {
return "";
}
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
if (discountTerms != null) {
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
for (IZUGFeRDPaymentTerms pt : paymentTerms)
{
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
if (discountTerms != null)
{
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
+ "</ram:CalculationPercent>";
if (discountTerms.getBaseDate() != null) {
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
if (discountTerms.getBaseDate() != null)
{
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
}
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
return paymentTermsXml;
}

View File

@@ -13,14 +13,14 @@ import org.apache.pdfbox.preflight.parser.PreflightParser;
import jakarta.activation.DataSource;
// Copied from PDFBox preflight 2.0.x
// Copied from PDFBox preflight 2.0.x
final class ByteArrayDataSource implements DataSource
{
private ByteArrayOutputStream data;
private String type = null;
private String name = null;
public ByteArrayDataSource (InputStream is) throws IOException
public ByteArrayDataSource (final InputStream is) throws IOException
{
data = new ByteArrayOutputStream ();
IOUtils.copy (is, data);
@@ -36,7 +36,7 @@ final class ByteArrayDataSource implements DataSource
* @param type
* the type to set
*/
public void setType (String type)
public void setType (final String type)
{
this.type = type;
}
@@ -45,7 +45,7 @@ final class ByteArrayDataSource implements DataSource
* @param name
* the name to set
*/
public void setName (String name)
public void setName (final String name)
{
this.name = name;
}
@@ -70,12 +70,13 @@ final class ByteArrayDataSource implements DataSource
// Try to create an API similar to the 2.x one
final class PreflightParserHelper
{
private static File createTmpFile (InputStream input) throws IOException
private static File createTmpFile (final InputStream input) throws IOException
{
FileOutputStream fos = null;
try
{
File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
final File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
tmpFile.deleteOnExit ();
fos = new FileOutputStream (tmpFile);
IOUtils.copy (input, fos);
return tmpFile;
@@ -87,7 +88,7 @@ final class PreflightParserHelper
}
}
public static PreflightParser createPreflightParser (DataSource dataSource) throws IOException
public static PreflightParser createPreflightParser (final DataSource dataSource) throws IOException
{
return new PreflightParser (createTmpFile (dataSource.getInputStream ()));
}

View File

@@ -4,13 +4,17 @@ import static java.math.BigDecimal.ZERO;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/***
* The Transactioncalculator e.g. adds the line totals and applies VAT on whole
* invoices
*
*
* @see LineCalculator
*/
public class TransactionCalculator implements IAbsoluteValueProvider {
@@ -27,7 +31,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
/***
* if something had already been paid in advance, this will get it from the
* transaction
*
*
* @return prepaid amount
*/
protected BigDecimal getTotalPrepaid() {
@@ -39,21 +43,21 @@ 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
*/
public BigDecimal getGrandTotal() {
final BigDecimal res = getTaxBasis();
BigDecimal basis = getTaxBasis();
return getVATPercentAmountMap().values().stream().map(VATAmount::getCalculated)
.map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(res);
.map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(basis);
}
/***
* returns total of charges for this tax rate
*
*
* @param percent a specific rate, or null for any rate
* @return the total amount
*/
@@ -62,6 +66,24 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
return sumAllowanceCharge(percent, charges);
}
/**
* Returns information about every tax that is involved in the current transaction.
*
* @return transaction taxes.
*/
public Set<VATAmount> getTaxDetails() {
return getVATPercentAmountMap().entrySet().stream()
.map(entry ->
new VATAmount(
entry.getValue().getBasis(),
entry.getValue().getCalculated(),
entry.getValue().getCategoryCode()
).setApplicablePercent(entry.getKey())
)
.collect(Collectors.toSet());
}
private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
BigDecimal res = BigDecimal.ZERO;
if ((charges != null) && (charges.length > 0)) {
@@ -77,7 +99,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
/***
* returns a (potentially concatenated) string of charge reasons, or "Charges"
* if none are defined
*
*
* @param percent a specific rate, or null for any rate
* @return the space separated String
*/
@@ -91,23 +113,20 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
String res = " ";
if ((charges != null) && (charges.length > 0)) {
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)
&& currentCharge.getReason() != null) {
res += currentCharge.getReason() + " ";
}
}
if (charges == null) {
return "";
}
res = res.substring(0, res.length() - 1);
return res;
return Arrays.stream(charges)
.filter(currentCharge -> (percent == null || currentCharge.getTaxPercent().compareTo(percent) == 0))
.map(IZUGFeRDAllowanceCharge::getReason)
.filter(Objects::nonNull)
.collect(Collectors.joining(" "));
}
/***
* returns a (potentially concatenated) string of allowance reasons, or
* "Allowances", if none are defined
*
*
* @param percent a specific rate, or null for any rate
* @return the space separated String
*/
@@ -122,7 +141,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
/***
* returns total of allowances for this tax rate
*
*
* @param percent a specific rate, or null for any rate
* @return the total amount
*/
@@ -134,25 +153,25 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
/***
* returns the total net value of all items, without document level
* charges/allowances
*
*
* @return item sum
*/
protected BigDecimal getTotal() {
BigDecimal dec = Stream.of(trans.getZFItems()).map(LineCalculator::new)
.map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add);
.map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add);
return dec;
}
/***
* returns the total net value of the invoice, including charges/allowances on
* document level
*
*
* @return item sum +- charges/allowances
*/
protected BigDecimal getTaxBasis() {
public BigDecimal getTaxBasis() {
return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP))
.subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP))
.setScale(2, RoundingMode.HALF_UP);
.subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP))
.setScale(2, RoundingMode.HALF_UP);
}
/**
@@ -167,11 +186,18 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
final String vatDueDateTypeCode = trans.getVATDueDateTypeCode();
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
BigDecimal percent = currentItem.getProduct().getVATPercent();
BigDecimal percent = null;
if (currentItem.getProduct() != null) {
percent = currentItem.getProduct().getVATPercent();
}
if (percent != null) {
LineCalculator lc = new LineCalculator(currentItem);
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
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);
@@ -189,8 +215,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
if (theAmount == null) {
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S",
vatDueDateTypeCode);
currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S",
vatDueDateTypeCode);
}
theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this)));
BigDecimal factor = taxPercent.divide(new BigDecimal(100));
@@ -207,8 +233,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
if (theAmount == null) {
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
vatDueDateTypeCode);
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
vatDueDateTypeCode);
}
theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this)));
BigDecimal factor = taxPercent.divide(new BigDecimal(100));
@@ -235,4 +261,11 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
return getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP);
}
public BigDecimal getDuePayable() {
BigDecimal res = getGrandTotal().subtract(getTotalPrepaid());
if (trans.getRoundingAmount() != null) {
res = res.add(trans.getRoundingAmount());
}
return res;
}
}

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() != null ? v.getVatExemptionReasonText(): this.vatExemptionReasonText);
}
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

@@ -0,0 +1,177 @@
package org.mustangproject.ZUGFeRD;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.XMLConstants;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.nio.charset.StandardCharsets;
public class ValidationLogVisualizer {
public enum Language {
EN,
FR,
DE
}
static final ClassLoader CLASS_LOADER = ValidationLogVisualizer.class.getClassLoader();
private static final String RESOURCE_PATH = "";
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationLogVisualizer.class);
private TransformerFactory mFactory = null;
private Templates mXsltPDFTemplate = null;
public ValidationLogVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl();
// fact = TransformerFactory.newInstance();
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
}
protected void applyXSLTToPDF(final String xmlContent, final OutputStream PDFOutstream)
throws TransformerException {
Transformer transformer = mXsltPDFTemplate.newTransformer();
transformer.transform(new StreamSource(new StringReader(xmlContent)), new StreamResult(PDFOutstream));
}
protected String toFOP(final String xmlContent)
throws TransformerException {
try {
if (mXsltPDFTemplate == null) {
mXsltPDFTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/result-pdf.xsl")));
}
} catch (TransformerConfigurationException ex) {
LOGGER.error("Failed to init XSLT templates", ex);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
applyXSLTToPDF(xmlContent, baos);
} catch (Exception e1) {
LOGGER.error("Failed to create PDF", e1);
}
return baos.toString(StandardCharsets.UTF_8);
}
public byte[] createPDFBytes(String xmlLogfileContent) {
// the writing part
String result = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
result = this.toFOP(xmlLogfileContent);
} catch ( TransformerException e) {
LOGGER.error("Failed to apply FOP", e);
}
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = null;
try {
cfg = cfgBuilder.build(CLASS_LOADER.getResourceAsStream("fop-config.xconf"));
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
FopFactoryBuilder builder = new FopFactoryBuilder(new File(".").toURI(), new ClasspathResolverURIAdapter()).setConfiguration(cfg);
// Step 1: Construct a FopFactory by specifying a reference to the configuration file
// (reuse if you plan to render multiple documents!)
FopFactory fopFactory = builder.build();
fopFactory.getFontManager().setResourceResolver(
ResourceResolverFactory.createInternalResourceResolver(
new File(".").toURI(),
new ClasspathResolverURIAdapter()));
FOUserAgent userAgent = fopFactory.newFOUserAgent();
userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-3b");
// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStream out = new BufferedOutputStream(baos)) {
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
// Step 4: Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Transformer transformer = factory.newTransformer(); // identity transformer
// Step 5: Setup input and output for XSLT transformation
// Setup input stream
Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res);
} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
}
return baos.toByteArray();
}
public byte[] toPDF(String xmlLogfileContent) {
return createPDFBytes(xmlLogfileContent);
}
public void toPDF(String xmlLogfileContent, String pdfFilename) {
byte[] pdfData = createPDFBytes(xmlLogfileContent);
try (FileOutputStream fos = new FileOutputStream(pdfFilename)) {
fos.write(pdfData);
} catch (IOException e) {
LOGGER.error("Failed to write PDF to file", e);
}
}
private static class ClasspathResourceURIResolver implements URIResolver {
ClasspathResourceURIResolver() {
// Do nothing, just prevents synthetic access warning.
}
@Override
public Source resolve(String href, String base) throws TransformerException {
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
}
}
}

View File

@@ -433,44 +433,57 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
}
private String buildPaymentTermsXml() {
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
final IZUGFeRDPaymentTerms[] paymentTerms = trans.getExtendedPaymentTerms();
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
final Date dueDate = paymentTerms.getDueDate();
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
throw new IllegalStateException(
String paymentTermsXml = "";
if (paymentTerms == null || paymentTerms.length == 0) {
return paymentTermsXml;
}
for (IZUGFeRDPaymentTerms pt : paymentTerms)
{
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
final Date dueDate = pt.getDueDate();
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null)
{
throw new IllegalStateException(
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
}
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
if (dueDate != null) {
paymentTermsXml += "<ram:DueDateDateTime>";
paymentTermsXml += DATE.udtFormat(dueDate);
paymentTermsXml += "</ram:DueDateDateTime>";
}
if (discountTerms != null) {
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
+ "</ram:CalculationPercent>";
if (discountTerms.getBaseDate() != null) {
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
}
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
if (dueDate != null)
{
paymentTermsXml += "<ram:DueDateDateTime>";
paymentTermsXml += DATE.udtFormat(dueDate);
paymentTermsXml += "</ram:DueDateDateTime>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
}
if (discountTerms != null)
{
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
+ "</ram:CalculationPercent>";
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
if (discountTerms.getBaseDate() != null)
{
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
}
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
}
return paymentTermsXml;
}

View File

@@ -45,13 +45,15 @@ import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.XMLTools;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
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 +78,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 +97,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 +107,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;
@@ -137,14 +141,25 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
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.getName() != null && !party.getName().isEmpty()) {
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
}
if (party.getDescription() != null) {
xml += "<ram:Description>" + XMLTools.encodeXML(party.getDescription()) + "</ram:Description>";
}
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>";
if (profile == Profiles.getByName("Minimum")) {
xml += "<ram:ID>" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
} else {
String schemeAttribute = "";
if ((party.getLegalOrganisation().getSchemedID().getScheme() != null) && (party.getLegalOrganisation().getSchemedID().getScheme().length() > 0)) {
schemeAttribute = "schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\"";
}
xml += "<ram:ID " + schemeAttribute + ">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
}
}
if (party.getLegalOrganisation().getTradingBusinessName() != null) {
xml += "<ram:TradingBusinessName>" + XMLTools.encodeXML(party.getLegalOrganisation().getTradingBusinessName()) + "</ram:TradingBusinessName>";
@@ -155,11 +170,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
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>";
@@ -200,7 +215,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>";
@@ -228,6 +243,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
@@ -247,20 +286,19 @@ 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") || profile == Profiles.getByName("EN16931"))) {
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
}
String reasonCode = "";
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
if (allowance.getReasonCode() != null) {
// only in XRechnung profile
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
}
final String allowanceChargeStr = "<ram:AppliedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
"<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
reason +
reasonCode +
reason +
"</ram:AppliedTradeAllowanceCharge>";
return allowanceChargeStr;
}
@@ -283,20 +321,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
String reason = "";
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
// only in extended profile
boolean isEN16931=(profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931"));
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) {
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
}
String reasonCode = "";
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) {
// only in XRechnung profile
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
}
final String itemTotalAllowanceChargeStr = "<ram:SpecifiedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
reason +
reasonCode +
reason +
"</ram:SpecifiedTradeAllowanceCharge>";
return itemTotalAllowanceChargeStr;
}
@@ -306,11 +344,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
this.trans = trans;
this.calc = new TransactionCalculator(trans);
boolean hasDueDate = false;
boolean hasDueDate = trans.getDueDate() != null;
final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
String exemptionReason = "";
if (trans.getPaymentTermDescription() != null) {
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription());
}
@@ -325,7 +361,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());
}
}
@@ -360,27 +396,27 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:GuidelineSpecifiedDocumentContextParameter>"
+ "</rsm:ExchangedDocumentContext>"
+ "<rsm:ExchangedDocument>"
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>"
// + "<ram:Name>RECHNUNG</ram:Name>"
// + "<ram:TypeCode>380</ram:TypeCode>"
+ "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
+ "<ram:IssueDateTime>"
+ DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
+ buildNotes(trans)
+ "</rsm:ExchangedDocument>"
+ "<rsm:SupplyChainTradeTransaction>";
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>";
if (profile == Profiles.getByName("Extended") && trans.getDocumentName() != null) {
xml += "<ram:Name>" + XMLTools.encodeXML(trans.getDocumentName()) + "</ram:Name>";
}
xml += "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
+ "<ram:IssueDateTime>" + DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
+ buildNotes(trans)
+ "</rsm:ExchangedDocument>"
+ "<rsm:SupplyChainTradeTransaction>";
int lineID = 0;
for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
lineID++;
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
String lineIDStr = Integer.toString(lineID);
if (currentItem.getId() != null) {
lineIDStr = currentItem.getId();
}
final LineCalculator lc = new LineCalculator(currentItem);
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
"<ram:AssociatedDocumentLineDocument>"
+ "<ram:LineID>" + lineID + "</ram:LineID>"
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>"
+ buildItemNotes(currentItem)
+ "</ram:AssociatedDocumentLineDocument>"
@@ -398,33 +434,52 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "</ram:BuyerAssignedID>";
}
String allowanceChargeStr = "";
if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
if (currentItem.getProduct().getAllowances() != null && currentItem.getProduct().getAllowances().length > 0) {
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getProduct().getAllowances()) {
allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem);
}
}
if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
if (currentItem.getProduct().getCharges() != null && currentItem.getProduct().getCharges().length > 0) {
for (final IZUGFeRDAllowanceCharge charge : currentItem.getProduct().getCharges()) {
allowanceChargeStr += getAllowanceChargeStr(charge, currentItem);
}
}
String itemTotalAllowanceChargeStr = "";
if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) {
for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getItemTotalAllowances()) {
if (currentItem.getAllowances() != null && currentItem.getAllowances().length > 0) {
for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getAllowances()) {
itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalAllowance, currentItem);
}
}
if (currentItem.getCharges() != null && currentItem.getCharges().length > 0) {
for (final IZUGFeRDAllowanceCharge itemTotalCharges : currentItem.getCharges()) {
itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalCharges, currentItem);
}
}
xml += "<ram:Name>" + XMLTools.encodeXML(currentItem.getProduct().getName()) + "</ram:Name>";
if (currentItem.getProduct().getDescription().length() > 0) {
if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) {
xml += "<ram:Description>" +
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>" +
@@ -433,8 +488,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>"
@@ -489,12 +544,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:SpecifiedLineTradeDelivery>"
+ "<ram:SpecifiedLineTradeSettlement>"
+ "<ram:ApplicableTradeTax>"
+ "<ram:TypeCode>VAT</ram:TypeCode>"
+ exemptionReason
+ "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>"
+ "<ram:RateApplicablePercent>"
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>"
+ "</ram:ApplicableTradeTax>";
+ "<ram:TypeCode>VAT</ram:TypeCode>";
if (currentItem.getProduct().getTaxExemptionReason() != null) {
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
if (!currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
xml += "<ram:RateApplicablePercent>"
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>";
}
xml += "</ram:ApplicableTradeTax>";
if ((currentItem.getDetailedDeliveryPeriodFrom() != null) || (currentItem.getDetailedDeliveryPeriodTo() != null)) {
xml += "<ram:BillingSpecifiedPeriod>";
if (currentItem.getDetailedDeliveryPeriodFrom() != null) {
@@ -506,7 +566,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:BillingSpecifiedPeriod>";
}
xml += itemTotalAllowanceChargeStr;
// item charges/allowances
if (!itemTotalAllowanceChargeStr.isEmpty()) {
xml += itemTotalAllowanceChargeStr ;
}
xml += "<ram:SpecifiedTradeSettlementLineMonetarySummation>"
+ "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
@@ -619,13 +682,19 @@ 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>";
}
if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>";
if ((trans.getPaymentReference() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getPaymentReference()) + "</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()) {
@@ -661,17 +730,25 @@ 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>" : "")
+ "<ram:RateApplicablePercent>"
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>";
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "");
if (!amountCategoryCode.equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
xml += "<ram:RateApplicablePercent>"
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>";
}
xml += "</ram:ApplicableTradeTax>";
}
}
}
@@ -687,23 +764,23 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
if (profile == Profiles.getByName("XRechnung")) {
for(IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
if (charge.getReason() != null) {
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
}
if (charge.getReasonCode() != null) {
xml += "<ram:ReasonCode>" + charge.getReasonCode() + "</ram:ReasonCode>";
}
if (charge.getReason() != null) {
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
}
xml += "<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + charge.getCategoryCode() + "</ram:CategoryCode>";
if (charge.getTaxPercent() != null) {
if (charge.getTaxPercent() != null && !charge.getCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
xml += "<ram:RateApplicablePercent>" + vatFormat(charge.getTaxPercent()) + "</ram:RateApplicablePercent>";
}
xml += "</ram:CategoryTradeTax>" +
@@ -731,7 +808,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>" +
@@ -772,7 +849,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
if ((trans.getPaymentTerms() == null) && (getProfile() != Profiles.getByName("Minimum")) && ((paymentTermsDescription != null) || (trans.getTradeSettlement() != null) || (hasDueDate))) {
xml += "<ram:SpecifiedTradePaymentTerms>";
@@ -780,6 +856,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:Description>" + paymentTermsDescription + "</ram:Description>";
}
if (trans.getDueDate() != null) {
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
+ DATE.udtFormat(trans.getDueDate())
+ "</ram:DueDateDateTime>";// 20130704
}
if (trans.getTradeSettlement() != null) {
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
@@ -788,12 +870,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
if (hasDueDate && (trans.getDueDate() != null)) {
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
+ DATE.udtFormat(trans.getDueDate())
+ "</ram:DueDateDateTime>";// 20130704
}
xml += "</ram:SpecifiedTradePaymentTerms>";
} else {
xml += buildPaymentTermsXml();
@@ -811,7 +887,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
final String chargesTotalLine = "<ram:ChargeTotalAmount>" + currencyFormat(calc.getChargesForPercent(null)) + "</ram:ChargeTotalAmount>";
xml += "<ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
if (getProfile() != Profiles.getByName("Minimum")) {
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BASICWL"))) {
xml += "<ram:LineTotalAmount>" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>";
xml += chargesTotalLine
+ allowanceTotalLine;
@@ -820,14 +896,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
// //
// currencyID=\"EUR\"
+ "<ram:TaxTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>"
+ "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>";
if (trans.getRoundingAmount() != null) {
xml += "<ram:RoundingAmount>" + currencyFormat(trans.getRoundingAmount()) + "</ram:RoundingAmount>";
}
xml += "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
// //
// currencyID=\"EUR\"
if (getProfile() != Profiles.getByName("Minimum")) {
xml += "<ram:TotalPrepaidAmount>" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>";
}
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>"
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getDuePayable()) + "</ram:DuePayableAmount>"
+ "</ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
if (trans.getInvoiceReferencedDocumentID() != null) {
xml += "<ram:InvoiceReferencedDocument>"
@@ -840,6 +920,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
if (doc.getFormattedIssueDateTime() != null) {
xml += "<ram:FormattedIssueDateTime>"
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
+ "</ram:FormattedIssueDateTime>";
}
xml += "</ram:InvoiceReferencedDocument>";
}
}
xml += "</ram:ApplicableHeaderTradeSettlement>";
// + "<IncludedSupplyChainTradeLineItem>\n"
@@ -871,8 +964,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));
@@ -896,56 +990,76 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
private String buildPaymentTermsXml() {
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
if (paymentTerms == null) {
ArrayList<IZUGFeRDPaymentTerms> paymentTerms = new ArrayList<IZUGFeRDPaymentTerms>(Arrays.asList(trans.getExtendedPaymentTerms()));
IZUGFeRDPaymentTerms izpt= trans.getPaymentTerms();
if (izpt!=null) {
paymentTerms.add(izpt);
}
String paymentTermsXml = "";
if (paymentTerms.size() == 0) {
return "";
}
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
final Date dueDate = paymentTerms.getDueDate();
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
throw new IllegalStateException(
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
}
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
for (IZUGFeRDPaymentTerms pt : paymentTerms)
{
if (dueDate != null) {
paymentTermsXml += "<ram:DueDateDateTime>";
paymentTermsXml += DATE.udtFormat(dueDate);
paymentTermsXml += "</ram:DueDateDateTime>";
}
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
if (trans.getTradeSettlement() != null) {
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
paymentTermsXml += payment.getPaymentXML();
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
final Date dueDate = pt.getDueDate();
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null)
{
throw new IllegalStateException(
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
}
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
if (dueDate != null)
{
paymentTermsXml += "<ram:DueDateDateTime>";
paymentTermsXml += DATE.udtFormat(dueDate);
paymentTermsXml += "</ram:DueDateDateTime>";
}
if (trans.getTradeSettlement() != null)
{
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement())
{
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit))
{
paymentTermsXml += payment.getPaymentXML();
}
}
}
}
if (discountTerms != null) {
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
+ "</ram:CalculationPercent>";
if (discountTerms != null)
{
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
final String currency = trans.getCurrency();
final String basisAmount = currencyFormat(calc.getGrandTotal());
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
+ "</ram:CalculationPercent>";
if (discountTerms.getBaseDate() != null) {
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
if (discountTerms.getBaseDate() != null)
{
final Date baseDate = discountTerms.getBaseDate();
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += DATE.udtFormat(baseDate);
paymentTermsXml += "</ram:BasisDateTime>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
}
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
}
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
return paymentTermsXml;
}

View File

@@ -58,6 +58,7 @@ import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema;
@@ -559,6 +560,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
// https://github.com/ZUGFeRD/mustangproject/issues/249
COSName cidSet = COSName.getPDFName("CIDSet");
COSName resources = COSName.getPDFName("Resources");
// iterate over all pdf pages
@@ -567,29 +569,45 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
PDPage page = (PDPage) object;
PDResources res = page.getResources();
for (COSName fontName : res.getFontNames()) {
try {
PDFont pdFont = res.getFont(fontName);
if (pdFont instanceof PDType0Font) {
PDType0Font typedFont = (PDType0Font) pdFont;
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
@SuppressWarnings("unused")
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
fontDescriptor.getCOSObject().removeItem(cidSet);
}
}
} catch (IOException e) {
throw e;
// Check for fonts in PDXObjects:
for (COSName xObjectName : res.getXObjectNames()) {
PDXObject xObject = res.getXObject(xObjectName);
COSDictionary d = xObject.getCOSObject().getCOSDictionary(resources);
if (d != null) {
PDResources xr = new PDResources(d);
removeCIDSetFromPDResources(cidSet, xr);
}
// do stuff with the font
}
// Check for fonts in document-resources:
removeCIDSetFromPDResources(cidSet, res);
}
}
}
private void removeCIDSetFromPDResources(COSName cidSet, PDResources res) throws IOException {
for (COSName fontName : res.getFontNames()) {
try {
PDFont pdFont = res.getFont(fontName);
if (pdFont instanceof PDType0Font) {
PDType0Font typedFont = (PDType0Font) pdFont;
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
@SuppressWarnings("unused")
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
fontDescriptor.getCOSObject().removeItem(cidSet);
}
}
} catch (IOException e) {
throw e;
}
// do stuff with the font
}
}
protected void prepareDocument() throws IOException {
PDDocumentCatalog cat = doc.getDocumentCatalog();

View File

@@ -83,15 +83,17 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
}
protected byte[] filenameToByteArray(String pdfFilename) throws IOException {
FileInputStream fileInputStream = new FileInputStream(pdfFilename);
return inputstreamToByteArray(fileInputStream);
try (FileInputStream fileInputStream = new FileInputStream(pdfFilename)) {
return inputstreamToByteArray(fileInputStream);
}
}
protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException {
byte[] bytes = new byte[fileInputStream.available()];
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
dataInputStream.readFully(bytes);
return bytes;
try (DataInputStream dataInputStream = new DataInputStream(fileInputStream)) {
dataInputStream.readFully(bytes);
return bytes;
}
}
/***

View File

@@ -13,92 +13,34 @@ package org.mustangproject.ZUGFeRD;
* @version 1.1.0
* @author jstaerk
*/
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.mustangproject.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ZUGFeRDImporter {
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
/**
* if metadata has been found
*/
protected boolean containsMeta = false;
/**
* map filenames of additional XML files to their contents
*/
private final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
/**
* map filenames of all embedded files in the respective PDF
*/
private final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
/**
* Raw XML form of the extracted data - may be directly obtained.
*/
private byte[] rawXML = null;
/**
* XMP metadata
*/
private String xmpString = null; // XMP metadata
/**
* parsed Document
*/
private Document document;
private Integer version;
protected ZUGFeRDImporter() {
//constructor for extending classes
public ZUGFeRDImporter() {
super();
}
public ZUGFeRDImporter(String pdfFilename) {
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
extractLowLevel(bis);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
public ZUGFeRDImporter(String filename) {
super(filename);
}
public ZUGFeRDImporter(InputStream pdfStream) {
try {
extractLowLevel(pdfStream);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
public ZUGFeRDImporter(InputStream stream) {
super(stream);
}
@@ -113,169 +55,21 @@ public class ZUGFeRDImporter {
/**
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
*
* @param inStream a inputstream of a pdf file
*/
private void extractLowLevel(InputStream inStream) throws IOException {
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
byte[] pad = new byte[4];
pdfStream.mark(0);
pdfStream.read(pad);
pdfStream.reset();
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
// PDDocumentInformation info = doc.getDocumentInformation();
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
LOGGER.info("no-xmlpart");
return;
}
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = convertStreamToString(XMP);
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
if (etn == null) {
return;
}
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
// String filePath = "/tmp/";
if (efMap != null) {
extractFiles(efMap); // see
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
} else {
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
if (kids == null) {
return;
}
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
extractFiles(namesL);
}
}
}
} else {
// no PDF probably XML
containsMeta = true;
setRawXML(XMLTools.getBytesFromStream(pdfStream));
}
}
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
for (final String alias : names.keySet()) {
final PDComplexFileSpecification fileSpec = names.get(alias);
final String filename = fileSpec.getFilename();
/**
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
*/
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
containsMeta = true;
// String embeddedFilename = filePath + filename;
// File file = new File(filePath + filename);
// System.out.println("Writing " + embeddedFilename);
// ByteArrayOutputStream fileBytes=new
// ByteArrayOutputStream();
// FileOutputStream fos = new FileOutputStream(file);
setRawXML(embeddedFile.toByteArray());
// fos.write(embeddedFile.getByteArray());
// fos.close();
}
if (filename.startsWith("additional_data")) {
additionalXMLs.put(filename, embeddedFile.toByteArray());
}
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
}
}
protected Document getDocument() {
return document;
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
xmlFact.setNamespaceAware(true);
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
}
public void setRawXML(byte[] rawXML) throws IOException {
this.containsMeta = true;
this.rawXML = rawXML;
this.version = null;
try {
setDocument();
} catch (ParserConfigurationException | SAXException e) {
LOGGER.error("Failed to parse XML", e);
throw new ZUGFeRDExportException(e);
}
}
protected String extractString(String xpathStr) {
if (!containsMeta) {
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
}
final String result;
try {
final Document document = getDocument();
final XPathFactory xpathFact = XPathFactory.newInstance();
final XPath xpath = xpathFact.newXPath();
result = xpath.evaluate(xpathStr, document);
} catch (final XPathExpressionException e) {
LOGGER.error("Failed to evaluate XPath", e);
throw new ZUGFeRDExportException(e);
}
return result;
}
/***
* Wrapper for protected method extractString
* @param xpathStr the xpath expression to be evaluated
* @return the extracted String for the specific path in the document
*/
public String wExtractString(String xpathStr) {
return extractString(xpathStr);
}
////////////////////////////////////
/**
* @return the reference (purpose) the sender specified for this invoice
*/
public String getForeignReference() {
String result = extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']/*[local-name() = 'PaymentReference']");
if (result == null || result.isEmpty()) {
result = extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'PaymentReference']");
}
return result;
return importedInvoice.getNumber();
}
/**
* @return the ZUGFeRD Profile
*/
public String getZUGFeRDProfil() {
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
if (guideline.contains("xrechnung")) {
return "XRECHNUNG";
@@ -292,6 +86,7 @@ public class ZUGFeRDImporter {
case "urn:factur-x.eu:1p0:minimum":
return "MINIMUM";
case "urn:ferd:CrossIndustryDocument:invoice:1p0:extended":
case "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended":
case "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended":
return "EXTENDED";
default:
@@ -299,21 +94,6 @@ public class ZUGFeRDImporter {
}
}
/**
* @return the Invoice Currency Code
*/
public String getInvoiceCurrencyCode() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
} else {
return extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/**
* @return the IssuerAssigned ID
@@ -336,57 +116,6 @@ public class ZUGFeRDImporter {
return extractIssuerAssignedID("ContractReferencedDocument");
}
private String extractIssuerAssignedID(String propertyName) {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/**
* @return the BuyerTradeParty ID
*/
public String getBuyerTradePartyID() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'ID']");
}
/**
* @return the Issue Date()
*/
public String getIssueDate() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
} else {
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
public Date getDetailedDeliveryPeriodFrom() {
final String toParse = extractString(
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
"//*[local-name() = 'BillingSpecifiedPeriod']" +
"//*[local-name() = 'StartDateTime']//*[local-name() = 'DateTimeString']");
return tryDate(toParse);
}
public Date getDetailedDeliveryPeriodTo() {
final String toParse = extractString(
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
"//*[local-name() = 'BillingSpecifiedPeriod']" +
"//*[local-name() = 'EndDateTime']//*[local-name() = 'DateTimeString']");
return tryDate(toParse);
}
/**
* @return the TaxBasisTotalAmount
@@ -470,7 +199,16 @@ public class ZUGFeRDImporter {
* @return the BuyerTradeParty SpecifiedTaxRegistration ID
*/
public String getBuyertradePartySpecifiedTaxRegistrationID() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'SpecifiedTaxRegistration']//*[local-name() = 'ID']");
String id = null;
if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) {
// this *should* be the official result
id = importedInvoice.getRecipient().getLegalOrganisation().getSchemedID().getID();
}
// but also provide some fallback
if (id == null) {
id = getBuyerTradePartyID();
}
return id;
}
@@ -494,14 +232,14 @@ public class ZUGFeRDImporter {
* @return the BuyerTradeParty Name
*/
public String getBuyerTradePartyName() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'Name']");
return importedInvoice.getRecipient().getName();
}
/**
* @return the BuyerTradeParty Name
*/
public String getDeliveryTradePartyName() {
return extractString("//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'Name']");
return importedInvoice.getDeliveryAddress().getName();
}
@@ -525,7 +263,7 @@ public class ZUGFeRDImporter {
* @return the Payment Terms
*/
public String getPaymentTerms() {
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']//*[local-name() = 'Description']");
return importedInvoice.getPaymentTermDescription();
}
/**
@@ -548,16 +286,7 @@ public class ZUGFeRDImporter {
* @return the Invoice ID
*/
public String getInvoiceID() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'ID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
return importedInvoice.getNumber();
}
@@ -582,16 +311,7 @@ public class ZUGFeRDImporter {
* @return the referred document
*/
public String getReference() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeAgreement']/*[local-name() = 'BuyerReference']");
} else {
return extractString("//*[local-name() = 'ApplicableHeaderTradeAgreement']/*[local-name() = 'BuyerReference']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
return importedInvoice.getReferenceNumber();
}
@@ -615,7 +335,18 @@ public class ZUGFeRDImporter {
* @return the sender's account IBAN code
*/
public String getIBAN() {
return extractString("//*[local-name() = 'PayeePartyCreditorFinancialAccount']/*[local-name() = 'IBANID']");
if ((importedInvoice==null)||(importedInvoice.getTradeSettlement()==null)) {
return null;
}
for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) {
if (settlement instanceof IZUGFeRDTradeSettlementDebit) {
return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN();
}
if (settlement instanceof IZUGFeRDTradeSettlementPayment) {
return ((IZUGFeRDTradeSettlementPayment) settlement).getOwnIBAN();
}
}
return null;
}
@@ -628,14 +359,7 @@ public class ZUGFeRDImporter {
* @return the total payable amount
*/
public String getAmount() {
String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']");
if (result == null || result.isEmpty()) {
/* fx/zf would be SpecifiedTradeSettlementMonetarySummation
* but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/
result = extractString("//*[local-name() = 'GrandTotalAmount']");
}
return result;
return importedInvoice.getGrandTotal().toPlainString();
}
@@ -643,7 +367,60 @@ public class ZUGFeRDImporter {
* @return when the payment is due
*/
public String getDueDate() {
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']/*[local-name() = 'DueDateDateTime']/*[local-name() = 'DateTimeString']");
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(importedInvoice.getDueDate());
}
////////////////////
/**
* @return the Invoice Currency Code
*/
public String getInvoiceCurrencyCode() {
return importedInvoice.getCurrency();
}
private String extractIssuerAssignedID(String propertyName) {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/**
* @return the BuyerTradeParty ID
*/
public String getBuyerTradePartyID() {
String id = importedInvoice.getRecipient().getID();
if (id == null) {
// provide some fallback
id = importedInvoice.getRecipient().getVATID();
}
return id;
}
/**
* @return the Issue Date()
*/
public String getIssueDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(importedInvoice.getIssueDate());
}
public Date getDetailedDeliveryPeriodFrom() {
return importedInvoice.getDetailedDeliveryPeriodFrom();
}
public Date getDetailedDeliveryPeriodTo() {
return importedInvoice.getDetailedDeliveryPeriodTo();
}
@@ -691,28 +468,6 @@ public class ZUGFeRDImporter {
}
public EStandard getStandard() throws Exception {
if (!containsMeta) {
throw new Exception("Not yet parsed");
}
final String head = getUTF8();
String rootNode = extractString("local-name(/*)");
if (rootNode.equals("CrossIndustryDocument")) {
return EStandard.zugferd;
} else if (rootNode.equals("Invoice")) {
return EStandard.ubl;
} else if (rootNode.equals("CrossIndustryInvoice")) {
return EStandard.facturx;
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
return EStandard.despatchadvice;
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
return EStandard.orderx;
}
throw new Exception("ZUGFeRD version could not be determined");
}
public int getVersion() throws Exception {
if (!containsMeta) {
throw new Exception("Not yet parsed");
@@ -736,35 +491,6 @@ public class ZUGFeRDImporter {
}
/**
* @return return UTF8 XML (without BOM) of the invoice
*/
public String getUTF8() {
if (rawXML == null) {
return null;
}
if (rawXML.length < 3) {
return new String(rawXML);
}
final byte[] bomlessData;
if ((rawXML[0] == (byte) 0xEF)
&& (rawXML[1] == (byte) 0xBB)
&& (rawXML[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
bomlessData = new byte[rawXML.length - 3];
System.arraycopy(rawXML, 3, bomlessData, 0,
rawXML.length - 3);
} else {
bomlessData = rawXML;
}
return new String(bomlessData);
}
/**
* Returns the raw XML data as extracted from the ZUGFeRD PDF file.
*
@@ -790,14 +516,6 @@ public class ZUGFeRDImporter {
}
static String convertStreamToString(java.io.InputStream is) {
try {
return IOUtils.toString(is, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* returns an instance of PostalTradeAddress for SellerTradeParty section
*
@@ -929,9 +647,11 @@ public class ZUGFeRDImporter {
/**
* returns a list of LineItems
* @deprecated use invoiceimporter getZFItems
*
* @return a List of LineItem instances
*/
@Deprecated
public List<Item> getLineItemList() {
final List<Node> nodeList = getLineItemNodes();
final List<Item> lineItemList = new ArrayList<>();
@@ -953,7 +673,7 @@ public class ZUGFeRDImporter {
if (node != null) {
final NodeList tradeAgreementChildren = node.getChildNodes();
node = getNodeByName(tradeAgreementChildren, "ChargeAmount");
lineItem.setPrice(tryBigDecimal(getNodeValue(node)));
lineItem.setPrice(XMLTools.tryBigDecimal(node));
node = getNodeByName(tradeAgreementChildren, "BasisQuantity");
if (node != null && node.getAttributes() != null) {
final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode");
@@ -966,49 +686,65 @@ public class ZUGFeRDImporter {
node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "ChargeAmount");
lineItem.setGrossPrice(tryBigDecimal(getNodeValue(node)));
lineItem.setGrossPrice(XMLTools.tryBigDecimal(node));
}
break;
case "AssociatedDocumentLineDocument":
node = getNodeByName(nn.getChildNodes(), "LineID");
lineItem.setId(getNodeValue(node));
lineItem.setId(XMLTools.getNodeValue(node));
break;
case "SpecifiedTradeProduct":
node = getNodeByName(nn.getChildNodes(), "GlobalID");
if (node != null) {
SchemedID globalId = new SchemedID()
.setScheme(node.getAttributes()
.getNamedItem("schemeID").getNodeValue())
.setId(XMLTools.getNodeValue(node));
lineItem.getProduct().addGlobalID(globalId);
}
node = getNodeByName(nn.getChildNodes(), "SellerAssignedID");
lineItem.getProduct().setSellerAssignedID(getNodeValue(node));
lineItem.getProduct().setSellerAssignedID(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID");
lineItem.getProduct().setBuyerAssignedID(getNodeValue(node));
lineItem.getProduct().setBuyerAssignedID(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "Name");
lineItem.getProduct().setName(getNodeValue(node));
lineItem.getProduct().setName(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "Description");
lineItem.getProduct().setDescription(getNodeValue(node));
lineItem.getProduct().setDescription(XMLTools.getNodeValue(node));
break;
case "SpecifiedLineTradeDelivery":
case "SpecifiedSupplyChainTradeDelivery":
node = getNodeByName(nn.getChildNodes(), "BilledQuantity");
lineItem.setQuantity(tryBigDecimal(getNodeValue(node)));
lineItem.setQuantity(XMLTools.tryBigDecimal(node));
break;
case "SpecifiedLineTradeSettlement":
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "RateApplicablePercent");
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
}
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
lineItem.setTax(XMLTools.tryBigDecimal(node));
}
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "CategoryCode");
if(node != null){
lineItem.getProduct().setTaxCategoryCode(XMLTools.getNodeValue(node));
}
}
node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod");
if (node != null) {
final Node start = getNodeByName(node.getChildNodes(), "StartDateTime");
@@ -1021,13 +757,13 @@ public class ZUGFeRDImporter {
if (end != null) {
dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString");
}
lineItem.setDetailedDeliveryPeriod(tryDate(dateTimeStart), tryDate(dateTimeEnd));
lineItem.setDetailedDeliveryPeriod(XMLTools.tryDate(dateTimeStart), XMLTools.tryDate(dateTimeEnd));
}
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
}
break;
case "SpecifiedSupplyChainTradeSettlement":
@@ -1036,19 +772,19 @@ public class ZUGFeRDImporter {
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "ApplicablePercent");
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
}
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
lineItem.setTax(XMLTools.tryBigDecimal(node));
}
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation");
if (node != null) {
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
}
break;
}
@@ -1123,51 +859,4 @@ public class ZUGFeRDImporter {
}
}
/**
* returns the value of an node
*
* @param node the Node to get the value from
* @return A String or empty String, if no value was found
*/
private String getNodeValue(Node node) {
if (node != null && node.getFirstChild() != null) {
return node.getFirstChild().getNodeValue();
}
return "";
}
/**
* tries to convert an String to BigDecimal.
*
* @param nodeValue The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
private BigDecimal tryBigDecimal(String nodeValue) {
try {
return new BigDecimal(nodeValue);
} catch (final Exception e) {
try {
return BigDecimal.valueOf(Float.valueOf(nodeValue));
} catch (final Exception ex) {
return new BigDecimal("0.00");
}
}
}
private Date tryDate(Node node) {
final String nodeValue = getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
return tryDate(nodeValue);
}
private static Date tryDate(String toParse) {
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
try {
return formatter.parse(toParse);
} catch (final Exception e) {
return null;
}
}
}

View File

@@ -20,56 +20,38 @@
*/
package org.mustangproject.ZUGFeRD;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import com.helger.commons.io.stream.StreamHelper;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.FopFactoryBuilder;
import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.Configuration;
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;
import com.helger.commons.io.stream.StreamHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ZUGFeRDVisualizer {
@@ -97,7 +79,7 @@ public class ZUGFeRDVisualizer {
private Templates mXsltXRTemplate = null;
private Templates mXsltUBLTemplate = null;
private Templates mXsltCIOTemplate = null;
private Templates mXsltHTMLTemplate = null;
private Map<Language, Templates> mXsltHTMLTemplates = null;
private Templates mXsltPDFTemplate = null;
private Templates mXsltZF1HTMLTemplate = null;
@@ -107,123 +89,170 @@ public class ZUGFeRDVisualizer {
mFactory.setURIResolver(new ClasspathResourceURIResolver());
}
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")));
}
if (mXsltHTMLTemplate == null) {
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
}
if (mXsltZF1HTMLTemplate == null) {
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
}
} catch (TransformerConfigurationException ex) {
LOGGER.error("Failed to init XSLT templates", ex);
}
/**
* *
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
* http://www.ferd-net.de/upload/Dokumente/FACTUR-X_ZUGFeRD_2p0_Teil1_Profil_EN16931_1p03.pdf
* http://countwordsfree.com/xmlviewer
*/
FileInputStream fis = new FileInputStream(xmlFilename);
String fileContent = "";
try {
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
} catch (IOException e2) {
LOGGER.error("Failed to read file content", e2);
}
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
/***
* returns which standard is used, CII or UBL
* @param fis inputstream (will be consumed)
* @return (facturx = cii)
*/
private EStandard findOutStandardFromRootNode(InputStream fis)
throws ParserConfigurationException {
String zf1Signature = "CrossIndustryDocument";
String zf2Signature = "CrossIndustryInvoice";
String ublSignature = "Invoice";
String ublCreditNoteSignature = "CreditNote";
String cioSignature = "SCRDMCCBDACIOMessageStructure" +
"";
boolean doPostProcessing = false;
String cioSignature = "SCRDMCCBDACIOMessageStructure";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
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)) {
applyZF1XSLT(fis, baos);
} else if (root.getLocalName().equals(zf2Signature)) {
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 IOException, TransformerException, ParserConfigurationException {
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
return visualize(fis, lang);
}
}
public String visualize(InputStream inputXml, Language lang)
throws IOException, TransformerException, ParserConfigurationException {
initTemplates(lang);
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
EStandard thestandard = findOutStandardFromRootNode(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream htmlOutput = new ByteArrayOutputStream();
ByteArrayInputStream xmlContentStream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8));
if (thestandard == EStandard.zugferd) {
applyZF1XSLT(xmlContentStream, htmlOutput);
return htmlOutput.toString(StandardCharsets.UTF_8);
} else if (thestandard == EStandard.facturx) {
//zf2 or fx
applyZF2XSLT(fis, iaos);
doPostProcessing = true;
} else if (root.getLocalName().equals(ublSignature)) {
applyZF2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.ubl) {
//zf2 or fx
applyUBL2XSLT(fis, iaos);
doPostProcessing = true;
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
applyUBL2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.ubl_creditnote) {
//zf2 or fx
applyUBLCreditNote2XSLT(fis, iaos);
doPostProcessing = true;
} else if (root.getLocalName().equals(cioSignature)) {
applyUBLCreditNote2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.orderx) {
//zf2 or fx
applyCIO2XSLT(fis, iaos);
doPostProcessing = true;
applyCIO2XSLT(xmlContentStream, htmlOutput);
} else {
throw new IllegalArgumentException("File does not look like CII or UBL");
}
if (doPostProcessing) {
// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
PipedOutputStream out;
try {
out = new PipedOutputStream(in);
new Thread(new Runnable() {
public void run() {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
iaos.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
}
}
}).start();
applyXSLTToHTML(in, baos);
} catch (IOException e1) {
LOGGER.error("Failed to create HTML", e1);
}
Optional<InputStream> in = copyStream(htmlOutput);
ByteArrayOutputStream htmlOutStream = new ByteArrayOutputStream();
if (in.isPresent()) {
applyXSLTToHTML(in.get(), htmlOutStream, lang);
}
return baos.toString(StandardCharsets.UTF_8);
return htmlOutStream.toString(StandardCharsets.UTF_8);
}
/**
* TODO: jstaerk: why not copy with that simple call: new ByteArrayInputStream(byteArrayOutputStream.toByteArray()) ?
*/
private Optional<InputStream> copyStream(ByteArrayOutputStream byteArrayOutputStream) {
// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
try {
PipedOutputStream out = new PipedOutputStream(in);
new Thread(() -> {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
byteArrayOutputStream.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
}
}).start();
} catch (IOException e1) {
LOGGER.error("Failed to create HTML", e1);
return Optional.empty();
}
return Optional.of(in);
}
private void initTemplates(Language lang) throws TransformerConfigurationException {
if (mXsltXRTemplate == null) {
mXsltXRTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
}
if (mXsltHTMLTemplates == null) {
mXsltHTMLTemplates = new HashMap<>();
if (mXsltHTMLTemplates.get(lang) == null) {
Templates mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
mXsltHTMLTemplates.put(lang, mXsltHTMLTemplate);
}
}
if (mXsltZF1HTMLTemplate == null) {
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
}
}
protected String toFOP(String xmlFilename)
throws FileNotFoundException, TransformerException {
throws IOException, TransformerException, ParserConfigurationException {
EStandard theStandard;
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
theStandard = findOutStandardFromRootNode(fis);
}
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
return toFOP(fis, theStandard);
}
}
protected String toFOP(InputStream is, EStandard theStandard)
throws TransformerException, IOException {
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")));
@@ -232,58 +261,86 @@ 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);
PipedInputStream in = new PipedInputStream();
PipedOutputStream out;
try {
out = new PipedOutputStream(in);
new Thread(new Runnable() {
public void run() {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
iaos.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
}
}
}).start();
applyXSLTToPDF(in, baos);
} catch (IOException e1) {
LOGGER.error("Failed to create PDF", e1);
if (theStandard == EStandard.facturx) {
applyZF2XSLT(is, iaos);
} else if (theStandard == EStandard.ubl) {
applyUBL2XSLT(is, iaos);
} else if (theStandard == EStandard.ubl_creditnote) {
applyUBLCreditNote2XSLT(is, iaos);
}
Optional<InputStream> in = copyStream(iaos);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (in.isPresent()) {
applyXSLTToPDF(in.get(), baos);
}
return baos.toString(StandardCharsets.UTF_8);
}
public void toPDF(String xmlFilename, String pdfFilename) {
// the writing part
File CIIinputFile = new File(xmlFilename);
File XMLinputFile = new File(xmlFilename);
String result = null;
String fopInput = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
result = this.toFOP(CIIinputFile.getAbsolutePath());
} catch (FileNotFoundException | TransformerException e) {
fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
toPDFfromFOP(fopInput, () -> {
try {
return new FileOutputStream(pdfFilename);
} catch (FileNotFoundException e) {
LOGGER.error("Failed to create PDF", e);
}
return null;
}, (OutputStream out) -> {});
}
public byte[] toPDF(String xmlContent) {
String fopInput = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
ByteArrayInputStream fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));
EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
fopInput = toFOP(fis, theStandard);
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
AtomicReference<byte[]> byteHolder = new AtomicReference<>();
ByteArrayOutputStream os = new ByteArrayOutputStream();
toPDFfromFOP(fopInput, () -> new BufferedOutputStream(os), (OutputStream out) -> {
try {
out.flush();
} catch (IOException e) {
LOGGER.error("Failed to create PDF", e);
}
byteHolder.set(os.toByteArray());
});
return byteHolder.get();
}
private void toPDFfromFOP(String fopInput, Supplier<OutputStream> outputStreamDelegate, Consumer<OutputStream> consumerDelegate) {
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = null;
@@ -311,40 +368,46 @@ public class ZUGFeRDVisualizer {
// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
try (OutputStream out = new BufferedOutputStream(outputStreamDelegate.get())) {
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
// Step 4: Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = factory.newTransformer(); // identity transformer
// Step 5: Setup input and output for XSLT transformation
// Setup input stream
Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
Source src = new StreamSource(new ByteArrayInputStream(fopInput.getBytes(StandardCharsets.UTF_8)));
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res);
//Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8));
consumerDelegate.accept(out);
} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
}
}
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
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));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream htmlOutstream)
throws TransformerException {
if (mXsltCIOTemplate == null) {
mXsltCIOTemplate = mFactory.newTemplates(
@@ -352,10 +415,10 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltCIOTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutstream));
}
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates(
@@ -363,10 +426,10 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltUBLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates(
@@ -374,28 +437,30 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltUBLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
Transformer transformer = mXsltZF1HTMLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException {
Transformer transformer = mXsltHTMLTemplate.newTransformer();
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream htmlOutStream, Language lang)
throws TransformerException, IOException {
Transformer transformer = mXsltHTMLTemplates.get(lang).newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
xmlFile.close();
}
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
throws TransformerException {
throws TransformerException, IOException {
Transformer transformer = mXsltPDFTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
xmlFile.close();
}
private static class ClasspathResourceURIResolver implements URIResolver {
@@ -404,7 +469,7 @@ public class ZUGFeRDVisualizer {
}
@Override
public Source resolve(String href, String base) throws TransformerException {
public Source resolve(String href, String base) {
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
}
}

View File

@@ -1,21 +1,23 @@
/** **********************************************************************
*
/**
* *********************************************************************
* <p>
* Copyright 2018 Jochen Staerk
*
* <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.model;
import java.util.Set;
@@ -29,6 +31,7 @@ public class TaxCategoryCodeTypeConstants {
public static final String ZEROTAXPRODUCTS = "Z";
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 final String FREEEXPORT = "G";
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT, FREEEXPORT, UNTAXEDSERVICE).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,151 @@
/**
* *********************************************************************
* <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->{
try {
return new BigDecimal(s.trim());
} catch (NumberFormatException e) {
return null;
}
});
}
/**
* 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();
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,37 +1,57 @@
<fop version="1.0">
<!-- Strict user configuration -->
<strict-configuration>true</strict-configuration>
<!-- Strict FO validation -->
<strict-validation>true</strict-validation>
<!-- Base URL for resolving relative URLs -->
<!-- Font Base URL for resolving relative font URLs -->
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
<source-resolution>96</source-resolution>
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
<target-resolution>96</target-resolution>
<!-- default page-height and page-width, in case
value is specified as auto -->
<default-page-settings height="297mm" width="210mm"/><!-- DIN A/4 -->
<!-- etc. etc..... -->
<renderers>
<renderer mime="application/pdf">
<fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
<auto-detect/>
<font kerning="no" embed-url="classpath:FreeSans.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="normal" />
<font-triplet name="Times-Bold" style="normal" weight="normal" />
</font>
</fonts>
<output-profile>classpath:AdobeCompat-v2.icc</output-profile>
</renderer>
</renderers>
</fop>
<fop version="1.0">
<!-- Strict user configuration -->
<strict-configuration>true</strict-configuration>
<!-- Strict FO validation -->
<strict-validation>true</strict-validation>
<!-- Base URL for resolving relative URLs -->
<!-- Font Base URL for resolving relative font URLs -->
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
<source-resolution>96</source-resolution>
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
<target-resolution>96</target-resolution>
<!-- default page-height and page-width, in case
value is specified as auto -->
<default-page-settings height="297mm" width="210mm"/><!-- DIN A/4 -->
<!-- etc. etc..... -->
<renderers>
<renderer mime="application/pdf">
<fonts>
<!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
<auto-detect/>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Regular.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="normal" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-It.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="italic" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Bold.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="normal" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-BoldIt.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="italic" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Regular.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-It.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="italic" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Bold.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-BoldIt.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="italic" weight="700"/>
</font>
</fonts>
<output-profile>classpath:AdobeCompat-v2.icc</output-profile>
</renderer>
</renderers>
</fop>

View File

@@ -1,33 +1,37 @@
<configuration>
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
<then>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Daily rollover -->
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
<!-- Keep 5 years worth of history -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</then>
</if>
<!-- Configure so that it outputs to both console and log file -->
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDERR"/>
</root>
</configuration>
<configuration>
<appender name="STDERR"
class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
<then>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Daily rollover -->
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
<!-- Keep 5 years worth of history -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</then>
</if>
<!-- Configure so that it outputs to both console and log file -->
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
<then>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</then>
</if>
<root level="INFO">
<appender-ref ref="STDERR"/>
</root>
</configuration>

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

@@ -0,0 +1,377 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<!-- Imports -->
<xsl:import href="common-xr.xsl"/>
<xsl:import href="xr-pdf/lib/konstanten.xsl"/>
<!--
FO engine used can be specified.
Specific extensions will be then enabled.
Supported values are:
axf - Antenna House XSL Formatter
fop - Apache FOP
-->
<xsl:param name="foengine"/>
<xsl:param name="axf.extensions"
select="if ($foengine eq 'axf') then true() else false()"/>
<xsl:param name="fop.extensions"
select="if ($foengine eq 'fop') then true() else false()"/>
<xsl:variable name="xml_result_color">
<xsl:if test="/validation/xml/summary/@status = 'valid'">
<xsl:text>green</xsl:text>
</xsl:if>
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
<xsl:text>red</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="xml_result_text">
<xsl:if test="/validation/xml/summary/@status = 'valid'">
<xsl:text>Das XML ist valide.</xsl:text>
</xsl:if>
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
<xsl:text>Das XML ist nicht valide.</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="pdf_result_color">
<xsl:if test="/validation/pdf/summary/@status = 'valid'">
<xsl:text>green</xsl:text>
</xsl:if>
<xsl:if test="/validation/pdf/summary/@status = 'invalid'">
<xsl:text>red</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="pdf_result_text">
<xsl:if test="/validation/pdf/summary/@status = 'valid'">
<xsl:text>Das ZUGFeRD-PDF ist valide.</xsl:text>
</xsl:if>
<xsl:if test="/validation/pdf/summary/@status = 'invalid'">
<xsl:text>Das ZUGFeRD-PDF ist nicht valide.</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="result_color">
<xsl:if test="/validation/summary/@status = 'valid'">
<xsl:text>green</xsl:text>
</xsl:if>
<xsl:if test="/validation/summary/@status = 'invalid'">
<xsl:text>red</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="result_text">
<xsl:if test="/validation/summary/@status = 'valid'">
<xsl:text>Es wird empfohlen, das Dokument anzunehmen und es weiterzuverarbeiten.</xsl:text>
</xsl:if>
<xsl:if test="/validation/summary/@status = 'invalid'">
<xsl:text>Es wird empfohlen, das Dokument zurückzuweisen.</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:template match="validation">
<fo:root language="{$lang}"
font-family="{$fontSans}">
<fo:layout-master-set>
<fo:simple-page-master master-name="DIN-A4"
page-height="297mm"
page-width="210mm">
<fo:region-body region-name="body"
margin="20mm 10mm 20mm 20mm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="DIN-A4">
<fo:flow flow-name="body">
<fo:block font-size="24px"
font-weight="bold">
<xsl:text>Prüfbericht</xsl:text>
</fo:block>
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="'Angaben zum geprüften Dokument'"/>
<xsl:with-param name="color"
select="'black'"/>
</xsl:call-template>
<fo:table>
<fo:table-column border-style="none"/>
<fo:table-column column-width="70%"
border-style="none"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block text-align="left">Referenz:</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="right">
<xsl:value-of select="./@filename"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block text-align="left">Zeitpunkt der Prüfung:</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="right">
<xsl:value-of select="./@datetime"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block text-align="left">Erkannter Dokumenttyp:</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="right">
<xsl:call-template name="EnableLineBreaks">
<xsl:with-param name="text"
select="./xml/info/profile"/>
<xsl:with-param name="separator"
select="'#'"/>
</xsl:call-template>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
<xsl:apply-templates select="./pdf"/>
<xsl:apply-templates select="./xml"/>
<!--
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select=="'Konformitätsprüfung:'"/>
<xsl:with-param name="color"
select=="'black'"/>
</xsl:call-template>
-->
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="concat('Bewertung: ', $result_text)"/>
<xsl:with-param name="color"
select="$result_color"/>
</xsl:call-template>
<fo:block>Validierungsergebnisse im Detail:</fo:block>
<fo:table>
<fo:table-column border-style="solid"/>
<fo:table-column border-style="solid"/>
<fo:table-column border-style="solid"/>
<fo:table-column border-style="solid"
column-width="68%"/>
<fo:table-header>
<fo:table-row background-color="#E0E0E0"
font-weight="bold"
border-style="solid">
<fo:table-cell>
<fo:block margin="0mm"
padding="1mm">Type</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block margin="0mm"
padding="1mm">Code</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block margin="0mm"
padding="1mm">Schwere</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block margin="0mm"
padding="1mm">Text</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body page-break-after="auto"
page-break-before="auto"
page-break-inside="avoid">
<xsl:choose>
<xsl:when test="./pdf/messages|./xml/messages|./messages">
<xsl:apply-templates select="./pdf/messages|./xml/messages|./messages"/>
</xsl:when>
<xsl:otherwise>
<fo:table-row border-style="solid"
font-size="12px">
<fo:table-cell number-columns-spanned="4">
<fo:block margin="0mm"
padding="1mm"
text-align="center">Es gibt keine Hinweise, Warnungen oder Fehler.</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:otherwise>
</xsl:choose>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="notice|warning|error|exception|fatal">
<xsl:variable name="msg_color">
<xsl:choose>
<xsl:when test="name() = 'error' or name() = 'exception' or name() = 'fatal'">
<xsl:text>red</xsl:text>
</xsl:when>
<xsl:when test="name() = 'warning'">
<xsl:text>orange</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>black</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="msg_label">
<xsl:choose>
<xsl:when test="name() = 'error' or name() = 'exception' or name() = 'fatal'">
<xsl:text>Fehler</xsl:text>
</xsl:when>
<xsl:when test="name() = 'warning'">
<xsl:text>Warnung</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Hinweis</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="msg_text">
<xsl:choose>
<xsl:when test="contains(., ' [ID ')">
<xsl:value-of select="substring-before(., ' [ID')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="rows-spanned">
<xsl:choose>
<xsl:when test="./@location">
<xsl:value-of select="'2'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'1'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<fo:table-row border-style="solid"
font-size="12px"
keep-with-previous="auto"
keep-with-next="always">
<fo:table-cell number-rows-spanned="{$rows-spanned}">
<fo:block margin="0mm"
padding="1mm">
<xsl:value-of select="./@type"/>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block margin="0mm"
padding="1mm">
<xsl:value-of select="substring-before(substring-after(., ' [ID '), ']')"/>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block color="{$msg_color}"
margin="0mm"
padding="1mm">
<xsl:value-of select="$msg_label"/>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block color="{$msg_color}"
margin="0mm"
padding="1mm">
<xsl:call-template name="IntersperseWithZeroSpaces">
<xsl:with-param name="str" select="$msg_text"/>
</xsl:call-template>
</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:if test="./@location">
<fo:table-row border-style="solid"
font-size="8px"
keep-with-previous="always"
keep-with-next="auto">
<fo:table-cell number-columns-spanned="3">
<fo:block color="{$msg_color}"
margin="0mm"
padding="1mm">
<fo:block font-size="12px">
<xsl:value-of select="'Pfad: '"/>
</fo:block>
<xsl:call-template name="EnableLineBreaks">
<xsl:with-param name="text"
select="./@location"/>
<xsl:with-param name="separator"
select="'/'"/>
</xsl:call-template>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:if>
</xsl:template>
<xsl:template match="pdf">
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="concat('ZUGFeRD-PDF: ', $pdf_result_text)"/>
<xsl:with-param name="color"
select="$pdf_result_color"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="xml">
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="concat('E-Rechnung XML: ', $xml_result_text)"/>
<xsl:with-param name="color"
select="$xml_result_color"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="SubHeader">
<xsl:param name="text"/>
<xsl:param name="color"/>
<fo:block color="{$color}"
background-color="#E0E0E0"
margin-bottom="10px"
padding="3px"
font-weight="bold"
margin-top="10px">
<xsl:value-of select="$text"/>
</fo:block>
</xsl:template>
<xsl:template name="EnableLineBreaks">
<xsl:param name="text"/>
<xsl:param name="separator"/>
<xsl:for-each select="tokenize($text, $separator)">
<xsl:choose>
<xsl:when test="position() eq 1">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</xsl:when>
<xsl:otherwise>
<fo:block>
<xsl:value-of select="concat($separator, .)"/>
</fo:block>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template name="IntersperseWithZeroSpaces">
<xsl:param name="str"/>
<xsl:variable name="spacechars">
&#x9;&#xA;
&#x2000;&#x2001;&#x2002;&#x2003;&#x2004;&#x2005;
&#x2006;&#x2007;&#x2008;&#x2009;&#x200A;&#x200B;
</xsl:variable>
<xsl:if test="string-length($str) &gt; 0">
<xsl:variable name="c1" select="substring($str, 1, 1)"/>
<xsl:variable name="c2" select="substring($str, 2, 1)"/>
<xsl:value-of select="$c1"/>
<xsl:if test="$c2 != '' and
not(contains($spacechars, $c1) or
contains($spacechars, $c2))">
<xsl:text>&#x200B;</xsl:text>
</xsl:if>
<xsl:call-template name="IntersperseWithZeroSpaces">
<xsl:with-param name="str" select="substring($str, 2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@@ -789,7 +789,7 @@
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Deliver_to_location_identifier/@scheme_identifier">
<xsl:with-param name="field-mapping-identifier" select="'xr:Deliver_to_location_identifier/@scheme_identifier'"/>
</xsl:apply-templates>
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Actual_delivery_date">
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Actual_delivery_date">
<xsl:with-param name="value" select="format-date(xr:DELIVERY_INFORMATION/xr:Actual_delivery_date, xrf:_('date-format'))"/>
</xsl:apply-templates>
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Deliver_to_party_name"/>
@@ -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"/>

View File

@@ -398,7 +398,7 @@
<nummer>BT-64</nummer>
</xsl:when>
<xsl:when test="$identifier = 'xr:Tax_representative_address_line_2'">
<label>Postfach</label>
<label>Adresszusatz</label>
<nummer>BT-65</nummer>
</xsl:when>
<xsl:when test="$identifier = 'xr:Tax_representative_address_line_3'">
@@ -486,7 +486,7 @@
<nummer>BT-75</nummer>
</xsl:when>
<xsl:when test="$identifier = 'xr:Deliver_to_address_line_2'">
<label>Postfach</label>
<label>Adresszusatz</label>
<nummer>BT-76</nummer>
</xsl:when>
<xsl:when test="$identifier = 'xr:Deliver_to_address_line_3'">

View File

@@ -64,7 +64,7 @@
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title><xsl:value-of select="xr:Invoice_number"/></dc:title>
<dc:title><rdf:Alt><rdf:li xml:lang="x-default"><xsl:value-of select="xr:Invoice_number"/></rdf:li></rdf:Alt></dc:title>
<!--
<dc:creator></dc:creator>
<dc:description></dc:description>

View File

@@ -1,235 +1,235 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
xmlns:xr="urn:ce.eu:en16931:2017:xoev-de:kosit:standard:xrechnung-1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xrv="http://www.example.org/XRechnung-Viewer"
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions"
version="2.0">
<!-- ==========================================================================
== Schriften
=========================================================================== -->
<xsl:variable name="fontSans">SourceSerifPro</xsl:variable>
<xsl:variable name="fontSerif">SourceSerifPro</xsl:variable>
<xsl:variable name="amount-picture" select="xrf:_('amount-format')"/>
<xsl:variable name="percentage-picture" select="xrf:_('percentage-format')"/>
<!-- ==========================================================================
== Attribute-Sets
=========================================================================== -->
<!-- Fliesstext -->
<xsl:attribute-set name="fliesstext">
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="line-height">12pt</xsl:attribute>
<xsl:attribute name="hyphenation-ladder-count">3</xsl:attribute>
<xsl:attribute name="hyphenate">true</xsl:attribute>
<xsl:attribute name="language">de</xsl:attribute>
</xsl:attribute-set>
<!-- H1 Marker -->
<xsl:attribute-set name="marker">
<xsl:attribute name="font-size">18pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
<xsl:attribute name="hyphenate">false</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
</xsl:attribute-set>
<!-- H1 -->
<xsl:attribute-set name="h1">
<xsl:attribute name="font-size">18pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
<xsl:attribute name="hyphenate">false</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- H2-Container -->
<xsl:attribute-set name="h2-container">
<xsl:attribute name="border-top">0.5pt solid</xsl:attribute>
<xsl:attribute name="padding-top">2.5pt</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- H2 -->
<xsl:attribute-set name="h2">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="border">0.5pt solid</xsl:attribute>
<xsl:attribute name="padding">4pt 6pt</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
<xsl:attribute name="margin">0</xsl:attribute>
</xsl:attribute-set>
<!-- H3 -->
<xsl:attribute-set name="h3">
<xsl:attribute name="font-size">10pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="margin-bottom">1mm</xsl:attribute>
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="margin-top">2mm</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Separator -->
<xsl:attribute-set name="separator">
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="margin-bottom">3mm</xsl:attribute>
<xsl:attribute name="border-bottom">0.5pt dotted</xsl:attribute>
<xsl:attribute name="color">#999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Wert-Felder -->
<xsl:variable name="wertHG">#eeeeee</xsl:variable>
<xsl:variable name="wert-legende-breite">28</xsl:variable>
<xsl:attribute-set name="wert-legende">
<xsl:attribute name="font-size">7pt</xsl:attribute>
<xsl:attribute name="line-height">10pt</xsl:attribute>
<xsl:attribute name="padding-top">1mm</xsl:attribute>
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="wert-ausgabe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="line-height">10pt</xsl:attribute>
<xsl:attribute name="background-color"><xsl:value-of select="$wertHG"/></xsl:attribute>
<xsl:attribute name="padding-top">1mm</xsl:attribute>
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
<xsl:attribute name="padding-left">2mm</xsl:attribute>
<xsl:attribute name="padding-right">2mm</xsl:attribute>
<xsl:attribute name="keep-together.within-column">always</xsl:attribute>
</xsl:attribute-set>
<!-- Rechnung-Felder -->
<xsl:attribute-set name="rechnung-legende">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-steuer">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-wert">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">right</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">0mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-legende-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-steuer-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-wert-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="text-align">right</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">0mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Box-Container -->
<xsl:attribute-set name="box-container-kapitel">
<xsl:attribute name="margin-bottom">10mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="box-container-bereich">
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
<xsl:attribute name="keep-together.within-page">always</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="box-container-inner">
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
</xsl:attribute-set>
<!-- Tabular invoice lines -->
<xsl:attribute-set name="invoicelines-table">
<xsl:attribute name="padding-left">2pt</xsl:attribute>
<xsl:attribute name="padding-right">2pt</xsl:attribute>
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:attribute name="table-layout">fixed</xsl:attribute>
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-table-header">
<xsl:attribute name="font-weight">bold</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-table-row">
<!-- Nested sub-invoice lines will recursively decrease font-size -->
<xsl:attribute name="font-size" select="if (self::xr:SUB_INVOICE_LINE) then '90%' else '100%'"/>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-nested-info">
<xsl:attribute name="font-size">80%</xsl:attribute>
<xsl:attribute name="font-style">italic</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-allowances-table">
<xsl:attribute name="padding-left">2pt</xsl:attribute>
<xsl:attribute name="padding-right">2pt</xsl:attribute>
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:attribute name="table-layout">fixed</xsl:attribute>
<xsl:attribute name="font-size">80%</xsl:attribute>
<xsl:attribute name="font-style">italic</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
xmlns:xr="urn:ce.eu:en16931:2017:xoev-de:kosit:standard:xrechnung-1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xrv="http://www.example.org/XRechnung-Viewer"
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions"
version="2.0">
<!-- ==========================================================================
== Schriften
=========================================================================== -->
<xsl:variable name="fontSans">SourceSansPro</xsl:variable>
<xsl:variable name="fontSerif">SourceSerifPro</xsl:variable>
<xsl:variable name="amount-picture" select="xrf:_('amount-format')"/>
<xsl:variable name="percentage-picture" select="xrf:_('percentage-format')"/>
<!-- ==========================================================================
== Attribute-Sets
=========================================================================== -->
<!-- Fliesstext -->
<xsl:attribute-set name="fliesstext">
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="line-height">12pt</xsl:attribute>
<xsl:attribute name="hyphenation-ladder-count">3</xsl:attribute>
<xsl:attribute name="hyphenate">true</xsl:attribute>
<xsl:attribute name="language">de</xsl:attribute>
</xsl:attribute-set>
<!-- H1 Marker -->
<xsl:attribute-set name="marker">
<xsl:attribute name="font-size">18pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
<xsl:attribute name="hyphenate">false</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
</xsl:attribute-set>
<!-- H1 -->
<xsl:attribute-set name="h1">
<xsl:attribute name="font-size">18pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
<xsl:attribute name="hyphenate">false</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- H2-Container -->
<xsl:attribute-set name="h2-container">
<xsl:attribute name="border-top">0.5pt solid</xsl:attribute>
<xsl:attribute name="padding-top">2.5pt</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- H2 -->
<xsl:attribute-set name="h2">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="border">0.5pt solid</xsl:attribute>
<xsl:attribute name="padding">4pt 6pt</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
<xsl:attribute name="margin">0</xsl:attribute>
</xsl:attribute-set>
<!-- H3 -->
<xsl:attribute-set name="h3">
<xsl:attribute name="font-size">10pt</xsl:attribute>
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="margin-bottom">1mm</xsl:attribute>
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="margin-top">2mm</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Separator -->
<xsl:attribute-set name="separator">
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="margin-bottom">3mm</xsl:attribute>
<xsl:attribute name="border-bottom">0.5pt dotted</xsl:attribute>
<xsl:attribute name="color">#999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Wert-Felder -->
<xsl:variable name="wertHG">#eeeeee</xsl:variable>
<xsl:variable name="wert-legende-breite">28</xsl:variable>
<xsl:attribute-set name="wert-legende">
<xsl:attribute name="font-size">7pt</xsl:attribute>
<xsl:attribute name="line-height">10pt</xsl:attribute>
<xsl:attribute name="padding-top">1mm</xsl:attribute>
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
<xsl:attribute name="margin-left">2mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="wert-ausgabe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="line-height">10pt</xsl:attribute>
<xsl:attribute name="background-color"><xsl:value-of select="$wertHG"/></xsl:attribute>
<xsl:attribute name="padding-top">1mm</xsl:attribute>
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
<xsl:attribute name="padding-left">2mm</xsl:attribute>
<xsl:attribute name="padding-right">2mm</xsl:attribute>
<xsl:attribute name="keep-together.within-column">always</xsl:attribute>
</xsl:attribute-set>
<!-- Rechnung-Felder -->
<xsl:attribute-set name="rechnung-legende">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-steuer">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-wert">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">right</xsl:attribute>
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">0mm</xsl:attribute>
<xsl:attribute name="margin-left">0mm</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-legende-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-steuer-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="text-align">left</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">1mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="rechnung-wert-summe">
<xsl:attribute name="font-size">9pt</xsl:attribute>
<xsl:attribute name="font-weight">bold</xsl:attribute>
<xsl:attribute name="text-align">right</xsl:attribute>
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
<xsl:attribute name="padding-left">0mm</xsl:attribute>
<xsl:attribute name="padding-right">0mm</xsl:attribute>
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
</xsl:attribute-set>
<!-- Box-Container -->
<xsl:attribute-set name="box-container-kapitel">
<xsl:attribute name="margin-bottom">10mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="box-container-bereich">
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
<xsl:attribute name="keep-together.within-page">always</xsl:attribute>
<xsl:attribute name="span">all</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="box-container-inner">
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
</xsl:attribute-set>
<!-- Tabular invoice lines -->
<xsl:attribute-set name="invoicelines-table">
<xsl:attribute name="padding-left">2pt</xsl:attribute>
<xsl:attribute name="padding-right">2pt</xsl:attribute>
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:attribute name="table-layout">fixed</xsl:attribute>
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-table-header">
<xsl:attribute name="font-weight">bold</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-table-row">
<!-- Nested sub-invoice lines will recursively decrease font-size -->
<xsl:attribute name="font-size" select="if (self::xr:SUB_INVOICE_LINE) then '90%' else '100%'"/>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-nested-info">
<xsl:attribute name="font-size">80%</xsl:attribute>
<xsl:attribute name="font-style">italic</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="invoicelines-allowances-table">
<xsl:attribute name="padding-left">2pt</xsl:attribute>
<xsl:attribute name="padding-right">2pt</xsl:attribute>
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:attribute name="table-layout">fixed</xsl:attribute>
<xsl:attribute name="font-size">80%</xsl:attribute>
<xsl:attribute name="font-style">italic</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>

View File

@@ -1193,7 +1193,7 @@ function downloadData (element_id) {
<div id="BT-50" title="BT-50" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach (BT-51):</div>
<div class="boxdaten legende">Adresszusatz (BT-51):</div>
<div id="BT-51" title="BT-51" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -1258,7 +1258,7 @@ function downloadData (element_id) {
<div id="BT-35" title="BT-35" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach (BT-36):</div>
<div class="boxdaten legende">Adresszusatz (BT-36):</div>
<div id="BT-36" title="BT-36" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -1998,7 +1998,7 @@ function downloadData (element_id) {
<div id="BT-64" title="BT-64" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach (BT-65):</div>
<div class="boxdaten legende">Adresszusatz (BT-65):</div>
<div id="BT-65" title="BT-65" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -2111,7 +2111,7 @@ function downloadData (element_id) {
<div id="BT-75" title="BT-75" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach (BT-76):</div>
<div class="boxdaten legende">Adresszusatz (BT-76):</div>
<div id="BT-76" title="BT-76" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_2"/></div>
</div>
<div class="boxzeile">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -122,7 +122,7 @@
<div title="BT-50" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach:</div>
<div class="boxdaten legende">Adresszusatz:</div>
<div title="BT-51" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -179,7 +179,7 @@
<div title="BT-35" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach:</div>
<div class="boxdaten legende">Adresszusatz:</div>
<div title="BT-36" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -892,7 +892,7 @@
<div title="BT-64" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach:</div>
<div class="boxdaten legende">Adresszusatz:</div>
<div title="BT-65" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_2"/></div>
</div>
<div class="boxzeile">
@@ -998,7 +998,7 @@
<div title="BT-75" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_1"/></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Postfach:</div>
<div class="boxdaten legende">Adresszusatz:</div>
<div title="BT-76" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_2"/></div>
</div>
<div class="boxzeile">