Merge branch 'ZUGFeRD-master' into java17-base

This commit is contained in:
Philip Helger
2026-05-01 20:29:46 +02:00
52 changed files with 8173 additions and 942 deletions

View File

@@ -21,7 +21,9 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
/***
* the period (usually days) count how long the percent apply
*/
protected Integer days=null;
protected Integer days;
protected BigDecimal basisAmount;
/***
* Create a cash discount (skonto) with the specified height in the specified period.
@@ -61,18 +63,31 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
return this;
}
public BigDecimal getBasisAmount() {
return basisAmount;
}
public CashDiscount setBasisAmount(BigDecimal basisAmount) {
this.basisAmount = basisAmount;
return this;
}
/***
* @return this particular cash discount as cross industry invoice XML
*/
@JsonIgnore
public String getAsCII() {
return "<ram:SpecifiedTradePaymentTerms>"+
"<ram:Description>Cash Discount</ram:Description>"+
" <ram:ApplicableTradePaymentDiscountTerms>"+
" <ram:BasisPeriodMeasure unitCode=\"DAY\">"+days+"</ram:BasisPeriodMeasure>"+
" <ram:CalculationPercent>"+XMLTools.nDigitFormat(percent,3)+"</ram:CalculationPercent>"+
" </ram:ApplicableTradePaymentDiscountTerms>"+
"</ram:SpecifiedTradePaymentTerms>";
String s = "<ram:SpecifiedTradePaymentTerms>"
+ " <ram:Description>Cash Discount</ram:Description>"
+ " <ram:ApplicableTradePaymentDiscountTerms>"
+ " <ram:BasisPeriodMeasure unitCode=\"DAY\">" + days + "</ram:BasisPeriodMeasure>";
if (basisAmount != null) {
s += " <ram:BasisAmount>" + XMLTools.nDigitFormat(basisAmount, 2) + "</ram:BasisAmount>";
}
s += " <ram:CalculationPercent>"+XMLTools.nDigitFormat(percent,3)+"</ram:CalculationPercent>"+
" </ram:ApplicableTradePaymentDiscountTerms>"+
"</ram:SpecifiedTradePaymentTerms>";
return s;
}
/***
@@ -84,8 +99,4 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
public String getAsXRechnung() {
return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n";
}
}

View File

@@ -39,6 +39,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Invoice implements IExportableTransaction {
protected boolean testIndicator;
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, invoicer = null, invoicee = null;
@@ -75,13 +76,23 @@ public class Invoice implements IExportableTransaction {
protected String creditorReferenceID; // required when direct debit is used.
private BigDecimal roundingAmount=null;
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
private String businessProcessId;
public Invoice() {
ZFItems = new ArrayList<>();
cashDiscounts = new ArrayList<>();
setCurrency("EUR");
}
@Override
public boolean getTestIndicator() {
return testIndicator;
}
public Invoice setTestIndicator() {
this.testIndicator = true;
return this;
}
@Override
public String getDocumentName() {
return documentName;
@@ -197,7 +208,18 @@ public class Invoice implements IExportableTransaction {
setObjectIdentifierReferencedDocument(dr);
return this;
}
public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode) {
ReferencedDocument dr = new ReferencedDocument(id);
dr.setReferenceTypeCode(referenceTypeCode);
return setObjectIdentifierReferencedDocument(dr);
}
public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode, Date issueDate) {
ReferencedDocument dr = new ReferencedDocument(id);
dr.setReferenceTypeCode(referenceTypeCode);
dr.setFormattedIssueDateTime(issueDate);
return setObjectIdentifierReferencedDocument(dr);
}
public Invoice setNumber(String number) {
this.number = number;
@@ -1156,4 +1178,14 @@ public class Invoice implements IExportableTransaction {
return this;
}
public Invoice setBusinessProcessId(String id) {
this.businessProcessId = id;
return this;
}
@Override
public String getBusinessProcessId() {
return businessProcessId;
}
}

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -11,6 +12,19 @@ import org.apache.commons.io.IOUtils;
import org.dom4j.io.XMLWriter;
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
public class XMLTools extends XMLWriter {
@Override
@@ -23,6 +37,86 @@ public class XMLTools extends XMLWriter {
return super.escapeElementEntities(s);
}
public static DocumentBuilder getDocumentBuilder(boolean namespaceAware) throws ParserConfigurationException {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
try {
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (IllegalArgumentException e) {
// ignore
}
try {
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (IllegalArgumentException e) {
// ignore
}
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
try {
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
} catch (ParserConfigurationException e) {
// ignore
}
try {
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
} catch (ParserConfigurationException e) {
// ignore
}
try {
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (ParserConfigurationException e) {
// ignore
}
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(namespaceAware);
return dbf.newDocumentBuilder();
}
public static Validator getValidator(URL schemaFile) throws SAXException
{
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (SAXNotSupportedException | SAXNotRecognizedException e) {
// ignore
}
Schema schema = schemaFactory.newSchema(schemaFile);
Validator validator = schema.newValidator();
try {
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (SAXNotSupportedException | SAXNotRecognizedException e) {
// ignore
}
return validator;
}
public static TransformerFactory getTransformerFactory()
{
TransformerFactory factory = new net.sf.saxon.TransformerFactoryImpl();
try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
// ignore
}
return factory;
}
public static String nDigitFormat(BigDecimal value, int scale) {
/*
* I needed 123.45, locale independent.I tried

View File

@@ -49,12 +49,6 @@ public class CustomXMLProvider implements IXMLProvider {
}
@Override
public void setTest() {
// TODO Auto-generated method stub
}
@Override
public Profile getProfile() {
return profile;

View File

@@ -41,28 +41,6 @@ public class DXExporterFromA1 extends DXExporterFromA3 {
private static boolean isValidA1(DataSource dataSource) throws IOException {
return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser (dataSource));
}
/***
* internal helper function: get namespace for order-x
* @param ver the delivery-x version
* @return the URN of the namespace
*/
@Override
public String getNamespaceForVersion(int ver) {
// As of late 2022 the Delivery-X standard is not yet published. See specification:
// Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022
// Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3
// http://docplayer.org/230301085-Der-digitale-lieferschein-dls.html
return "urn:factur-x:pdfa:CrossIndustryDocument:despatchadvice:1p0#";
}
/***
* internal helper: returns the namespace prefix for the given order-x version number
* @param ver the ox version
* @return the namespace prefix as string, without colon
*/
@Override
public String getPrefixForVersion(int ver) {
return "fx";
}
private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException {
/*
@@ -136,6 +114,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 {
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
return (DXExporterFromA1) super.setConformanceLevel(newLevel);
}
@Override
public DXExporterFromA1 setProducer(String producer){
return (DXExporterFromA1) super.setProducer(producer);

View File

@@ -71,6 +71,7 @@ import jakarta.activation.FileDataSource;
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
protected boolean compressionEnabled = false;
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
/**
@@ -129,6 +130,29 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
private boolean attachZUGFeRDHeaders = true;
/***
* internal helper function: get namespace for order-x
* @param ver the delivery-x version
* @return the URN of the namespace
*/
@Override
public String getNamespaceForVersion(int ver) {
// As of late 2022 the Delivery-X standard is not yet published. See specification:
// Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022
// Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3
// http://docplayer.org/230301085-Der-digitale-lieferschein-dls.html
return "urn:factur-x:pdfa:CrossIndustryDocument:despatchadvice:1p0#";
}
/***
* internal helper: returns the namespace prefix for the given order-x version number
* @param ver the ox version
* @return the namespace prefix as string, without colon
*/
@Override
public String getPrefixForVersion(int ver) {
return "fx";
}
/**
* Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
* metadata level, this will not e.g. convert graphics to JPG-2000)
@@ -296,8 +320,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
// ef.addCompression();
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
ef.setSubtype(subType);
ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar());
@@ -417,7 +441,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this;
}
@Override
public DXExporterFromA3 setCreator(String creator) {
this.creator = creator;

View File

@@ -38,7 +38,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.TradeParty;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
/***
@@ -49,6 +48,11 @@ import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
*/
public interface IExportableTransaction {
@JsonIgnore
default boolean getTestIndicator() {
return false;
}
/**
* appears in /rsm:CrossIndustryDocument/rsm:HeaderExchangedDocument/ram:Name
*
@@ -626,4 +630,16 @@ public interface IExportableTransaction {
default String getCreditorReferenceID() {
return null;
}
/**
* BT-23 Business process identifier
* /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/
* ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID
*
* @return business process ID (e.g. "B1" or a URN) or null if not provided
*/
default String getBusinessProcessId() {
return null;
}
}

View File

@@ -22,13 +22,9 @@ public interface IXMLProvider {
public byte[] getXML();
public void setTest();
public void generateXML(IExportableTransaction trans);
public void setProfile(Profile p);
public Profile getProfile();
}

View File

@@ -56,6 +56,7 @@ public interface IZUGFeRDExporter extends Closeable, IExporter {
public IZUGFeRDExporter load(InputStream pdfSource) throws IOException;
public IZUGFeRDExporter setCreator(String creator);
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel);
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean enablePDFAttachmentCompression);
public IZUGFeRDExporter setProducer(String producer);
public IZUGFeRDExporter setZUGFeRDVersion(int version);
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException;

View File

@@ -41,24 +41,6 @@ public class OXExporterFromA1 extends OXExporterFromA3 {
private static boolean isValidA1(DataSource dataSource) throws IOException {
return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser(dataSource));
}
/***
* internal helper function: get namespace for order-x
* @param ver the order-x version
* @return the URN of the namespace
*/
@Override
public String getNamespaceForVersion(int ver) {
return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#";
}
/***
* internal helper: returns the namespace prefix for the given order-x version number
* @param ver the ox version
* @return the namespace prefix as string, without colon
*/
@Override
public String getPrefixForVersion(int ver) {
return "fx";
}
private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException {
/*

View File

@@ -126,9 +126,30 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
*/
protected String orderXDocumentType = "ORDER";
private boolean attachZUGFeRDHeaders = true;
/***
* internal helper function: get namespace for order-x
* @param ver the order-x version
* @return the URN of the namespace
*/
@Override
public String getNamespaceForVersion(int ver) {
return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#";
}
/***
* internal helper: returns the namespace prefix for the given order-x version number
* @param ver the ox version
* @return the namespace prefix as string, without colon
*/
@Override
public String getPrefixForVersion(int ver) {
return "fx";
}
/** Defines whether attachments to the PDF should be using FLATE compression */
private boolean compressionEnabled = false;
/**
* Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
* metadata level, this will not e.g. convert graphics to JPG-2000)
@@ -296,8 +317,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
// ef.addCompression();
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
ef.setSubtype(subType);
ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar());

View File

@@ -32,6 +32,7 @@ public class Profiles {
{"BASICWL", new Profile("BASICWL", "urn:factur-x.eu:1p0:basicwl")},
{"BASIC", new Profile("BASIC", "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic")},
{"EN16931", new Profile("EN16931", "urn:cen.eu:en16931:2017")},
{"EXTENDED-CTC-FR", new Profile("EXTENDED-CTC-FR", "urn:cen.eu:en16931:2017#conformant#urn.cpro.gouv.fr:1p0:extended-ctc-fr")},
{"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")},
{"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0")} // up next: urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0

View File

@@ -136,11 +136,6 @@ public class UBLDAPullProvider implements IXMLProvider {
}
@Override
public void setTest() {
}
@Override
public void setProfile(Profile p) {
profile = p;
@@ -150,5 +145,4 @@ public class UBLDAPullProvider implements IXMLProvider {
public Profile getProfile() {
return profile;
}
}

View File

@@ -15,6 +15,7 @@ import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter;
import org.mustangproject.XMLTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,8 +43,7 @@ public class ValidationLogVisualizer {
public ValidationLogVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl();
// fact = TransformerFactory.newInstance();
mFactory = XMLTools.getTransformerFactory();
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
}

View File

@@ -16,6 +16,8 @@ import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.mustangproject.XMLTools;
/***
* Uses a XSLT transformation to upgrade
@@ -31,8 +33,7 @@ public class XMLUpgrader {
private Templates mXsltTemplate = null;
public XMLUpgrader() {
mFactory = new net.sf.saxon.TransformerFactoryImpl();
//fact = TransformerFactory.newInstance();
mFactory = XMLTools.getTransformerFactory();
mFactory.setURIResolver(new ClasspathResourceURIResolver());
}

View File

@@ -49,14 +49,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
private String paymentTermsDescription;
protected Profile profile = Profiles.getByName("COMFORT", 1);
/**
* enables the flag to indicate a test invoice in the XML structure
*/
@Override
public void setTest() {
}
@Override
protected String vatFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);

View File

@@ -35,7 +35,6 @@ import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.dom4j.Document;
@@ -61,14 +60,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
private String paymentTermsDescription;
protected Profile profile = Profiles.getByName("EN16931");
/**
* enables the flag to indicate a test invoice in the XML structure
*/
@Override
public void setTest() {
}
protected String vatFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
@@ -384,11 +375,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">"
+ "<!-- generated by: mustangproject.org v" + ZUGFeRD2PullProvider.class.getPackage().getImplementationVersion() + "-->"
+ "<rsm:ExchangedDocumentContext>\n";
// + "
// <ram:TestIndicator><udt:Indicator>"+testBooleanStr+"</udt:Indicator></ram:TestIndicator>"
//
if (getProfile() == Profiles.getByName("XRechnung")) {
if (trans.getTestIndicator() && profile == Profiles.getByName("Extended")) {
xml += "<ram:TestIndicator><udt:Indicator>" + trans.getTestIndicator() + "</udt:Indicator></ram:TestIndicator>";
}
String businessProcessId = trans.getBusinessProcessId();
if (businessProcessId != null && !businessProcessId.isBlank()) {
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
+ "<ram:ID>" + XMLTools.encodeXML(businessProcessId) + "</ram:ID>\n"
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
}else if (getProfile() == Profiles.getByName("XRechnung")) {
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
+ "<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n"
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
@@ -419,8 +416,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
"<ram:AssociatedDocumentLineDocument>"
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>"
+ buildItemNotes(currentItem)
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>";
if (getProfile() == Profiles.getByName("Extended")) {
if (currentItem.getParentLineID() != null) {
xml += "<ram:ParentLineID>" + XMLTools.encodeXML(currentItem.getParentLineID()) + "</ram:ParentLineID>";
}
if (currentItem.getLineStatusReasonCode() != null) {
xml += "<ram:LineStatusReasonCode>" + XMLTools.encodeXML(currentItem.getLineStatusReasonCode()) + "</ram:LineStatusReasonCode>";
}
}
xml += buildItemNotes(currentItem)
+ "</ram:AssociatedDocumentLineDocument>"
+ "<ram:SpecifiedTradeProduct>";
@@ -550,15 +555,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:SpecifiedLineTradeDelivery>"
+ "<ram:SpecifiedLineTradeSettlement>"
+ "<ram:ApplicableTradeTax>"
+ "<ram:TypeCode>VAT</ram:TypeCode>";
+ "<ram:ApplicableTradeTax>";
// <CalculatedAmount/>
xml += "<ram:TypeCode>VAT</ram:TypeCode>";
if (currentItem.getProduct().getTaxExemptionReason() != null) {
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
if (currentItem.getProduct().getTaxExemptionReasonCode() != null) {
xml += "<ram:ExemptionReasonCode>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReasonCode()) + "</ram:ExemptionReasonCode>";
}
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
BigDecimal vatValue=BigDecimal.ZERO;
if (currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS)) {
vatValue=BigDecimal.ZERO;
@@ -662,6 +668,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:AdditionalReferencedDocument>"
+ "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getObjectIdentifierReferencedDocument().getIssuerAssignedID()) + "</ram:IssuerAssignedID>"
+ "<ram:TypeCode>130</ram:TypeCode>";
// NEW: BT-18-1 scheme identifier
String rtc = trans.getObjectIdentifierReferencedDocument().getReferenceTypeCode();
if (rtc != null && !rtc.isEmpty()) {
xml += "<ram:ReferenceTypeCode>" + XMLTools.encodeXML(rtc) + "</ram:ReferenceTypeCode>";
}
if (trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()!=null) {
xml += "<ram:FormattedIssueDateTime>" + DATE.qdtFormat(trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()) + "</ram:FormattedIssueDateTime>";
}
@@ -700,6 +711,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:ActualDeliverySupplyChainEvent>";
}
if (trans.getDespatchAdviceReferencedDocumentID() != null && !trans.getDespatchAdviceReferencedDocumentID().trim().isEmpty()) {
xml += "<ram:DespatchAdviceReferencedDocument>";
xml += "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getDespatchAdviceReferencedDocumentID()) + "</ram:IssuerAssignedID>";
xml += "</ram:DespatchAdviceReferencedDocument>";
}
if (trans.getDeliveryNoteReferencedDocumentID() != null && !trans.getDeliveryNoteReferencedDocumentID().trim().isEmpty()) {
xml += "<ram:DeliveryNoteReferencedDocument>";
xml += "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getDeliveryNoteReferencedDocumentID()) + "</ram:IssuerAssignedID>";
@@ -708,14 +725,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:FormattedIssueDateTime><qdt:DateTimeString format=\"102\">"+XMLTools.encodeXML(dateFormat102.format(trans.getDeliveryNoteReferencedDocumentDate()))+"</qdt:DateTimeString></ram:FormattedIssueDateTime>";
}
xml += "</ram:DeliveryNoteReferencedDocument>";
}
if (trans.getDespatchAdviceReferencedDocumentID() != null && !trans.getDespatchAdviceReferencedDocumentID().trim().isEmpty()) {
xml += "<ram:DespatchAdviceReferencedDocument>";
xml += "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getDespatchAdviceReferencedDocumentID()) + "</ram:IssuerAssignedID>";
xml += "</ram:DespatchAdviceReferencedDocument>";
}
xml += "</ram:ApplicableHeaderTradeDelivery>";
@@ -811,8 +820,14 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
"</ram:ChargeIndicator>";
if (charge.getPercent() != null && (profile == Profiles.getByName("EN16931") || (profile == Profiles.getByName("EXTENDED")))) {
xml += "<ram:CalculationPercent>" + vatFormat(charge.getPercent()) + "</ram:CalculationPercent>";
}
if (charge.getBasisAmount() != null && (profile == Profiles.getByName("EN16931") || (profile == Profiles.getByName("EXTENDED")))) {
xml += "<ram:BasisAmount>" + currencyFormat(charge.getBasisAmount()) + "</ram:BasisAmount>";
}
xml += "<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
if (charge.getReasonCode() != null) {
xml += "<ram:ReasonCode>" + charge.getReasonCode() + "</ram:ReasonCode>";
}
@@ -850,19 +865,25 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
if (profile == Profiles.getByName("XRechnung")) {
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>false</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(calc)) + "</ram:ActualAmount>";
if (allowance.getReason() != null) {
xml += "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
"</ram:ChargeIndicator>";
if (allowance.getPercent() != null) {
xml += "<ram:CalculationPercent>" + vatFormat(allowance.getPercent()) + "</ram:CalculationPercent>";
}
if (allowance.getBasisAmount() != null) {
xml += "<ram:BasisAmount>" + currencyFormat(allowance.getBasisAmount()) + "</ram:BasisAmount>";
}
xml += "<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(calc)) + "</ram:ActualAmount>";
if (allowance.getReasonCode() != null) {
xml += "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
}
if (allowance.getReason() != null) {
xml += "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
}
xml += "<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + allowance.getCategoryCode() + "</ram:CategoryCode>";

View File

@@ -77,8 +77,9 @@ import jakarta.activation.DataSource;
import jakarta.activation.FileDataSource;
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter {
private boolean isFacturX = true;
private boolean isFacturX = true;
public static final int DefaultZUGFeRDVersion = 2;
protected boolean ignorePDFAErrors = false;
@@ -101,6 +102,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
private Profile profile = null;
protected boolean documentPrepared = false;
/** Defines whether attachments to the PDF should be using FLATE compression */
private boolean compressionEnabled = false;
/**
* Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set,
* in which case passing a IZUGFeRDExportableTransaction is not necessary. By
@@ -392,7 +396,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
// ef.addCompression();
ef.setSubtype(subType);
ef.setSize(data.length);
@@ -503,6 +508,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
return this;
}
@Override
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
return this;
}
public ZUGFeRDExporterFromA3 setCreator(String creator) {
this.creator = creator;

View File

@@ -188,6 +188,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
return getExporter().setConformanceLevel(newLevel);
}
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
return getExporter().setEnablePDFAttachmentCompression(compressionEnabled);
}
public IZUGFeRDExporter setProducer(String producer) {
return getExporter().setProducer(producer);

View File

@@ -19,6 +19,8 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
@@ -29,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
@@ -472,17 +475,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
}
/**
* @return raw XML of the invoice
*/
public String getMeta() {
if (rawXML == null) {
return null;
}
return new String(rawXML, StandardCharsets.UTF_8);
}
public int getVersion() throws Exception {
if (!containsMeta) {
@@ -517,20 +509,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
}
/**
* will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
*
* @return true if the invoice contains ZUGFeRD XML
*/
public boolean canParse() {
// SpecifiedExchangedDocumentContext is in the schema, so a relatively good
// indication if zugferd is present - better than just invoice
final String meta = getMeta();
return (meta != null) && (meta.length() > 0) && ((meta.contains("SpecifiedExchangedDocumentContext")
/* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */));
}
/**
* returns an instance of PostalTradeAddress for SellerTradeParty section

View File

@@ -18,9 +18,7 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
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;
@@ -48,6 +46,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -311,36 +310,62 @@ public class ZUGFeRDInvoiceImporter {
setRawXML(rawXML, true);
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
final DocumentBuilder builder = dbf.newDocumentBuilder();
/**
* @return raw XML of the invoice
*/
public String getMeta() {
if (rawXML == null) {
return null;
}
return new String(rawXML, StandardCharsets.UTF_8);
}
/**
* will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
*
* @return true if the invoice contains ZUGFeRD XML
*/
public boolean canParse() {
// SpecifiedExchangedDocumentContext is in the schema, so a relatively good
// indication if zugferd is present - better than just invoice
final String meta = getMeta();
if ((meta == null) || (meta.length() == 0)) {
return false;
}
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
if (parseAutomatically) {
try {
importedInvoice = new CalculatedInvoice();
extractInto(importedInvoice);
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
try {
DocumentBuilder builder = XMLTools.getDocumentBuilder(true);
document = builder.parse(is);
} catch (Exception e) {
return false;
}
return ((meta.contains("SpecifiedExchangedDocumentContext")
/* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */));
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException {
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
DocumentBuilder builder = XMLTools.getDocumentBuilder(true);
if (canParse()) {
document = builder.parse(is);
if (parseAutomatically) {
try {
importedInvoice = new CalculatedInvoice();
extractInto(importedInvoice);
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -472,7 +497,7 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"TaxInclusiveAmount\"]");
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementMonetarySummation\"]/*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"TaxInclusiveAmount\"]");
BigDecimal expectedGrandTotal = null;
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (totalNodes.getLength() > 0) {
@@ -485,7 +510,7 @@ public class ZUGFeRDInvoiceImporter {
}
}
xpr = xpath.compile("//*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"TaxExclusiveAmount\"]");
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementMonetarySummation\"]/*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"TaxExclusiveAmount\"]");
BigDecimal expectedTaxBasis = null;
NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (basisNodes.getLength() > 0) {
@@ -628,14 +653,8 @@ public class ZUGFeRDInvoiceImporter {
}
String tenderReference = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim();
String tenderReferenceDate = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim();
if((tenderReference != null)&&(!tenderReference.isEmpty())){
if((tenderReferenceDate != null)&&(!tenderReferenceDate.isEmpty())){
zpp.setTenderReferencedDocument(new ReferencedDocument(tenderReference, parseDate(tenderReferenceDate, "yyyy-MM-dd")));
} else {
zpp.setTenderReferencedDocument(tenderReference);
}
zpp.setTenderReferencedDocument(tenderReference);
}
String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"] | /*[local-name()=\"CreditNote\"]/*[local-name()=\"PaymentMeans\"]/*[local-name()=\"PaymentDueDate\"]").trim();
@@ -1275,6 +1294,25 @@ public class ZUGFeRDInvoiceImporter {
//appliedAmount
//AppliedTradeTax
}
if (cd.getDays() == null) {
NodeList siblingNodes = cashdiscountNodes.item(i).getParentNode().getChildNodes();
for (int siblingIndex = 0; siblingIndex < siblingNodes.getLength(); siblingIndex++) {
if (siblingNodes.item(siblingIndex).getLocalName() != null && siblingNodes.item(siblingIndex).getLocalName().equals("DueDateDateTime")) {
NodeList dueDateChilds = siblingNodes.item(siblingIndex).getChildNodes();
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
String dueDateString = XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex));
dueDate = parseDate(dueDateString, "yyyyMMdd");
long diffInMillies = Math.abs(dueDate.getTime() - zpp.getIssueDate().getTime());
long diffInDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
cd.setDays((int) diffInDays);
}
}
}
}
}
if ((cd.getPercent() != null)&&(cd.getDays() != null)) {
zpp.addCashDiscount(cd);
}
@@ -1331,6 +1369,9 @@ public class ZUGFeRDInvoiceImporter {
+ Stream.of(tc.trans.getZFItems())
.map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString())
.collect(Collectors.joining(" + "));
if (tc.trans.getRoundingAmount()!=null) {
moreDetails += " and rounding amount "+tc.trans.getRoundingAmount().toPlainString();
}
} catch (Exception ignored) {
}
throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);

View File

@@ -32,6 +32,7 @@ import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter;
import org.mustangproject.EStandard;
import org.mustangproject.XMLTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -39,7 +40,6 @@ import org.w3c.dom.Element;
import org.xml.sax.InputSource;
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;
@@ -47,8 +47,6 @@ import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
@@ -85,8 +83,7 @@ public class ZUGFeRDVisualizer {
private Templates mXsltZF1HTMLTemplate = null;
public ZUGFeRDVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl();
// fact = TransformerFactory.newInstance();
mFactory = XMLTools.getTransformerFactory();
mFactory.setURIResolver(new ClasspathResourceURIResolver());
}
@@ -104,40 +101,8 @@ public class ZUGFeRDVisualizer {
String ublCreditNoteSignature = "CreditNote";
String cioSignature = "SCRDMCCBDACIOMessageStructure";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
try
{
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
}
catch (IllegalArgumentException e)
{
LOGGER.warn("Property: \"Access external DTD\" not supported.");
}
try
{
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
}
catch (IllegalArgumentException e)
{
LOGGER.warn("Property: \"Access external schema\" not supported.");
}
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
try {
DocumentBuilder db = dbf.newDocumentBuilder();
DocumentBuilder db = XMLTools.getDocumentBuilder(true);
Document doc = db.parse(new InputSource(fis));
Element root = doc.getDocumentElement();
if (root.getLocalName().equals(zf1Signature)) {
@@ -252,7 +217,7 @@ public class ZUGFeRDVisualizer {
}
}
protected String toFOP(String xmlFilename)
protected String toFOP(String xmlFilename, Language lang)
throws IOException, TransformerException, ParserConfigurationException {
EStandard theStandard;
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
@@ -260,11 +225,11 @@ public class ZUGFeRDVisualizer {
}
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
return toFOP(fis, theStandard);
return toFOP(fis, theStandard, lang);
}
}
protected String toFOP(InputStream is, EStandard theStandard)
protected String toFOP(InputStream is, EStandard theStandard, Language lang)
throws TransformerException, IOException {
try {
@@ -291,12 +256,16 @@ public class ZUGFeRDVisualizer {
Optional<InputStream> in = copyStream(iaos);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (in.isPresent()) {
applyXSLTToPDF(in.get(), baos);
applyXSLTToPDF(in.get(), baos, lang);
}
return baos.toString(StandardCharsets.UTF_8);
}
public void toPDF(String xmlFilename, String pdfFilename) {
toPDF(xmlFilename, pdfFilename, Language.DE);
}
public void toPDF(String xmlFilename, String pdfFilename, Language lang) {
// the writing part
File XMLinputFile = new File(xmlFilename);
@@ -307,7 +276,7 @@ public class ZUGFeRDVisualizer {
out from git with arbitrary options (which may include CSRF changes)
*/
try {
fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
fopInput = this.toFOP(XMLinputFile.getAbsolutePath(), lang);
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
@@ -321,8 +290,12 @@ public class ZUGFeRDVisualizer {
return null;
}, (OutputStream out) -> {});
}
public byte[] toPDF(String xmlContent) {
return toPDF(xmlContent, Language.DE);
}
public byte[] toPDF(String xmlContent, Language lang) {
String fopInput = null;
@@ -334,7 +307,7 @@ public class ZUGFeRDVisualizer {
EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
fopInput = toFOP(fis, theStandard);
fopInput = toFOP(fis, theStandard, lang);
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
@@ -470,9 +443,10 @@ public class ZUGFeRDVisualizer {
xmlFile.close();
}
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream, Language lang)
throws TransformerException, IOException {
Transformer transformer = mXsltPDFTemplate.newTransformer();
transformer.setParameter("lang", lang.name().toLowerCase());
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
xmlFile.close();

View File

@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8"?>
<properties>
<entry key="xr:Buyer_reference" id="BT-10">Référence acheteur</entry>
<entry key="xr:Buyer_name" id="BT-44">Nom</entry>
<entry key="xr:Buyer_address_line_1" id="BT-50">Adresse ligne 1</entry>
<entry key="xr:Buyer_address_line_2" id="BT-51">Adresse ligne 2</entry>
<entry key="xr:Buyer_address_line_3" id="BT-163">Adresse ligne 3</entry>
<entry key="xr:Buyer_post_code" id="BT-53">Code postal</entry>
<entry key="xr:Buyer_city" id="BT-52">Ville</entry>
<entry key="xr:Buyer_country_code" id="BT-55">Code pays</entry>
<entry key="xr:Buyer_identifier" id="BT-46">Identifiant</entry>
<entry key="xr:Buyer_identifier/@scheme_identifier" id="BT-46_scheme">Schéma de l'identifiant</entry>
<entry key="xr:Buyer_contact_point" id="BT-56">Nom</entry>
<entry key="xr:Buyer_contact_telephone_number" id="BT-57">Téléphone</entry>
<entry key="xr:Buyer_contact_email_address" id="BT-58">Adresse e-mail</entry>
<entry key="xr:Seller_name" id="BT-27">Raison sociale</entry>
<entry key="xr:Seller_address_line_1" id="BT-35">Adresse ligne 1</entry>
<entry key="xr:Seller_address_line_2" id="BT-36">Adresse ligne 2</entry>
<entry key="xr:Seller_address_line_3" id="BT-162">Adresse ligne 3</entry>
<entry key="xr:Seller_post_code" id="BT-38">Code postal</entry>
<entry key="xr:Seller_city" id="BT-37">Ville</entry>
<entry key="xr:Seller_country_subdivision" id="BT-39">Région</entry>
<entry key="xr:Seller_country_code" id="BT-40">Code pays</entry>
<entry key="xr:Seller_identifier" id="BT-29">Identifiant</entry>
<entry key="xr:Seller_identifier/@scheme_identifier" id="BT-29_scheme">Schéma de l'identifiant</entry>
<entry key="xr:Seller_contact_point" id="BT-41">Nom</entry>
<entry key="xr:Seller_contact_telephone_number" id="BT-42">Téléphone</entry>
<entry key="xr:Seller_contact_email_address" id="BT-43">Adresse e-mail</entry>
<entry key="xr:Invoice_number" id="BT-1">Numéro de facture</entry>
<entry key="xr:Invoice_issue_date" id="BT-2">Date de facture</entry>
<entry key="xr:Invoice_type_code" id="BT-3">Type de facture</entry>
<entry key="xr:Invoice_currency_code" id="BT-5">Devise</entry>
<entry key="xr:Project_reference" id="BT-11">Numéro de projet</entry>
<entry key="xr:Contract_reference" id="BT-12">Numéro de contrat</entry>
<entry key="xr:Purchase_order_reference" id="BT-13">Numéro de commande</entry>
<entry key="xr:Sales_order_reference" id="BT-14">Référence de commande client</entry>
<!-- Be aware that these are for the whole invoice -->
<entry key="xr:Invoicing_period_start_date" id="BT-73">Du</entry>
<entry key="xr:Invoicing_period_end_date" id="BT-74">Au</entry>
<entry key="xr:Preceding_Invoice_reference" id="BT-25">Numéro de facture</entry>
<entry key="xr:Preceding_Invoice_issue_date" id="BT-26">Date de facture</entry>
<entry key="xr:Sum_of_Invoice_line_net_amount" id="BT-106">Total des lignes</entry>
<entry key="xr:Sum_of_allowances_on_document_level" id="BT-107">Total des remises</entry>
<entry key="xr:Sum_of_charges_on_document_level" id="BT-108">Total des frais</entry>
<entry key="xr:Invoice_total_amount_without_VAT" id="BT-109">Total HT</entry>
<entry key="xr:Invoice_total_VAT_amount" id="BT-110">Total TVA</entry>
<entry key="xr:Invoice_total_VAT_amount_in_accounting_currency" id="BT-111">Total TVA en devise comptable</entry>
<entry key="xr:Invoice_total_amount_with_VAT" id="BT-112">Total TTC</entry>
<entry key="xr:Paid_amount" id="BT-113">Montant payé</entry>
<entry key="xr:Rounding_amount" id="BT-114">Montant d'arrondi</entry>
<entry key="xr:Amount_due_for_payment" id="BT-115">Montant dû</entry>
<entry key="xr:VAT_category_code" id="BT-118">Catégorie de TVA</entry>
<entry key="xr:VAT_category_taxable_amount" id="BT-116">Total</entry>
<entry key="xr:VAT_category_rate" id="BT-119">Taux de TVA</entry>
<entry key="xr:VAT_category_tax_amount" id="BT-117">Montant de TVA</entry>
<entry key="xr:VAT_exemption_reason_text" id="BT-120">Motif d'exonération</entry>
<entry key="xr:VAT_exemption_reason_code" id="BT-121">Code du motif d'exonération</entry>
<!-- Remises -->
<entry key="xr:Document_level_allowance_VAT_category_code" id="BT-95">Catégorie de TVA de la remise</entry>
<entry key="xr:Document_level_allowance_base_amount" id="BT-93">Montant de base</entry>
<entry key="xr:Document_level_allowance_percentage" id="BT-94">Pourcentage</entry>
<entry key="xr:Document_level_allowance_amount" id="BT-92">Remise</entry>
<entry key="xr:Document_level_allowance_VAT_rate" id="BT-96">Taux de TVA de la remise</entry>
<entry key="xr:Document_level_allowance_reason" id="BT-97">Motif de la remise</entry>
<entry key="xr:Document_level_allowance_reason_code" id="BT-98">Code du motif de la remise</entry>
<!-- Frais -->
<entry key="xr:Document_level_charge_VAT_category_code" id="BT-102">Catégorie de TVA des frais</entry>
<entry key="xr:Document_level_charge_base_amount" id="BT-100">Montant de base</entry>
<entry key="xr:Document_level_charge_percentage" id="BT-101">Pourcentage</entry>
<entry key="xr:Document_level_charge_amount" id="BT-99">Frais</entry>
<entry key="xr:Document_level_charge_VAT_rate" id="BT-103">Taux de TVA des frais</entry>
<entry key="xr:Document_level_charge_reason" id="BT-104">Motif des frais</entry>
<entry key="xr:Document_level_charge_reason_code" id="BT-105">Code du motif des frais</entry>
<entry key="xr:Payment_terms" id="BT-20">Escompte ; autres conditions de paiement</entry>
<entry key="xr:Payment_due_date" id="BT-9">Date d'échéance</entry>
<entry key="xr:Payment_means_type_code" id="BT-81">Code du moyen de paiement</entry>
<entry key="xr:Payment_means_text" id="BT-82">Moyen de paiement</entry>
<entry key="xr:Remittance_information" id="BT-83">Référence de paiement</entry>
<entry key="xr:Payment_card_primary_account_number" id="BT-87">Numéro de carte</entry>
<entry key="xr:Payment_card_holder_name" id="BT-88">Titulaire de la carte</entry>
<!-- BG-19 Direct Debit -->
<entry key="xr:Mandate_reference_identifier" id="BT-89">Référence du mandat</entry>
<entry key="xr:Debited_account_identifier" id="BT-91">IBAN</entry>
<entry key="xr:Bank_assigned_creditor_identifier" id="BT-90">Identifiant créancier</entry>
<!-- BG-17 Credit Transfer-->
<entry key="xr:Payment_account_name" id="BT-85">Titulaire du compte</entry>
<entry key="xr:Payment_account_identifier" id="BT-84">IBAN</entry>
<entry key="xr:Payment_service_provider_identifier" id="BT-86">BIC</entry>
<entry key="xr:Invoice_note_subject_code" id="BT-21">Objet</entry>
<entry key="xr:Invoice_note" id="BT-22">Remarque</entry>
<!-- BG-4 Seller and others -->
<entry key="xr:Seller_trading_name" id="BT-28">Nom commercial</entry>
<entry key="xr:Seller_electronic_address" id="BT-34">Adresse électronique</entry>
<entry key="xr:Seller_electronic_address/@scheme_identifier" id="BT-34_scheme">Schéma de l'adresse électronique</entry>
<entry key="xr:Seller_legal_registration_identifier" id="BT-30">Numéro SIRET/SIREN</entry>
<entry key="xr:Seller_legal_registration_identifier/@scheme_identifier" id="BT-30_scheme">Schéma du numéro d'enregistrement</entry>
<entry key="xr:Seller_VAT_identifier" id="BT-31">Numéro de TVA intracommunautaire</entry>
<entry key="xr:Seller_tax_registration_identifier" id="BT-32">Numéro fiscal</entry>
<entry key="xr:Seller_additional_legal_information" id="BT-33">Informations juridiques complémentaires</entry>
<entry key="xr:VAT_accounting_currency_code" id="BT-6">Code devise de la TVA</entry>
<!-- BG 12 and 11 Tax Representative party -->
<entry key="xr:Seller_tax_representative_name" id="BT-62">Nom</entry>
<entry key="xr:Tax_representative_address_line_1" id="BT-64">Adresse ligne 1</entry>
<entry key="xr:Tax_representative_address_line_2" id="BT-65">Adresse ligne 2</entry>
<entry key="xr:Tax_representative_address_line_3" id="BT-164">Adresse ligne 3</entry>
<entry key="xr:Tax_representative_post_code" id="BT-67">Code postal</entry>
<entry key="xr:Tax_representative_city" id="BT-66">Ville</entry>
<entry key="xr:Tax_representative_country_subdivision" id="BT-68">Région</entry>
<entry key="xr:Tax_representative_country_code" id="BT-69">Code pays</entry>
<entry key="xr:Seller_tax_representative_VAT_identifier" id="BT-63">Numéro de TVA intracommunautaire</entry>
<!-- Additional buyer information from different bgs -->
<entry key="xr:Buyer_trading_name" id="BT-45">Nom commercial</entry>
<entry key="xr:Buyer_country_subdivision" id="BT-54">Région</entry>
<entry key="xr:Buyer_electronic_address" id="BT-49">Adresse électronique</entry>
<entry key="xr:Buyer_electronic_address/@scheme_identifier" id="BT-49_scheme">Schéma de l'adresse électronique</entry>
<entry key="xr:Buyer_legal_registration_identifier" id="BT-47">Numéro SIRET/SIREN</entry>
<entry key="xr:Buyer_legal_registration_identifier/@scheme_identifier" id="BT-47_scheme">Schéma du numéro d'enregistrement</entry>
<entry key="xr:Buyer_VAT_identifier" id="BT-48">Numéro de TVA intracommunautaire</entry>
<entry key="xr:Value_added_tax_point_date" id="BT-7">Date d'exigibilité de la TVA</entry>
<entry key="xr:Value_added_tax_point_date_code" id="BT-8">Code de la date d'exigibilité de la TVA</entry>
<entry key="xr:Buyer_accounting_reference" id="BT-19">Référence comptable</entry>
<entry key="xr:Deliver_to_location_identifier" id="BT-71">Identifiant du lieu de livraison</entry>
<entry key="xr:Deliver_to_location_identifier/@scheme_identifier" id="BT-71_scheme">Schéma de l'identifiant</entry>
<entry key="xr:Actual_delivery_date" id="BT-72">Date de livraison</entry>
<entry key="xr:Deliver_to_party_name" id="BT-70">Nom du destinataire</entry>
<entry key="xr:Deliver_to_address_line_1" id="BT-75">Adresse ligne 1</entry>
<entry key="xr:Deliver_to_address_line_2" id="BT-76">Adresse ligne 2</entry>
<entry key="xr:Deliver_to_address_line_3" id="BT-165">Adresse ligne 3</entry>
<entry key="xr:Deliver_to_post_code" id="BT-78">Code postal</entry>
<entry key="xr:Deliver_to_city" id="BT-77">Ville</entry>
<entry key="xr:Deliver_to_country_subdivision" id="BT-79">Région</entry>
<entry key="xr:Deliver_to_country_code" id="BT-80">Code pays</entry>
<entry key="xr:Tender_or_lot_reference" id="BT-17">Numéro d'appel d'offres</entry>
<entry key="xr:Receiving_advice_reference" id="BT-15">Référence de l'avis de réception</entry>
<entry key="xr:Despatch_advice_reference" id="BT-16">Référence de l'avis d'expédition</entry>
<entry key="xr:Business_process_type" id="BT-23">Identifiant du processus</entry>
<entry key="xr:Specification_identifier" id="BT-24">Identifiant de spécification</entry>
<entry key="xr:Invoiced_object_identifier" id="BT-18">Identifiant de l'objet</entry>
<entry key="xr:Invoiced_object_identifier/@scheme_identifier" id="BT-18_scheme">Schéma de l'identifiant de l'objet</entry>
<!-- BG 10 -->
<entry key="xr:Payee_name" id="BT-59">Nom</entry>
<entry key="xr:Payee_identifier" id="BT-60">Identifiant</entry>
<entry key="xr:Payee_identifier/@scheme_identifier" id="BT-60_scheme">Schéma de l'identifiant</entry>
<entry key="xr:Payee_legal_registration_identifier" id="BT-61">Numéro SIRET/SIREN</entry>
<entry key="xr:Payee_legal_registration_identifier/@scheme_identifier" id="BT-61_scheme">Schéma du numéro d'enregistrement</entry>
<!-- BG 24 Additonal supporting Documents -->
<entry key="xr:Supporting_document_reference" id="BT-122">Identifiant</entry>
<entry key="xr:Supporting_document_description" id="BT-123">Description</entry>
<entry key="xr:External_document_location" id="BT-124">Référence (ex. adresse Internet)</entry>
<entry key="xr:Attached_document" id="BT-125">Document joint</entry>
<entry key="xr:Attached_document/@mime_code" id="BT-125_mime_code">Format du document joint</entry>
<entry key="xr:Attached_document/@filename" id="BT-125_filename">Nom du document joint</entry>
<entry key="xrv:zeitstempel">Date/Heure</entry>
<entry key="xrv:betreff">Objet</entry>
<entry key="xrv:text">Texte</entry>
<entry key="xrv:details">Détails</entry>
<entry key="xr:Invoice_line_identifier" id="BT-126">Position</entry>
<entry key="xr:Invoice_line_note" id="BT-127">Texte libre</entry>
<entry key="xr:Invoice_line_object_identifier" id="BT-128_identifier">Identifiant de l'objet</entry>
<entry key="xr:Invoice_line_object_identifier/@scheme_identifier" id="BT-128_scheme">Schéma de l'identifiant de l'objet</entry>
<entry key="xr:Referenced_purchase_order_line_reference" id="BT-132">Numéro de ligne de commande</entry>
<entry key="xr:Invoice_line_Buyer_accounting_reference" id="BT-133">Référence comptable</entry>
<entry key="xr:Invoice_line_period_start_date" id="BT-134">Du</entry>
<entry key="xr:Invoice_line_period_end_date" id="BT-135">Au</entry>
<entry key="xr:Invoiced_quantity" id="BT-129">Quantité</entry>
<entry key="xr:Invoiced_quantity_unit_of_measure_code" id="BT-130">Unité</entry>
<entry key="xr:Item_net_price" id="BT-146">Prix unitaire (HT)</entry>
<entry key="xr:Invoice_line_net_amount" id="BT-131">Prix total (HT)</entry>
<entry key="xr:Item_price_discount" id="BT-147">Remise (HT)</entry>
<entry key="xr:Item_gross_price" id="BT-148">Prix catalogue (HT)</entry>
<entry key="xr:Item_price_base_quantity" id="BT-149">Quantité de base</entry>
<entry key="xr:Item_price_base_quantity_unit_of_measure" id="BT-150">Code de l'unité de mesure</entry>
<entry key="xr:Invoiced_item_VAT_category_code" id="BT-151">TVA</entry>
<entry key="xr:Invoiced_item_VAT_rate" id="BT-152">Taux de TVA</entry>
<entry key="xr:Item_name" id="BT-153">Désignation</entry>
<entry key="xr:Item_description" id="BT-154">Description</entry>
<entry key="xr:Item_Sellers_identifier" id="BT-155">Référence article</entry>
<entry key="xr:Item_Buyers_identifier" id="BT-156">Référence article acheteur</entry>
<entry key="xr:Item_standard_identifier" id="BT-157">Identifiant article</entry>
<entry key="xr:Item_standard_identifier/@scheme_identifier" class="BT-157_scheme">Schéma de l'identifiant article</entry>
<entry key="xr:Item_classification_identifier" id="BT-158">Code de classification article</entry>
<entry key="xr:Item_classification_identifier/@scheme_identifier" id="BT-158_scheme">Identifiant du schéma</entry>
<entry key="xr:Item_classification_identifier/@scheme_version_identifier" id="BT-158_scheme_version">Version du schéma</entry>
<entry key="xr:Item_country_of_origin" id="BT-159">Code du pays d'origine</entry>
<entry key="xr:Invoice_line_allowance_base_amount" id="BT-137">Montant de base (HT)</entry>
<entry key="xr:Invoice_line_allowance_percentage" id="BT-138">Pourcentage</entry>
<entry key="xr:Invoice_line_allowance_amount" id="BT-136">Remise (HT)</entry>
<entry key="xr:Invoice_line_allowance_reason" id="BT-139">Motif de la remise</entry>
<entry key="xr:Invoice_line_allowance_reason_code" id="BT-140">Code du motif de la remise</entry>
<entry key="xr:Invoice_line_charge_base_amount" id="BT-142">Montant de base (HT)</entry>
<entry key="xr:Invoice_line_charge_percentage" id="BT-143">Pourcentage</entry>
<entry key="xr:Invoice_line_charge_amount" id="BT-141">Frais (HT)</entry>
<entry key="xr:Invoice_line_charge_reason" id="BT-144">Motif des frais</entry>
<entry key="xr:Invoice_line_charge_reason_code" id="BT-145">Code du motif des frais</entry>
<entry key="xr:Third_party_payment_type" id="BT-DEX-001">Type de créance tierce</entry>
<entry key="xr:Third_party_payment_amount" id="BT-DEX-002">Montant de la créance tierce</entry>
<entry key="xr:Third_party_payment_description" id="BT-DEX-003">Description de la créance tierce</entry>
<entry key="uebersicht">Données de la facture électronique</entry>
<entry key="uebersichtKaeufer" id="BG-7">Informations sur l'acheteur</entry>
<entry key="uebersichtVerkaeufer" id="BG-4">Informations sur le vendeur</entry>
<entry key="uebersichtRechnungsInfo" id="invoice-data">Données de facturation</entry>
<entry key="uebersichtRechnungsuebersicht" id="BG-22">Totaux de la facture</entry>
<entry key="uebersichtFremdleistungen" id="BG-DEX-09">Créance tierce</entry>
<entry key="uebersichtUmsatzsteuer" id="BG-23">Ventilation de la TVA au niveau de la facture</entry>
<entry key="uebersichtNachlass" id="BG-20">Remise au niveau de la facture</entry>
<entry key="uebersichtZuschlaege" id="BG-21">Frais au niveau de la facture</entry>
<entry key="uebersichtRechnungAbrechnungszeitraum">Période de facturation</entry>
<entry key="uebersichtRechnungVorausgegangeneRechnungen" id="BG-3">Factures précédentes</entry>
<entry key="uebersichtZahlungInfo" id="BG-4">Informations de paiement</entry>
<entry key="uebersichtZahlungKarte" id="BG-18">Informations carte</entry>
<entry key="uebersichtZahlungLastschrift" id="BG-19">Prélèvement</entry>
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Virement</entry>
<entry key="uebersichtBemerkungen" id="BG-1">Remarques sur la facture</entry>
<entry key="details">Lignes de facture</entry>
<entry key="detailsPositionAbrechnungszeitraum" id="BG-26">Période de facturation</entry>
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Détails du prix</entry>
<entry key="detailsPositionNachlaesse" id="BG-27">Remises au niveau de la ligne de facture</entry>
<entry key="detailsPositionZuschlaege" id="BG-28">Frais au niveau de la ligne de facture</entry>
<entry key="detailsPositionArtikelinformationen" id="BG-31">Informations sur l'article</entry>
<entry key="detailsPositionArtikeleigenschaften" id="BG-32">Caractéristiques de l'article</entry>
<entry key="zusaetze">Compléments</entry>
<entry key="zusaetzeVerkaeufer" id="BG-4">Informations sur le vendeur</entry>
<entry key="zusaetzeSteuervertreter" id="BG-11">Représentant fiscal du vendeur</entry>
<entry key="zusaetzeKaeufer" id="BG-7">Informations sur l'acheteur</entry>
<entry key="zusaetzeLieferung" id="BG-13">Informations de livraison</entry>
<entry key="zusaetzeVertrag">Informations contractuelles</entry>
<entry key="zusaetzeZahlungsempfaenger" id="BG-10">Bénéficiaire différent du vendeur</entry>
<entry key="laufzettel">Fiche de suivi</entry>
<entry key="laufzettelHistorie">Historique de traitement</entry>
<entry key="anlagen">Pièces jointes</entry>
<entry key="anlagenListe">Documents justificatifs</entry>
<entry key="date-format">[D]/[M]/[Y]</entry>
<entry key="datetime-format">[D]/[M]/[Y] [H]:[m]:[s]</entry>
<entry key="amount-format">###.##0,00</entry>
<entry key="percentage-format">##0,00</entry>
<entry key="at-least-two-format">###.##0,#################</entry>
<entry key="sum-of-third-party-payment-amounts" id="BT-DEX-002_sum">Total des créances tierces</entry>
<entry key="_disclaimer">Nous déclinons toute responsabilité quant à l'exactitude des données.</entry>
<entry key="_invoice-note-group">Remarques sur la facture</entry>
<entry key="_open">Ouvrir</entry>
<entry key="no-data">Aucune donnée disponible</entry>
<entry key="_net">HT</entry>
<entry key="_gross">TTC</entry>
<entry key="_no-content">Les sections sans contenu ne sont pas affichées !</entry>
<entry key="_description">Description</entry>
<entry key="_price">Prix</entry>
<entry key="_price-unit">Unité de prix</entry>
<entry key="_vat">Taux TVA</entry>
<entry key="_tax-code">Code taxe</entry>
<entry key="_total">Total</entry>
<entry key="_page">Page</entry>
</properties>

View File

@@ -85,6 +85,37 @@
font-weight="bold">
<xsl:text>Prüfbericht</xsl:text>
</fo:block>
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="'Angaben zur Prüfanwendung'"/>
<xsl:with-param name="color"
select="'black'"/>
</xsl:call-template>
<fo:table>
<fo:table-column border-style="none"/>
<fo:table-column column-width="70%"
border-style="none"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block text-align="left">Name:</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="right">mustangproject.org</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<fo:block text-align="left">Version:</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block text-align="right">
<xsl:value-of select="./xml/info/validator/@version"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="'Angaben zum geprüften Dokument'"/>

View File

@@ -8,6 +8,7 @@
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions">
<xsl:decimal-format name="de" decimal-separator="," grouping-separator="." NaN="" />
<xsl:decimal-format name="fr" decimal-separator="," grouping-separator="." NaN="" />
<xsl:decimal-format name="en" decimal-separator="." grouping-separator="," NaN="" />
<xsl:template name="uebersicht">

View File

@@ -0,0 +1,64 @@
package org.mustangproject.ZUGFeRD;
import junit.framework.TestCase;
import org.mustangproject.Invoice;
import org.mustangproject.Item;
import org.mustangproject.Product;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.TradeParty;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import static org.xmlunit.assertj.XmlAssert.assertThat;
public class BT18ReferenceTypeCodeTest extends TestCase {
/**
* Ensures BT-18-1 (scheme identifier) is serialized as ram:ReferenceTypeCode
* inside ram:AdditionalReferencedDocument (TypeCode=130).
*/
public void testBT18ReferenceTypeCodeIsWrittenWhenSet() {
// Minimal invoice (XRechnung profile => CII output)
Invoice i = new Invoice()
.setIssueDate(new Date())
.setDueDate(new Date())
.setDeliveryDate(new Date())
.setSender(new TradeParty("Test company", "teststr", "55232", "teststadt", "DE"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE"))
.setNumber("INV-123")
.addItem(new Item(
new Product("Testprodukt", "", "C62", BigDecimal.ZERO),
new BigDecimal("1.00"),
BigDecimal.ONE
));
// BT-18: Invoiced Object Identifier (AdditionalReferencedDocument / TypeCode 130)
ReferencedDocument rd = new ReferencedDocument("ID unique");
rd.setReferenceTypeCode("AJW"); // BT-18-1
i.setObjectIdentifierReferencedDocument(rd);
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
zf2p.setProfile(Profiles.getByName("XRechnung"));
zf2p.generateXML(i);
String xml = new String(zf2p.getXML(), StandardCharsets.UTF_8);
// Assert: we still have a CII invoice
assertTrue(xml.contains("<rsm:CrossIndustryInvoice"));
// Assert: BT-18 block contains ID, TypeCode=130 and ReferenceTypeCode=AJW
assertThat(xml)
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='TypeCode'])")
.isEqualTo("130");
assertThat(xml)
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='IssuerAssignedID'])")
.isEqualTo("ID unique");
assertThat(xml)
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='ReferenceTypeCode'])")
.isEqualTo("AJW");
}
}

View File

@@ -0,0 +1,121 @@
package org.mustangproject.ZUGFeRD;
import org.junit.jupiter.api.Test;
import org.mustangproject.ZUGFeRD.Profiles;
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider;
import org.mustangproject.Invoice;
import org.mustangproject.Item;
import org.mustangproject.Product;
import org.mustangproject.TradeParty;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for BT-23 (BusinessProcessSpecifiedDocumentContextParameter/ID) emission in CII output.
*
* Assumptions / prerequisites for the proposed fix:
* - IExportableTransaction#getBusinessProcessId() exists (as a default method returning null)
* - Invoice#setBusinessProcessId(String) exists and Invoice overrides getBusinessProcessId()
* - ZUGFeRD2PullProvider writes BT-23 if a custom businessProcessId is provided,
* otherwise keeps the current XRechnung default behavior.
*/
public class BusinessProcessIdCiiTest {
@Test
void xrechnung_nonRegression_defaultBusinessProcessId_isStillEmitted() throws Exception {
// ===== GIVEN: A minimal invoice (as provided) WITHOUT setting businessProcessId =====
TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR");
TradeParty seller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR");
Invoice invoice = new Invoice()
.setNumber("INV-CALC-001") // BT-1: Invoice number -> /.../ExchangedDocument/ram:ID
.setIssueDate(new Date()) // BT-2: Issue date
.setDeliveryDate(new Date()) // Delivery date (depending on profile/exporter mapping)
.setDueDate(new Date()) // BT-9: Payment due date
.setCurrency("EUR") // BT-5: Invoice currency code
.setSender(buyer)
.setRecipient(seller);
Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00"));
invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20)));
// ===== WHEN: Generating CII XML using the XRechnung profile =====
ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider();
provider.setProfile(Profiles.getByName("XRechnung"));
provider.generateXML(invoice);
String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
// ===== THEN: Non-regression check =====
// XRechnung currently emits BT-23 with a default fixed URN.
// This behavior must remain unchanged when businessProcessId is NOT explicitly provided by the user.
assertTrue(
xml.contains("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
"XRechnung should still emit BusinessProcessSpecifiedDocumentContextParameter (BT-23)."
);
assertTrue(
xml.contains("<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>"),
"XRechnung default BT-23 URN must remain unchanged when businessProcessId is not set."
);
}
@Test
void whenBusinessProcessIdIsSet_itIsEmitted_inExtendedProfile() throws Exception {
// ===== GIVEN: The same minimal invoice, but WITH an explicit businessProcessId =====
TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR");
TradeParty seller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR");
Invoice invoice = new Invoice()
.setNumber("INV-CALC-001")
.setIssueDate(new Date())
.setDeliveryDate(new Date())
.setDueDate(new Date())
.setCurrency("EUR")
.setSender(buyer)
.setRecipient(seller);
Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00"));
invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20)));
// Proposed new API: user-provided BT-23 value
invoice.setBusinessProcessId("B1");
// BT-23 path:
// /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/
// ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID
// ===== WHEN: Generating CII XML using the EXTENDED profile =====
ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider();
provider.setProfile(Profiles.getByName("EXTENDED"));
provider.generateXML(invoice);
String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
// ===== THEN: BT-23 must be emitted with the user-provided value =====
assertTrue(
xml.contains("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
"When businessProcessId is set, BT-23 must be emitted even for EXTENDED."
);
assertTrue(
xml.contains("<ram:ID>B1</ram:ID>"),
"BT-23 must contain the user-provided businessProcessId."
);
// Extra sanity check: the guideline/specification identifier (BT-24) should still be present
assertTrue(
xml.contains("<ram:GuidelineSpecifiedDocumentContextParameter>"),
"BT-24 (GuidelineSpecifiedDocumentContextParameter) must be present."
);
}
}

View File

@@ -0,0 +1,178 @@
package org.mustangproject.ZUGFeRD;
import junit.framework.TestCase;
import org.mustangproject.Invoice;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import javax.xml.xpath.XPathExpressionException;
/* https://github.com/ZUGFeRD/mustangproject/issues/993 */
public class CashDiscountFacturXTest extends TestCase {
String XMLString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
" <rsm:CrossIndustryInvoice xmlns:rsm=\"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ram=\"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100\" xmlns:udt=\"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100\" xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">\n" +
" <!-- generated by: mustangproject.org v2.15.1-SNAPSHOT-->\n" +
" <rsm:ExchangedDocumentContext>\n" +
" <ram:GuidelineSpecifiedDocumentContextParameter>\n" +
" <ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID>\n" +
" </ram:GuidelineSpecifiedDocumentContextParameter>\n" +
" </rsm:ExchangedDocumentContext>\n" +
" <rsm:ExchangedDocument>\n" +
" <ram:ID>471102</ram:ID>\n" +
" <ram:TypeCode>380</ram:TypeCode>\n" +
" <ram:IssueDateTime>\n" +
" <udt:DateTimeString format=\"102\">20260201</udt:DateTimeString>\n" +
" </ram:IssueDateTime>\n" +
" </rsm:ExchangedDocument>\n" +
" <rsm:SupplyChainTradeTransaction>\n" +
" <ram:IncludedSupplyChainTradeLineItem>\n" +
" <ram:AssociatedDocumentLineDocument>\n" +
" <ram:LineID>1</ram:LineID>\n" +
" </ram:AssociatedDocumentLineDocument>\n" +
" <ram:SpecifiedTradeProduct>\n" +
" <ram:Name>Trennblätter A4</ram:Name>\n" +
" </ram:SpecifiedTradeProduct>\n" +
" <ram:SpecifiedLineTradeAgreement>\n" +
" <ram:NetPriceProductTradePrice>\n" +
" <ram:ChargeAmount>9.9000</ram:ChargeAmount>\n" +
" <ram:BasisQuantity unitCode=\"H87\">1.0000</ram:BasisQuantity>\n" +
" </ram:NetPriceProductTradePrice>\n" +
" </ram:SpecifiedLineTradeAgreement>\n" +
" <ram:SpecifiedLineTradeDelivery>\n" +
" <ram:BilledQuantity unitCode=\"H87\">20.0000</ram:BilledQuantity>\n" +
" </ram:SpecifiedLineTradeDelivery>\n" +
" <ram:SpecifiedLineTradeSettlement>\n" +
" <ram:ApplicableTradeTax>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
" <ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>\n" +
" </ram:ApplicableTradeTax>\n" +
" <ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
" <ram:LineTotalAmount>198.00</ram:LineTotalAmount>\n" +
" </ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
" </ram:SpecifiedLineTradeSettlement>\n" +
" </ram:IncludedSupplyChainTradeLineItem>\n" +
" <ram:IncludedSupplyChainTradeLineItem>\n" +
" <ram:AssociatedDocumentLineDocument>\n" +
" <ram:LineID>2</ram:LineID>\n" +
" </ram:AssociatedDocumentLineDocument>\n" +
" <ram:SpecifiedTradeProduct>\n" +
" <ram:Name>Joghurt Banane</ram:Name>\n" +
" </ram:SpecifiedTradeProduct>\n" +
" <ram:SpecifiedLineTradeAgreement>\n" +
" <ram:NetPriceProductTradePrice>\n" +
" <ram:ChargeAmount>5.5000</ram:ChargeAmount>\n" +
" <ram:BasisQuantity unitCode=\"H87\">1.0000</ram:BasisQuantity>\n" +
" </ram:NetPriceProductTradePrice>\n" +
" </ram:SpecifiedLineTradeAgreement>\n" +
" <ram:SpecifiedLineTradeDelivery>\n" +
" <ram:BilledQuantity unitCode=\"H87\">50.0000</ram:BilledQuantity>\n" +
" </ram:SpecifiedLineTradeDelivery>\n" +
" <ram:SpecifiedLineTradeSettlement>\n" +
" <ram:ApplicableTradeTax>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
" <ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>\n" +
" </ram:ApplicableTradeTax>\n" +
" <ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
" <ram:LineTotalAmount>275.00</ram:LineTotalAmount>\n" +
" </ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
" </ram:SpecifiedLineTradeSettlement>\n" +
" </ram:IncludedSupplyChainTradeLineItem>\n" +
" <ram:ApplicableHeaderTradeAgreement>\n" +
" <ram:SellerTradeParty>\n" +
" <ram:Name>Lieferant GmbH</ram:Name>\n" +
" <ram:PostalTradeAddress>\n" +
" <ram:PostcodeCode>80333</ram:PostcodeCode>\n" +
" <ram:LineOne>Lieferantenstraße 20</ram:LineOne>\n" +
" <ram:CityName>München</ram:CityName>\n" +
" <ram:CountryID>DE</ram:CountryID>\n" +
" </ram:PostalTradeAddress>\n" +
" <ram:SpecifiedTaxRegistration>\n" +
" <ram:ID schemeID=\"VA\">DE123456789</ram:ID>\n" +
" </ram:SpecifiedTaxRegistration>\n" +
" <ram:SpecifiedTaxRegistration>\n" +
" <ram:ID schemeID=\"FC\">201/113/40209</ram:ID>\n" +
" </ram:SpecifiedTaxRegistration>\n" +
" </ram:SellerTradeParty>\n" +
" <ram:BuyerTradeParty>\n" +
" <ram:Name>Kunden AG Mitte</ram:Name>\n" +
" <ram:PostalTradeAddress>\n" +
" <ram:PostcodeCode>69876</ram:PostcodeCode>\n" +
" <ram:LineOne>Kundenstraße 15</ram:LineOne>\n" +
" <ram:CityName>Frankfurt</ram:CityName>\n" +
" <ram:CountryID>DE</ram:CountryID>\n" +
" </ram:PostalTradeAddress>\n" +
" </ram:BuyerTradeParty>\n" +
" </ram:ApplicableHeaderTradeAgreement>\n" +
" <ram:ApplicableHeaderTradeDelivery>\n" +
" <ram:ActualDeliverySupplyChainEvent>\n" +
" <ram:OccurrenceDateTime>\n" +
" <udt:DateTimeString format=\"102\">20180304</udt:DateTimeString>\n" +
" </ram:OccurrenceDateTime>\n" +
" </ram:ActualDeliverySupplyChainEvent>\n" +
" </ram:ApplicableHeaderTradeDelivery>\n" +
" <ram:ApplicableHeaderTradeSettlement>\n" +
" <ram:PaymentReference>471102</ram:PaymentReference>\n" +
" <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>\n" +
" <ram:ApplicableTradeTax>\n" +
" <ram:CalculatedAmount>19.25</ram:CalculatedAmount>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:BasisAmount>275.00</ram:BasisAmount>\n" +
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
" <ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>\n" +
" </ram:ApplicableTradeTax>\n" +
" <ram:ApplicableTradeTax>\n" +
" <ram:CalculatedAmount>37.62</ram:CalculatedAmount>\n" +
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
" <ram:BasisAmount>198.00</ram:BasisAmount>\n" +
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
" <ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>\n" +
" </ram:ApplicableTradeTax>\n" +
" <ram:SpecifiedTradePaymentTerms>\n" +
" <ram:Description>Please remit until 04.03.2018</ram:Description>\n" +
" <ram:DueDateDateTime>\n" +
" <udt:DateTimeString format=\"102\">20180304</udt:DateTimeString>\n" +
" </ram:DueDateDateTime>\n" +
" </ram:SpecifiedTradePaymentTerms>\n" +
" <ram:SpecifiedTradePaymentTerms>\n" +
" <ram:Description>Bis zum 15.02.2026 erhalten Sie 4,000 % Skonto, 21.19 EUR</ram:Description>\n" +
" <ram:DueDateDateTime>\n" +
" <udt:DateTimeString format=\"102\">20260215</udt:DateTimeString>\n" +
" </ram:DueDateDateTime>\n" +
" <ram:ApplicableTradePaymentDiscountTerms>\n" +
" <ram:BasisAmount>529.8700</ram:BasisAmount>\n" +
" <ram:CalculationPercent>4.00</ram:CalculationPercent>\n" +
" <ram:ActualDiscountAmount>21.19</ram:ActualDiscountAmount>\n" +
" </ram:ApplicableTradePaymentDiscountTerms>\n" +
" </ram:SpecifiedTradePaymentTerms>\n" +
" <ram:SpecifiedTradePaymentTerms>\n" +
" <ram:Description>Bis zum 16.02.2026 ohne Abzug</ram:Description>\n" +
" <ram:DueDateDateTime>\n" +
" <udt:DateTimeString format=\"102\">20260216</udt:DateTimeString>\n" +
" </ram:DueDateDateTime>\n" +
" </ram:SpecifiedTradePaymentTerms>\n" +
" <ram:SpecifiedTradeSettlementHeaderMonetarySummation>\n" +
" <ram:LineTotalAmount>473.00</ram:LineTotalAmount>\n" +
" <ram:ChargeTotalAmount>0.00</ram:ChargeTotalAmount>\n" +
" <ram:AllowanceTotalAmount>0.00</ram:AllowanceTotalAmount>\n" +
" <ram:TaxBasisTotalAmount>473.00</ram:TaxBasisTotalAmount>\n" +
" <ram:TaxTotalAmount currencyID=\"EUR\">56.87</ram:TaxTotalAmount>\n" +
" <ram:GrandTotalAmount>529.87</ram:GrandTotalAmount>\n" +
" <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount>\n" +
" <ram:DuePayableAmount>529.87</ram:DuePayableAmount>\n" +
" </ram:SpecifiedTradeSettlementHeaderMonetarySummation>\n" +
" </ram:ApplicableHeaderTradeSettlement>\n" +
" </rsm:SupplyChainTradeTransaction>\n" +
" </rsm:CrossIndustryInvoice>";
public void testCashDiscount() throws XPathExpressionException, ParseException {
InputStream inputStream = new ByteArrayInputStream(XMLString.getBytes(StandardCharsets.UTF_8));
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(inputStream);
Invoice invoice = zii.extractInvoice();
assertEquals(new BigDecimal("4.00"), invoice.getCashDiscounts()[0].getPercent());
}
}

View File

@@ -23,7 +23,11 @@ import junit.framework.TestSuite;
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.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
@@ -218,8 +222,6 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
zi.doRecalculateItemPricesFromLineTotals();
zi.doIgnoreCalculationErrors();
zi.setInputStream(inputStream);
// Reading ZUGFeRD
String amount = zi.getAmount();
assertEquals("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" +
" <x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" +
@@ -297,8 +299,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
" </x:xmpmeta>\n" +
"<?xpacket end=\"r\"?>", zi.getXMP());
// this resembles the data written in MustangReaderWriterCustomXMLTest
assertEquals(amount, "1005.55");
assertEquals("845.00", zi.getTaxBasisTotalAmount());
assertEquals("1005.55", zi.getAmount());
}
@@ -415,17 +417,18 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos);
ze.close();
String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0);
assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0);
byte[] targetBytes = baos.toByteArray();
String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "ZUGFeRD-invoice.xml");
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.BASIC));
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.EXTENDED));
assertTrue(xmlContent.contains(DocumentContextParameterTypeConstants.COMFORT));
} catch (IOException e) {
fail("IOException should not happen in testZExport");
}
}
public void testFXExport() throws Exception {
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20171118_506fx.pdf";
@@ -442,7 +445,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
result.write(buffer, 0, length);
}
ze.addAdditionalFile("test.pdf", result.toByteArray());
var bytes = result.toByteArray();
ze.addAdditionalFile("test.pdf", bytes);
ze.setTransaction(this);
ze.disableAutoClose(true);
@@ -451,12 +455,14 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos);
ze.close();
String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
byte[] targetBytes = baos.toByteArray();
String pdfContent = new String(targetBytes, StandardCharsets.UTF_8);
String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "factur-x.xml");
assertTrue(pdfContent.contains("(via mustangproject.org"));
// check for pdf-a schema extension
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);
assertFalse(pdfContent.indexOf("<pdfaSchema:prefix>fx</pdfaSchema:prefix>") == -1);
assertFalse(pdfContent.indexOf("urn:cen.eu:en16931:2017") == -1);
assertTrue(pdfContent.contains("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>"));
assertTrue(pdfContent.contains("<pdfaSchema:prefix>fx</pdfaSchema:prefix>"));
assertTrue(xmlContent.contains("urn:cen.eu:en16931:2017"));
}
// now check the contents (like MustangReaderTest)
@@ -470,6 +476,26 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
assertEquals(getNumber(), zi.getForeignReference());
}
private static String readEmbeddedXmlFromPdf(byte[] targetBytes, String xmlFilename) throws IOException {
// Read attached (compressed) XML file
try (PDDocument pdDocument = Loader.loadPDF(targetBytes)) {
PDDocumentNameDictionary names = new PDDocumentNameDictionary(pdDocument.getDocumentCatalog());
PDEmbeddedFilesNameTreeNode embeddedFiles = names.getEmbeddedFiles();
Map<String, PDComplexFileSpecification> fileMap = embeddedFiles.getNames();
PDComplexFileSpecification fileSpec = fileMap.get(xmlFilename);
if (fileSpec == null) {
fail("Files does not contain a file named " + xmlFilename);
return null;
}
PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
String xmlContent = new String(embeddedFile.toByteArray(), StandardCharsets.UTF_8);
return xmlContent;
}
}
public void testExceptionOnPDF14() {
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf";

View File

@@ -0,0 +1,154 @@
package org.mustangproject.ZUGFeRD;
import org.junit.jupiter.api.Test;
import org.mustangproject.*;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.*;
public class SubInvoiceLineExportTest {
private static final String TARGET_PDF = "./target/testout-SubInvoiceLines.pdf";
@Test
public void testSubInvoiceLineExportRoundTrip() throws Exception {
// -- Build invoice with hierarchical positions --
TradeParty sender = new TradeParty("Test Seller", "Test Street 1", "12345", "Test City", "DE")
.addTaxID("DE123456789")
.addVATID("DE123456789");
Contact senderContact = new Contact("Max Mustermann", "012345", "max@test.de");
sender.setContact(senderContact);
TradeParty recipient = new TradeParty("Test Buyer", "Buyer Street 1", "54321", "Buyer City", "DE")
.addVATID("DE987654321");
// DETAIL line 0101: child of group 01
Item detail0101 = new Item(
new Product("Laser printer", "", "H87", new BigDecimal("19")),
new BigDecimal("300.00"),
new BigDecimal("2.0000")
);
detail0101.setId("0101");
detail0101.setParentLineID("01");
detail0101.setLineStatusReasonCode("DETAIL");
// DETAIL line 0102: child of group 01
Item detail0102 = new Item(
new Product("Ink printer", "", "H87", new BigDecimal("19")),
new BigDecimal("150.00"),
new BigDecimal("3.0000")
);
detail0102.setId("0102");
detail0102.setParentLineID("01");
detail0102.setLineStatusReasonCode("DETAIL");
// INFORMATION line 0103: child of group 01, non-calculation-relevant
Item info0103 = new Item(
new Product("Setup instructions", "", "H87", new BigDecimal("19")),
new BigDecimal("0.00"),
new BigDecimal("1.0000")
);
info0103.setId("0103");
info0103.setParentLineID("01");
info0103.setLineStatusReasonCode("INFORMATION");
// GROUP line 01: parent, LineTotalAmount = sum of DETAIL children = 1050
Item group01 = new Item(
new Product("Hardware bundle", "", "H87", new BigDecimal("19")),
new BigDecimal("1050.00"),
new BigDecimal("1.0000")
);
group01.setId("01");
group01.setLineStatusReasonCode("GROUP");
Invoice invoice = new Invoice()
.setNumber("SUB-INV-001")
.setIssueDate(new Date())
.setDeliveryDate(new Date())
.setDueDate(new Date())
.setSender(sender)
.setRecipient(recipient)
.setCurrency("EUR")
.setPaymentTermDescription("Net 30")
.addItem(detail0101)
.addItem(detail0102)
.addItem(info0103)
.addItem(group01);
// -- Export to PDF with EXTENDED profile --
try (InputStream sourcePDF = this.getClass()
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
ZUGFeRDExporterFromA3 exporter = new ZUGFeRDExporterFromA3()
.setProducer("Mustang SubInvoiceLine Test")
.setCreator("Test")
.setZUGFeRDVersion(2)
.setProfile("EXTENDED")
.load(sourcePDF)) {
exporter.setTransaction(invoice);
// Verify XML contains the hierarchical elements
String xml = new String(exporter.getProvider().getXML(), StandardCharsets.UTF_8);
assertTrue(xml.contains("<ram:ParentLineID>01</ram:ParentLineID>"),
"XML must contain ParentLineID element");
assertTrue(xml.contains("<ram:LineStatusReasonCode>DETAIL</ram:LineStatusReasonCode>"),
"XML must contain DETAIL LineStatusReasonCode");
assertTrue(xml.contains("<ram:LineStatusReasonCode>GROUP</ram:LineStatusReasonCode>"),
"XML must contain GROUP LineStatusReasonCode");
assertTrue(xml.contains("<ram:LineStatusReasonCode>INFORMATION</ram:LineStatusReasonCode>"),
"XML must contain INFORMATION LineStatusReasonCode");
exporter.export(TARGET_PDF);
}
// -- Re-import and verify round-trip --
ZUGFeRDImporter importer = new ZUGFeRDImporter(TARGET_PDF);
Invoice reimported = new Invoice();
importer.extractInto(reimported);
// Find items by ID
Item rDetail0101 = findItemById(reimported, "0101");
assertNotNull(rDetail0101, "DETAIL item 0101 must be reimported");
assertEquals("01", rDetail0101.getParentLineID());
assertEquals("DETAIL", rDetail0101.getLineStatusReasonCode());
assertTrue(rDetail0101.isCalculationRelevant());
Item rDetail0102 = findItemById(reimported, "0102");
assertNotNull(rDetail0102, "DETAIL item 0102 must be reimported");
assertEquals("01", rDetail0102.getParentLineID());
assertEquals("DETAIL", rDetail0102.getLineStatusReasonCode());
assertTrue(rDetail0102.isCalculationRelevant());
Item rInfo0103 = findItemById(reimported, "0103");
assertNotNull(rInfo0103, "INFORMATION item 0103 must be reimported");
assertEquals("01", rInfo0103.getParentLineID());
assertEquals("INFORMATION", rInfo0103.getLineStatusReasonCode());
assertFalse(rInfo0103.isCalculationRelevant());
Item rGroup01 = findItemById(reimported, "01");
assertNotNull(rGroup01, "GROUP item 01 must be reimported");
assertNull(rGroup01.getParentLineID());
assertEquals("GROUP", rGroup01.getLineStatusReasonCode());
assertFalse(rGroup01.isCalculationRelevant());
// Verify only DETAIL lines contribute to totals
// DETAIL: 0101=600, 0102=450 = 1050
TransactionCalculator tc = new TransactionCalculator(reimported);
assertEquals(new BigDecimal("1050.00"), tc.getTotal().setScale(2));
}
private Item findItemById(Invoice invoice, String id) {
for (IZUGFeRDExportableItem item : invoice.getZFItems()) {
if (item instanceof Item && id.equals(((Item) item).getId())) {
return (Item) item;
}
}
return null;
}
}

View File

@@ -1,176 +1,210 @@
/**
* *********************************************************************
* <p>
* Copyright 2019 Jochen Staerk
* <p>
* Use is subject to license terms.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* <p>
* See the License for the specific language governing permissions and
* limitations under the License.
* <p>
* **********************************************************************
*/
package org.mustangproject.ZUGFeRD;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language;
import org.mustangproject.util.ByteArraySearcher;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class VisualizationTest extends ResourceCase {
final String TARGET_PDF_CII = "./target/testout-Visualization-cii.pdf";
final String TARGET_PDF_UBL = "./target/testout-Visualization-cii.pdf";
public void testCIIVisualizationBasic() {
this.runZUGFeRDVisualization("factur-x.xml", "factur-x-vis.fr.html", Language.FR);
}
public void testCIIVisualizationExtended() {
this.runZUGFeRDVisualization("factur-x-extended.xml", "factur-x-vis-extended.de.html", Language.DE);
}
public void testUBLCreditNoteVisualizationBasic() {
this.runZUGFeRDVisualization("ubl-creditnote.xml", "factur-x-vis-ubl-creditnote.en.html", Language.EN);
}
public void testUBLVisualizationBasic() {
this.runZUGFeRDVisualization("ubl/01.01a-INVOICE.ubl.xml", "factur-x-vis-ubl.en.html", Language.EN);
}
private void runZUGFeRDVisualization(String inputFilename, String resultFileName, Language lang) {
File CIIinputFile = getResourceAsFile(inputFilename);
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
result = zvi.visualize(CIIinputFile.getAbsolutePath(), lang);
Files.write(Paths.get("./target/testout-" + resultFileName), result.getBytes(StandardCharsets.UTF_8));
File expectedResult = getResourceAsFile(resultFileName);
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8)
;
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
} catch (TransformerException e) {
fail("TransformerException should not happen: " + e.getMessage());
} catch (IOException e) {
fail("IOException should not happen: " + e.getMessage());
} catch (ParserConfigurationException e) {
fail("ParserConfigurationException should not happen: " + e.getMessage());
}
assertNotNull(result);
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
assertEquals(expected.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", ""), result.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", ""));
}
public void testPDFVisualizationCII() {
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
// the writing part
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationUBL() {
File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.xml");
// the writing part
String sourceFilename = "factur-x.xml";
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationUBLCreditNote() {
File UBLinputFile = getResourceAsFile("ubl/UBL-CreditNote-2.1-Example.ubl.xml");
// the writing part
String sourceFilename = "factur-x.xml";
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
} catch (IOException e) {
fail("IOException should not occur");
}
}
}
/**
* *********************************************************************
* <p>
* Copyright 2019 Jochen Staerk
* <p>
* Use is subject to license terms.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* <p>
* See the License for the specific language governing permissions and
* limitations under the License.
* <p>
* **********************************************************************
*/
package org.mustangproject.ZUGFeRD;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language;
import org.mustangproject.util.ByteArraySearcher;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class VisualizationTest extends ResourceCase {
final String TARGET_PDF_CII = "./target/testout-Visualization-cii.pdf";
final String TARGET_PDF_UBL = "./target/testout-Visualization-cii.pdf";
public void testCIIVisualizationBasic() {
this.runZUGFeRDVisualization("factur-x.xml", "factur-x-vis.fr.html", Language.FR);
}
public void testCIIVisualizationExtended() {
this.runZUGFeRDVisualization("factur-x-extended.xml", "factur-x-vis-extended.de.html", Language.DE);
}
public void testUBLCreditNoteVisualizationBasic() {
this.runZUGFeRDVisualization("ubl-creditnote.xml", "factur-x-vis-ubl-creditnote.en.html", Language.EN);
}
public void testUBLVisualizationBasic() {
this.runZUGFeRDVisualization("ubl/01.01a-INVOICE.ubl.xml", "factur-x-vis-ubl.en.html", Language.EN);
}
private void runZUGFeRDVisualization(String inputFilename, String resultFileName, Language lang) {
File CIIinputFile = getResourceAsFile(inputFilename);
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
result = zvi.visualize(CIIinputFile.getAbsolutePath(), lang);
Files.write(Paths.get("./target/testout-" + resultFileName), result.getBytes(StandardCharsets.UTF_8));
File expectedResult = getResourceAsFile(resultFileName);
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8)
;
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
} catch (TransformerException e) {
fail("TransformerException should not happen: " + e.getMessage());
} catch (IOException e) {
fail("IOException should not happen: " + e.getMessage());
} catch (ParserConfigurationException e) {
fail("ParserConfigurationException should not happen: " + e.getMessage());
}
assertNotNull(result);
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
assertEquals(expected.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", ""), result.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", ""));
}
public void testPDFVisualizationCII() {
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "<rdf:li>de</rdf:li>".getBytes()));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationCIIEnglish() {
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII, Language.EN);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "<rdf:li>en</rdf:li>".getBytes()));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationCIIFrench() {
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII, Language.FR);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "<rdf:li>fr</rdf:li>".getBytes()));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationUBL() {
File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.xml");
// the writing part
String sourceFilename = "factur-x.xml";
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
} catch (IOException e) {
fail("IOException should not occur");
}
}
public void testPDFVisualizationUBLCreditNote() {
File UBLinputFile = getResourceAsFile("ubl/UBL-CreditNote-2.1-Example.ubl.xml");
// the writing part
String sourceFilename = "factur-x.xml";
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL);
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
} catch (IllegalArgumentException e) {
fail("IllegalArgumentException should not happen: " + e.getMessage());
}
try {
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
} catch (IOException e) {
fail("IOException should not occur");
}
}
}

View File

@@ -544,7 +544,7 @@ public class ZF2PushTest extends TestCase {
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
.addCharge(new Charge(new BigDecimal(0.5)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"))
.addAllowance(new Allowance(new BigDecimal(0.2)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"))
.addAllowance(new Allowance(new BigDecimal(0.2)).setTaxPercent(new BigDecimal(19)).setReasonCode("95"))
);
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
@@ -598,7 +598,7 @@ public class ZF2PushTest extends TestCase {
ReferencedDocument dr1=new ReferencedDocument("90-kl-98798-C", sdf.parse("2025-10-12"));
ReferencedDocument dr2=new ReferencedDocument("90-kl-98798-C1", sdf.parse("2025-10-13"));
dr2.setReferenceTypeCode("AAG");
ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setPaymentReference("Verwendungszweck").setDocumentName("Rechnung")
ze.setTransaction(new Invoice().setTestIndicator().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setPaymentReference("Verwendungszweck").setDocumentName("Rechnung")
.setSellerOrderReferencedDocumentID("9384").setBuyerOrderReferencedDocumentID("28934")
.setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo))
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setEmail("sender@test.org").setID(orgID).addVATID("DE0815"))
@@ -628,6 +628,7 @@ public class ZF2PushTest extends TestCase {
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
assertTrue(theXML.contains("<ram:TestIndicator"));
ze.export(TARGET_PUSHEDGE);
} catch (IOException e) {
fail("IOException should not be raised");
@@ -761,8 +762,9 @@ public class ZF2PushTest extends TestCase {
.setNumber(number)
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"))
.addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt"))
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0)))
.addCharge(new Charge().setPercent(new BigDecimal(50)).setBasisAmount(price).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK").setReason("Verschiedenes"))
.addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("95").setReason("Mengenrabatt"))
);
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));