Merge branch 'master' into patch-1
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/***
|
||||
* (absolute) allowances on item and document level
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Allowance extends Charge {
|
||||
|
||||
/***
|
||||
@@ -28,6 +34,7 @@ public class Allowance extends Charge {
|
||||
* @return false since its not supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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;
|
||||
|
||||
public void calculate() {
|
||||
TransactionCalculator 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;
|
||||
}
|
||||
public CalculatedInvoice setGrandTotal(BigDecimal grand) {
|
||||
grandTotal=grand;
|
||||
return this;
|
||||
}
|
||||
public BigDecimal getTaxBasis() {
|
||||
if (taxBasis==null) {
|
||||
calculate();
|
||||
}
|
||||
return taxBasis;
|
||||
}
|
||||
public CalculatedInvoice setTaxBasis(BigDecimal basis) {
|
||||
taxBasis=basis;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getDuePayable() {
|
||||
if (duePayable==null) {
|
||||
calculate();
|
||||
}
|
||||
return duePayable;
|
||||
}
|
||||
public CalculatedInvoice setDuePayable(BigDecimal due) {
|
||||
duePayable=due;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getLineTotalAmount() {
|
||||
if (lineTotalAmount==null) {
|
||||
calculate();
|
||||
}
|
||||
return lineTotalAmount;
|
||||
}
|
||||
public CalculatedInvoice setLineTotalAmount(BigDecimal total) {
|
||||
lineTotalAmount=total;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDCashDiscount;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -7,6 +9,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,6 +35,31 @@ 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
|
||||
*/
|
||||
|
||||
@@ -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.IAbsoluteValueProvider;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
|
||||
@@ -8,6 +11,8 @@ import java.math.BigDecimal;
|
||||
/***
|
||||
* Absolute and relative charges for document and item level
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
|
||||
/**
|
||||
@@ -151,6 +156,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
* @return true since it is supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -20,15 +20,19 @@
|
||||
*/
|
||||
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 final String listID;
|
||||
private final String code;
|
||||
private String listID;
|
||||
private String code;
|
||||
private String listVersionID;
|
||||
|
||||
/**
|
||||
@@ -54,6 +58,13 @@ public class ClassCode {
|
||||
this(listID, code, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* bean constructor
|
||||
*/
|
||||
public ClassCode() {
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Set the version for the scheme returned by {@link #getListID()}
|
||||
* @param listVersionID the scheme version
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,14 +20,18 @@
|
||||
*/
|
||||
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 final ClassCode classCode;
|
||||
private ClassCode classCode;
|
||||
private String className;
|
||||
|
||||
/**
|
||||
@@ -50,6 +54,13 @@ public class DesignatedProductClassification implements IDesignatedProductClassi
|
||||
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;
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* bean constructor
|
||||
*/
|
||||
public DirectDebit() {
|
||||
this.IBAN = "";
|
||||
this.mandate = "";
|
||||
}
|
||||
|
||||
/***
|
||||
* constructor for normal use :-)
|
||||
@@ -35,8 +47,18 @@ 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;
|
||||
}
|
||||
|
||||
public DirectDebit setMandate(String mandate) {
|
||||
this.mandate = mandate;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
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;
|
||||
@@ -8,12 +13,20 @@ public class FileAttachment {
|
||||
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 String getDescription() {
|
||||
@@ -60,8 +73,4 @@ public class FileAttachment {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,9 +35,10 @@ 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, payee = null;
|
||||
protected ArrayList<CashDiscount> cashDiscounts = null;
|
||||
@@ -58,12 +57,18 @@ public class Invoice implements IExportableTransaction {
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
|
||||
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
|
||||
protected IZUGFeRDPaymentTerms paymentTerms = null;
|
||||
|
||||
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;
|
||||
|
||||
public Invoice() {
|
||||
ZFItems = new ArrayList<>();
|
||||
@@ -113,6 +118,16 @@ 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) {
|
||||
xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDCashDiscount[] getCashDiscounts() {
|
||||
return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
|
||||
@@ -138,6 +153,7 @@ public class Invoice implements IExportableTransaction {
|
||||
*/
|
||||
public Invoice setCorrection(String number) {
|
||||
setInvoiceReferencedDocumentID(number);
|
||||
addInvoiceReferencedDocument(new ReferencedDocument(number));
|
||||
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||
return this;
|
||||
}
|
||||
@@ -410,6 +426,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 +486,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 +552,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 +581,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 +605,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() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -11,13 +12,18 @@ 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 = BigDecimal.ZERO;
|
||||
protected BigDecimal quantity;
|
||||
@@ -35,6 +41,8 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
protected ArrayList<ReferencedDocument> additionalReference = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
||||
protected List<IncludedNote> includedNotes = null;
|
||||
//protected HashMap<String, String> attributes = new HashMap<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -62,11 +70,39 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
setProduct(new Product());
|
||||
|
||||
// Bharti's homework 20241126:Streams https://www.youtube.com/watch?v=Lf01cBzmuXw
|
||||
// and Lambdas https://www.youtube.com/watch?v=HCyx31NW8xg
|
||||
setProduct(new Product(itemMap.getNode("Item").get()));
|
||||
icnm.getAsString("Name").ifPresent(product::setName);
|
||||
icnm.getAsString("Description").ifPresent(product::setDescription);
|
||||
|
||||
icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> {
|
||||
SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID);
|
||||
});
|
||||
|
||||
icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> {
|
||||
BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID);
|
||||
});
|
||||
|
||||
// String name = icnm.getAsStringOrNull("Name");
|
||||
// String val = icnm.getAsStringOrNull("Value");
|
||||
// if (name != null && val != null) {
|
||||
// if (attributes == null) {
|
||||
// attributes = new HashMap<>();
|
||||
// }
|
||||
// product.attributes.put(name, val);
|
||||
// }
|
||||
//icnm.getNode("AdditionalItemProperty").flatMap(n ->n.getAttributes()).ifPresent(product::setAttributes);
|
||||
|
||||
|
||||
|
||||
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
});
|
||||
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> {
|
||||
icnm.getAsString("LineID").ifPresent(this::setId);
|
||||
});
|
||||
|
||||
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
||||
// ubl
|
||||
@@ -81,21 +117,43 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
|
||||
});
|
||||
|
||||
itemMap.getAllNodes("DocumentReference").map(ReferencedDocument::fromNode)
|
||||
.forEach(this::addAdditionalReference);
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
|
||||
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
|
||||
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode)
|
||||
.forEach(this::addReferencedDocument);
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).
|
||||
forEach(this::addReferencedDocument);
|
||||
});
|
||||
|
||||
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);
|
||||
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")
|
||||
@@ -123,6 +181,37 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
boolean foundCode = false;
|
||||
for (SubjectCode code : SubjectCode.values()) {
|
||||
if (code.toString().equals(subjectCode)) {
|
||||
includedNotes.add(new IncludedNote(content, code));
|
||||
foundCode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundCode) {
|
||||
includedNotes.add(new IncludedNote(content, null));
|
||||
}
|
||||
});
|
||||
addNotes(includedNotes);
|
||||
});
|
||||
}
|
||||
|
||||
public Item addReferencedLineID(String s) {
|
||||
@@ -143,6 +232,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
|
||||
*
|
||||
@@ -258,7 +352,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
|
||||
*/
|
||||
@@ -269,7 +363,7 @@ 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
|
||||
*/
|
||||
@@ -288,6 +382,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;
|
||||
}
|
||||
|
||||
@@ -314,7 +428,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
|
||||
*/
|
||||
@@ -333,8 +447,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
|
||||
@@ -370,4 +484,19 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
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;
|
||||
@@ -17,17 +20,20 @@ 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 taxExemptionReason=null;
|
||||
protected String taxCategoryCode=null;
|
||||
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 = new HashMap<>();
|
||||
|
||||
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
|
||||
|
||||
/***
|
||||
@@ -56,11 +62,13 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
|
||||
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
@@ -72,6 +80,26 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//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 ->
|
||||
@@ -112,6 +140,7 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. intra-commnunity supply or small business
|
||||
@@ -126,7 +155,7 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
* @param taxExemptionReasonText String e.g. Kleinunternehmer gemäß §19 UStG https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxExemptionReason(String taxExemptionReasonText) {
|
||||
public Product setTaxExemptionReason(String taxExemptionReasonText) {
|
||||
taxExemptionReason = taxExemptionReasonText;
|
||||
return this;
|
||||
}
|
||||
@@ -148,7 +177,7 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
* @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) {
|
||||
public Product setTaxCategoryCode(String code) {
|
||||
taxCategoryCode = code;
|
||||
return this;
|
||||
}
|
||||
@@ -275,18 +304,22 @@ 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
|
||||
@@ -299,16 +332,16 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
}
|
||||
|
||||
public Product setAttributes(Map<String, String> attributes) {
|
||||
this.attributes.clear();
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addAttribute(String name, String value ) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
public Product addAttribute(String name, String value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -334,6 +367,23 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
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
|
||||
*
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
package org.mustangproject;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -46,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;
|
||||
@@ -61,13 +89,20 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
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"),
|
||||
nodes.getAsStringOrNull("TypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"));
|
||||
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
|
||||
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"),
|
||||
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -36,5 +36,9 @@ public enum SubjectCode {
|
||||
/**
|
||||
* Discount and bonus agreements
|
||||
*/
|
||||
AAK
|
||||
AAK,
|
||||
/**
|
||||
* Vehicle licence number
|
||||
*/
|
||||
ABZ
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -15,13 +17,15 @@ 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;
|
||||
@@ -60,6 +64,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected void parseFromUBL(NodeList nodes) {
|
||||
if (nodes.getLength() > 0) {
|
||||
|
||||
@@ -69,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) {
|
||||
@@ -88,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();
|
||||
@@ -148,8 +262,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("Name")) {
|
||||
setName(postal.item(postalChildIndex).getTextContent());
|
||||
@@ -167,7 +279,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (currentUBLChild.equals("GlobalID")) {
|
||||
@@ -181,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,6 +561,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
*
|
||||
@@ -511,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;
|
||||
@@ -670,6 +741,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return contact;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
|
||||
if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@@ -4,9 +4,13 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
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;
|
||||
|
||||
public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
@@ -39,10 +43,56 @@ 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
|
||||
* @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
|
||||
@@ -60,7 +110,39 @@ public class XMLTools extends XMLWriter {
|
||||
}
|
||||
|
||||
|
||||
public static String encodeXML(CharSequence s) {
|
||||
/***
|
||||
* 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) {
|
||||
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
|
||||
try {
|
||||
return formatter.parse(toParse);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* relplaces some entities like < , > and & with their escaped pendant like &lt;
|
||||
* @param s the string
|
||||
* @return the "safe" string
|
||||
*/
|
||||
public static String encodeXML(CharSequence s) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
}
|
||||
@@ -132,4 +214,16 @@ public class XMLTools extends XMLWriter {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/***
|
||||
@@ -321,6 +323,16 @@ public interface IExportableTransaction {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the rounding amount
|
||||
* (only to be usef for NL whose currency requires a rounding to 5ct)
|
||||
*
|
||||
* @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.
|
||||
@@ -415,13 +427,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
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -153,4 +155,21 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,30 @@ 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(), 18, RoundingMode.HALF_UP)
|
||||
|
||||
BigDecimal quantity=BigDecimal.ZERO;
|
||||
if ((currentItem!=null)&&(currentItem.getQuantity()!=null)) {
|
||||
quantity=currentItem.getQuantity();
|
||||
}
|
||||
|
||||
// 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(getPrice()).divide(basisQuantity, 18, RoundingMode.HALF_UP)
|
||||
.subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
|
||||
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
|
||||
@@ -459,6 +459,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (var 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>"
|
||||
|
||||
@@ -4,13 +4,16 @@ 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.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 +30,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() {
|
||||
@@ -41,19 +44,19 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
/***
|
||||
* 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
|
||||
*/
|
||||
@@ -77,7 +80,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 +94,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 +122,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 +134,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,13 +167,17 @@ 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);
|
||||
String reasonText=currentItem.getProduct().getTaxExemptionReason();
|
||||
if (reasonText!=null) {
|
||||
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
|
||||
String reasonText = currentItem.getProduct().getTaxExemptionReason();
|
||||
if (reasonText != null) {
|
||||
itemVATAmount.setVatExemptionReasonText(reasonText);
|
||||
}
|
||||
VATAmount current = hm.get(percent.stripTrailingZeros());
|
||||
@@ -193,8 +197,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));
|
||||
@@ -211,8 +215,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));
|
||||
@@ -239,4 +243,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
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.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;
|
||||
|
||||
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 void toPDF(String xmlLogfileContent, String pdfFilename) {
|
||||
|
||||
// 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).
|
||||
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
|
||||
|
||||
// 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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,11 +277,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
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>";
|
||||
}
|
||||
@@ -334,7 +334,7 @@ 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 = "";
|
||||
@@ -401,6 +401,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
int lineID = 0;
|
||||
for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
|
||||
lineID++;
|
||||
String lineIDStr = Integer.toString(lineID);
|
||||
if (currentItem.getId()!=null) {
|
||||
lineIDStr=currentItem.getId();
|
||||
}
|
||||
if (currentItem.getProduct().getTaxExemptionReason() != null) {
|
||||
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
|
||||
}
|
||||
@@ -408,7 +412,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
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>"
|
||||
|
||||
@@ -454,7 +458,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
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()) + "\"";
|
||||
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
|
||||
if (classification.getClassCode().getListVersionID() != null) {
|
||||
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
|
||||
}
|
||||
@@ -818,13 +822,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((trans.getPaymentTerms() == null) && (getProfile() != Profiles.getByName("Minimum")) && ((paymentTermsDescription != null) || (trans.getTradeSettlement() != null) || (hasDueDate))) {
|
||||
xml += "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
if (paymentTermsDescription != null) {
|
||||
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()) {
|
||||
@@ -834,12 +843,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();
|
||||
@@ -857,7 +860,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;
|
||||
@@ -866,14 +869,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>"
|
||||
@@ -886,6 +893,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (var 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"
|
||||
|
||||
@@ -83,8 +83,9 @@ 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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -299,21 +93,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 +115,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 +198,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 +231,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 +262,7 @@ public class ZUGFeRDImporter {
|
||||
* @return the Payment Terms
|
||||
*/
|
||||
public String getPaymentTerms() {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']//*[local-name() = 'Description']");
|
||||
return importedInvoice.getPaymentTermDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,16 +285,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -615,7 +343,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 +367,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 +375,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 +476,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 +499,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 +524,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
|
||||
*
|
||||
@@ -809,7 +535,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
nl = getNodeListByPath("//*[localname() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
} else {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
}
|
||||
@@ -929,9 +655,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 +681,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 +694,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 +765,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 +780,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 +867,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,42 +20,9 @@
|
||||
*/
|
||||
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 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 com.helger.commons.io.stream.StreamHelper;
|
||||
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;
|
||||
@@ -65,12 +32,19 @@ 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.Optional;
|
||||
|
||||
public class ZUGFeRDVisualizer {
|
||||
|
||||
@@ -113,7 +87,7 @@ public class ZUGFeRDVisualizer {
|
||||
* @param fis inputstream (will be consumed)
|
||||
* @return (facturx = cii)
|
||||
*/
|
||||
public EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
private EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
@@ -144,111 +118,108 @@ public class ZUGFeRDVisualizer {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
try {
|
||||
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
|
||||
*/
|
||||
public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException {
|
||||
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);
|
||||
}
|
||||
return visualize(fis, lang);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException {
|
||||
initTemplates(lang);
|
||||
|
||||
boolean doPostProcessing = false;
|
||||
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
|
||||
EStandard thestandard = findOutStandardFromRootNode(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream htmlOutput = new ByteArrayOutputStream();
|
||||
|
||||
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
EStandard thestandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
ByteArrayInputStream xmlContentStream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
if (thestandard == EStandard.zugferd) {
|
||||
applyZF1XSLT(fis, baos);
|
||||
applyZF1XSLT(xmlContentStream, htmlOutput);
|
||||
return htmlOutput.toString(StandardCharsets.UTF_8);
|
||||
} else if (thestandard == EStandard.facturx) {
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyZF2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl) {
|
||||
//zf2 or fx
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyUBL2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl_creditnote) {
|
||||
//zf2 or fx
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
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);
|
||||
}
|
||||
|
||||
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 (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")));
|
||||
}
|
||||
}
|
||||
|
||||
protected String toFOP(String xmlFilename)
|
||||
throws FileNotFoundException, TransformerException {
|
||||
throws IOException, TransformerException {
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename);//rewind :-(
|
||||
|
||||
return toFOP(fis, theStandard);
|
||||
}
|
||||
|
||||
protected String toFOP(InputStream is, EStandard theStandard)
|
||||
throws TransformerException, IOException {
|
||||
|
||||
try {
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
@@ -259,45 +230,22 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
//zf2 or fx
|
||||
if (theStandard == EStandard.facturx) {
|
||||
applyZF2XSLT(fis, iaos);
|
||||
applyZF2XSLT(is, iaos);
|
||||
} else if (theStandard == EStandard.ubl) {
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
applyUBL2XSLT(is, iaos);
|
||||
} else if (theStandard == EStandard.ubl_creditnote) {
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
applyUBLCreditNote2XSLT(is, 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);
|
||||
Optional<InputStream> in = copyStream(iaos);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (in.isPresent()) {
|
||||
applyXSLTToPDF(in.get(), baos);
|
||||
}
|
||||
|
||||
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@@ -313,7 +261,7 @@ public class ZUGFeRDVisualizer {
|
||||
*/
|
||||
try {
|
||||
result = this.toFOP(XMLinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException | TransformerException e) {
|
||||
} catch (TransformerException | IOException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
@@ -367,7 +315,7 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -376,10 +324,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
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(
|
||||
@@ -387,10 +335,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(
|
||||
@@ -398,10 +346,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(
|
||||
@@ -409,28 +357,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 {
|
||||
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException, IOException {
|
||||
Transformer transformer = mXsltHTMLTemplate.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 {
|
||||
@@ -439,7 +389,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,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, TAXEXEMPT).collect(Collectors.toSet());
|
||||
public static final String FREEEXPORT = "G";
|
||||
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT, FREEEXPORT).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -106,7 +106,14 @@ public class NodeMap {
|
||||
* @return the text content of the matching node, converted to BigDecimal
|
||||
*/
|
||||
public Optional<BigDecimal> getAsBigDecimal(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent).map(s->new BigDecimal(s.trim()));
|
||||
return getNode(localNames).map(Node::getTextContent).map(s->{
|
||||
try {
|
||||
return new BigDecimal(s.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user