Merge branch 'ZUGFeRD:master' into fix-logback

This commit is contained in:
Adrian-Devries
2025-01-06 11:41:17 +01:00
committed by GitHub
47 changed files with 5499 additions and 4540 deletions

View File

@@ -1,8 +1,25 @@
- 639
- 633
-626,
-622,
-356
allow to add includedNotes with type
- - 645
- 631 multiple invoice referenced documents
- 629
- 630 #296 #565
-
2.15.2
=======
2024-12-19
- correcly write charge reason codes also for non-Xrechnung #617
- correctly import additional referenced documents into invoice/corrected setting of attachments from jackson
- corrected parseException structure
- allow 1p0 as potential xmp version number
- #618 import BT-20
- #599 add tax category code for free export
- #600 Fixes a problem where a stream was not safely closed
2.15.1
=======

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
@@ -12,7 +12,7 @@
should also work for XRechnung/CII.
</name>
<packaging>jar</packaging>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
@@ -23,7 +23,7 @@
<dependency>
<groupId>org.mustangproject</groupId>
<artifactId>validator</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<!-- prototypes of new mustangproject versions can be installed by referring to them and installed to the local repo from a jar file with
mvn install:install-file -Dfile=mustang-1.5.4-SNAPSHOT.jar -DgroupId=org.mustangproject.ZUGFeRD -DartifactId=mustang -Dversion=1.5.4 -Dpackaging=jar -DgeneratePom=true
-->
@@ -45,7 +45,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.13</version>
<version>1.5.13</version>
</dependency>
<dependency>
<!-- This library is needed so that logback stderr output is sent to, well, stderr, otherwise it lands in stdout -->

View File

@@ -19,6 +19,7 @@
package org.mustangproject.commandline;
import org.apache.commons.cli.*;
import org.apache.commons.io.FilenameUtils;
import org.mustangproject.CII.CIIToUBL;
import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment;
@@ -84,6 +85,7 @@ public class Main {
+ " [--logAppend <text>]: text to be added to log line\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source <filename>]: input PDF or XML file\n"
+ " [--log-as-pdf]: save log output as pdf\n"
+ " --action validateExpectInvalid validate directory expecting negative results \n"
+ " [--no-notices]: refrain from reporting notices\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
@@ -357,6 +359,7 @@ public class Main {
options.addOption(new Option("d", "directory", true, "which directory to operate on"));
options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions"));
options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline"));
options.addOption(new Option("log-as-pdf", "log-as-pdf", false, "saving log output to pdf file"));
boolean optionsRecognized = false;
String action = "";
@@ -380,6 +383,7 @@ public class Main {
String format = cmd.getOptionValue("format");
String lang = cmd.getOptionValue("language");
Boolean noNotices = cmd.hasOption("no-notices");
Boolean LogAsPDF = cmd.hasOption("log-as-pdf");
String zugferdVersion = cmd.getOptionValue("version");
String zugferdProfile = cmd.getOptionValue("profile");
@@ -427,7 +431,7 @@ public class Main {
performUBL(sourceName, outName);
optionsRecognized = true;
} else if ((action != null) && (action.equals("validate"))) {
optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"));
optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"), LogAsPDF);
} else if ((action != null) && (action.equals("validateExpectValid"))) {
optionsRecognized = performValidateExpect(true, directoryName);
} else if ((action != null) && (action.equals("validateExpectInvalid"))) {
@@ -454,7 +458,7 @@ public class Main {
}
private static boolean performValidate(String sourceName, boolean noNotices, String logAppend) {
private static boolean performValidate(String sourceName, boolean noNotices, String logAppend, boolean createLogAsPDF) {
boolean optionsRecognized;
if (sourceName == null) {
sourceName = getFilenameFromUser("Source PDF or XML", "invoice.pdf", "pdf|xml", true, false);
@@ -466,7 +470,16 @@ public class Main {
if (noNotices) {
zfv.disableNotices();
}
System.out.println(zfv.validate(sourceName));
String validationResultXML = zfv.validate(sourceName);
System.out.println(validationResultXML);
if( createLogAsPDF) {
ValidationLogVisualizer vlvi = new ValidationLogVisualizer();
String fileBasename = FilenameUtils.getBaseName(sourceName);
vlvi.toPDF(validationResultXML, fileBasename + "_result.pdf");
}
optionsRecognized = !zfv.hasOptionsError();
if (!zfv.wasCompletelyValid()) {
System.exit(-1);

View File

@@ -3,13 +3,13 @@
<parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
<artifactId>library</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)</name>
<description>FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
@@ -200,21 +200,51 @@
</manifest>
<manifestSections>
<manifestSection>
<name>FreeSans.ttf</name>
<name>SourceSansPro-Regular.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>FreeSerif.ttf</name>
<name>SourceSansPro-It.ttf</name>
<manifestEntries>
<Content-Type>application/x-font</Content-Type>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>Times-Bold.ttf</name>
<name>SourceSansPro-Bold.ttf</name>
<manifestEntries>
<Content-Type>application/x-font</Content-Type>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSansPro-BoldIt.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-Regular.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-It.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-Bold.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-BoldIt.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
</manifestSections>

View File

@@ -3,11 +3,14 @@ package org.mustangproject.Exceptions;
import java.text.ParseException;
/***
* will be thrown if a invoice cant be reproduced numerically
* will be thrown if an invoice cant be reproduced numerically
*/
public class ArithmetricException extends ParseException {
public ArithmetricException() {
super(
"Could not reproduce the invoice, this could mean that it could not be read properly", 0);
this("");
}
public ArithmetricException(String details) {
super("Could not reproduce the invoice. " + details, 0);
}
}

View File

@@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Invoice implements IExportableTransaction {
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
protected Date issueDate = null, dueDate = null, deliveryDate = null;
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null;
protected ArrayList<CashDiscount> cashDiscounts = null;
@@ -57,7 +57,12 @@ public class Invoice implements IExportableTransaction {
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
protected IZUGFeRDPaymentTerms paymentTerms = null;
protected String invoiceReferencedDocumentID = null;
protected Date invoiceReferencedIssueDate;
// New field for storing Invoiced Object Identifier (BG-3)
protected ArrayList<ReferencedDocument> invoiceReferencedDocuments = null;
protected String specifiedProcuringProjectID = null;
protected String specifiedProcuringProjectName = null;
protected String despatchAdviceReferencedDocumentID = null;
@@ -148,6 +153,7 @@ public class Invoice implements IExportableTransaction {
*/
public Invoice setCorrection(String number) {
setInvoiceReferencedDocumentID(number);
addInvoiceReferencedDocument(new ReferencedDocument(number));
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
return this;
}
@@ -546,6 +552,26 @@ public class Invoice implements IExportableTransaction {
return this;
}
// Getter for BG-3
@Override
public ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
return invoiceReferencedDocuments;
}
// Setter for BG-3
public void setInvoiceReferencedDocuments(ArrayList<ReferencedDocument> invoiceReferencedDocuments) {
this.invoiceReferencedDocuments = invoiceReferencedDocuments;
}
// Method to add a single ReferencedDocument for BG-3
public Invoice addInvoiceReferencedDocument(ReferencedDocument doc) {
if (invoiceReferencedDocuments == null) {
invoiceReferencedDocuments = new ArrayList<>();
}
invoiceReferencedDocuments.add(doc);
return this;
}
@Override
public IZUGFeRDAllowanceCharge[] getZFAllowances() {
if (Allowances.isEmpty()) {

View File

@@ -352,7 +352,7 @@ public class Item implements IZUGFeRDExportableItem {
/***
* Adds a item level addition to the price (will be multiplied by quantity)
* @see org.mustangproject.Charge
* @see Charge
* @param izac a relative or absolute charge
* @return fluent setter
*/
@@ -363,7 +363,7 @@ public class Item implements IZUGFeRDExportableItem {
/***
* Adds a item level reduction the price (will be multiplied by quantity)
* @see org.mustangproject.Allowance
* @see Allowance
* @param izac a relative or absolute allowance
* @return fluent setter
*/
@@ -383,10 +383,23 @@ public class Item implements IZUGFeRDExportableItem {
}
notes.add(text);
addNote(IncludedNote.unspecifiedNote(text));
return this;
}
/***
* adds categorized item level freetext fields (includednote)
* @param theNote IncludedNote to add
* @return fluent setter
*/
public Item addNote(IncludedNote theNote) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.unspecifiedNote(text));
includedNotes.add(theNote);
return this;

View File

@@ -2,6 +2,8 @@ package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Date;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
@@ -13,17 +15,30 @@ public class ReferencedDocument implements IReferencedDocument {
String issuerAssignedID;
String typeCode;
String referenceTypeCode;
Date formattedIssueDateTime;
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode) {
this.issuerAssignedID = issuerAssignedID;
this(issuerAssignedID);
this.typeCode = typeCode;
this.referenceTypeCode = referenceTypeCode;
}
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode, Date formattedIssueDateTime) {
this(issuerAssignedID, typeCode, referenceTypeCode);
this.formattedIssueDateTime = formattedIssueDateTime;
}
public ReferencedDocument(String issuerAssignedID, String referenceTypeCode) {
this(issuerAssignedID, "916", referenceTypeCode); // additional invoice related document
}
public ReferencedDocument(String issuerAssingedID, Date formattedIssueDateTime) {
this(issuerAssingedID);
this.formattedIssueDateTime = formattedIssueDateTime;
}
public ReferencedDocument(String issuerAssignedID) {
this.issuerAssignedID = issuerAssignedID;
this.typeCode = "916"; // additional invoice related document
this.referenceTypeCode = referenceTypeCode;
}
/***
@@ -50,6 +65,15 @@ public class ReferencedDocument implements IReferencedDocument {
this.referenceTypeCode = referenceTypeCode;
}
/**
* issue date of this line
*
* @param formattedIssueDateTime as Date
*/
public void setFormattedIssueDateTime(Date formattedIssueDateTime) {
this.formattedIssueDateTime = formattedIssueDateTime;
}
@Override
public String getIssuerAssignedID() {
return issuerAssignedID;
@@ -65,6 +89,12 @@ public class ReferencedDocument implements IReferencedDocument {
return referenceTypeCode;
}
@Override
public Date getFormattedIssueDateTime()
{
return formattedIssueDateTime;
}
public static ReferencedDocument fromNode(Node node) {
if (!node.hasChildNodes()) {
return null;
@@ -72,6 +102,7 @@ public class ReferencedDocument implements IReferencedDocument {
NodeMap nodes = new NodeMap(node);
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
nodes.getAsStringOrNull("ReferenceTypeCode"));
nodes.getAsStringOrNull("ReferenceTypeCode"),
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
}
}

View File

@@ -30,11 +30,13 @@ package org.mustangproject.ZUGFeRD;
* */
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
/***
@@ -424,14 +426,24 @@ public interface IExportableTransaction {
*
* @return the ID of the document
*/
@Deprecated
default String getInvoiceReferencedDocumentID() {
return null;
}
@Deprecated
default Date getInvoiceReferencedIssueDate() {
return null;
}
/**
* Getter for BG-3
* @return list of documents
*/
default ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
return null;
}
/**
* get the issue timestamp of the BuyerOrderReferencedDocument, which sits in
* the ApplicableSupplyChainTradeAgreement

View File

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

View File

@@ -459,6 +459,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
}
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (var doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
if (doc.getFormattedIssueDateTime() != null) {
xml += "<ram:FormattedIssueDateTime>"
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
+ "</ram:FormattedIssueDateTime>";
}
xml += "</ram:InvoiceReferencedDocument>";
}
}
xml += "</ram:ApplicableHeaderTradeSettlement>";
// + " <IncludedSupplyChainTradeLineItem>"

View File

@@ -4,7 +4,10 @@ import static java.math.BigDecimal.ZERO;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/***
@@ -91,17 +94,14 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
}
private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
String res = " ";
if ((charges != null) && (charges.length > 0)) {
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)
&& currentCharge.getReason() != null) {
res += currentCharge.getReason() + " ";
if (charges == null) {
return "";
}
}
}
res = res.substring(0, res.length() - 1);
return res;
return Arrays.stream(charges)
.filter(currentCharge -> (percent == null || currentCharge.getTaxPercent().compareTo(percent) == 0))
.map(IZUGFeRDAllowanceCharge::getReason)
.filter(Objects::nonNull)
.collect(Collectors.joining(" "));
}
/***

View File

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

View File

@@ -829,6 +829,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:Description>" + paymentTermsDescription + "</ram:Description>";
}
if (trans.getDueDate() != null) {
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
+ DATE.udtFormat(trans.getDueDate())
+ "</ram:DueDateDateTime>";// 20130704
}
if (trans.getTradeSettlement() != null) {
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
@@ -837,12 +843,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
}
if (trans.getDueDate() != null) {
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
+ DATE.udtFormat(trans.getDueDate())
+ "</ram:DueDateDateTime>";// 20130704
}
xml += "</ram:SpecifiedTradePaymentTerms>";
} else {
xml += buildPaymentTermsXml();
@@ -893,6 +893,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (var doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
if (doc.getFormattedIssueDateTime() != null) {
xml += "<ram:FormattedIssueDateTime>"
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
+ "</ram:FormattedIssueDateTime>";
}
xml += "</ram:InvoiceReferencedDocument>";
}
}
xml += "</ram:ApplicableHeaderTradeSettlement>";
// + "<IncludedSupplyChainTradeLineItem>\n"

View File

@@ -83,9 +83,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
}
protected byte[] filenameToByteArray(String pdfFilename) throws IOException {
FileInputStream fileInputStream = new FileInputStream(pdfFilename);
try (FileInputStream fileInputStream = new FileInputStream(pdfFilename)) {
return inputstreamToByteArray(fileInputStream);
}
}
protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException {
byte[] bytes = new byte[fileInputStream.available()];

View File

@@ -659,6 +659,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
*
* @return a List of LineItem instances
*/
@Deprecated
public List<Item> getLineItemList() {
final List<Node> nodeList = getLineItemNodes();
final List<Item> lineItemList = new ArrayList<>();

View File

@@ -32,6 +32,9 @@ import java.nio.file.StandardOpenOption;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ZUGFeRDInvoiceImporter {
@@ -472,9 +475,11 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"DuePayableAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableAmount\"]");
NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
BigDecimal duePayableAmount = null;
if (lineDueNodes.getLength() > 0) {
duePayableAmount = new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)));
if (zpp instanceof CalculatedInvoice) {
((CalculatedInvoice) zpp).setDuePayable(new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0))));
((CalculatedInvoice) zpp).setDuePayable(duePayableAmount);
}
}
@@ -816,6 +821,23 @@ public class ZUGFeRDInvoiceImporter {
}
zpp.setInvoiceReferencedDocumentID(extractString("//*[local-name()=\"InvoiceReferencedDocument\"]/*[local-name()=\"IssuerAssignedID\"]|//*[local-name()=\"BillingReference\"]/*[local-name()=\"InvoiceDocumentReference\"]/*[local-name()=\"ID\"]"));
xpr = xpath.compile("//*[local-name()=\"InvoiceReferencedDocument\"]");
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (nodes.getLength() != 0) {
for (int i = 0; i < nodes.getLength(); i++) {
Node currentItemNode = nodes.item(i);
ReferencedDocument doc = ReferencedDocument.fromNode(currentItemNode);
if (doc != null
&& (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID())
|| !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime())))
{
zpp.addInvoiceReferencedDocument(doc);
}
}
}
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
String rounding = extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"Party\"]/*[local-name()=\"PayableRoundingAmount\"]");
@@ -834,7 +856,7 @@ public class ZUGFeRDInvoiceImporter {
}
xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]");
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (nodes.getLength() != 0) {
for (int i = 0; i < nodes.getLength(); i++) {
@@ -938,8 +960,7 @@ public class ZUGFeRDInvoiceImporter {
TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getGrandTotal()
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
String calculatedPayableTotal = tc.getDuePayable().toPlainString();
EStandard whichType;
try {
whichType = getStandard();
@@ -947,10 +968,21 @@ public class ZUGFeRDInvoiceImporter {
throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0);
}
if ((whichType != EStandard.despatchadvice)
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2)))
&& (!ignoreCalculationErrors))) {
throw new ArithmetricException();
if (whichType != EStandard.despatchadvice && !ignoreCalculationErrors) {
// Check calculation if document type allows it and calculation errors should not be ignored
String payableTotalFromXml = XMLTools.nDigitFormat(Objects.requireNonNullElse(duePayableAmount, expectedGrandTotal), 2);
if (!calculatedPayableTotal.equals(payableTotalFromXml)) {
String moreDetails = "";
try {
moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
+ Stream.of(tc.trans.getZFItems())
.map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
.collect(Collectors.joining(" + "));
} catch (Exception ignored) {
}
throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
}
}
}
return zpp;
@@ -1036,6 +1068,7 @@ public class ZUGFeRDInvoiceImporter {
* for PDF embedded files in FX use getFileAttachmentsPDF()
* @deprecated use invoice.getAdditionalReferencedDocuments
*/
@Deprecated
public List<FileAttachment> getFileAttachmentsXML() {
return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
}
@@ -1067,5 +1100,4 @@ public class ZUGFeRDInvoiceImporter {
LOGGER.error(e.getMessage(), e);
}
}
}

View File

@@ -20,42 +20,9 @@
*/
package org.mustangproject.ZUGFeRD;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.FopFactoryBuilder;
import com.helger.commons.io.stream.StreamHelper;
import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
@@ -65,12 +32,19 @@ import org.mustangproject.ClasspathResolverURIAdapter;
import org.mustangproject.EStandard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.commons.io.stream.StreamHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class ZUGFeRDVisualizer {
@@ -113,7 +87,7 @@ public class ZUGFeRDVisualizer {
* @param fis inputstream (will be consumed)
* @return (facturx = cii)
*/
public EStandard findOutStandardFromRootNode(InputStream fis) {
private EStandard findOutStandardFromRootNode(InputStream fis) {
String zf1Signature = "CrossIndustryDocument";
String zf2Signature = "CrossIndustryInvoice";
@@ -144,14 +118,85 @@ public class ZUGFeRDVisualizer {
return null;
}
public String visualize(String xmlFilename, Language lang)
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
try {
if (mXsltPDFTemplate == null) {
mXsltPDFTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException {
FileInputStream fis = new FileInputStream(xmlFilename);
return visualize(fis, lang);
}
public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException {
initTemplates(lang);
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
EStandard thestandard = findOutStandardFromRootNode(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream htmlOutput = new ByteArrayOutputStream();
ByteArrayInputStream xmlContentStream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8));
if (thestandard == EStandard.zugferd) {
applyZF1XSLT(xmlContentStream, htmlOutput);
return htmlOutput.toString(StandardCharsets.UTF_8);
} else if (thestandard == EStandard.facturx) {
//zf2 or fx
applyZF2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.ubl) {
//zf2 or fx
applyUBL2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.ubl_creditnote) {
//zf2 or fx
applyUBLCreditNote2XSLT(xmlContentStream, htmlOutput);
} else if (thestandard == EStandard.orderx) {
//zf2 or fx
applyCIO2XSLT(xmlContentStream, htmlOutput);
} else {
throw new IllegalArgumentException("File does not look like CII or UBL");
}
Optional<InputStream> in = copyStream(htmlOutput);
ByteArrayOutputStream htmlOutStream = new ByteArrayOutputStream();
if (in.isPresent()) {
applyXSLTToHTML(in.get(), htmlOutStream);
}
return htmlOutStream.toString(StandardCharsets.UTF_8);
}
/**
* TODO: jstaerk: why not copy with that simple call: new ByteArrayInputStream(byteArrayOutputStream.toByteArray()) ?
*/
private Optional<InputStream> copyStream(ByteArrayOutputStream byteArrayOutputStream) {
// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
try {
PipedOutputStream out = new PipedOutputStream(in);
new Thread(() -> {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
byteArrayOutputStream.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
}
}).start();
} catch (IOException e1) {
LOGGER.error("Failed to create HTML", e1);
return Optional.empty();
}
return Optional.of(in);
}
private void initTemplates(Language lang) throws TransformerConfigurationException {
if (mXsltXRTemplate == null) {
mXsltXRTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
}
if (mXsltHTMLTemplate == null) {
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
@@ -160,90 +205,10 @@ public class ZUGFeRDVisualizer {
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
}
} catch (TransformerConfigurationException ex) {
LOGGER.error("Failed to init XSLT templates", ex);
}
/**
* *
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
* http://www.ferd-net.de/upload/Dokumente/FACTUR-X_ZUGFeRD_2p0_Teil1_Profil_EN16931_1p03.pdf
* http://countwordsfree.com/xmlviewer
*/
FileInputStream fis = new FileInputStream(xmlFilename);
String fileContent = "";
try {
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
} catch (IOException e2) {
LOGGER.error("Failed to read file content", e2);
}
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean doPostProcessing = false;
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
EStandard thestandard = findOutStandardFromRootNode(fis);
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
if (thestandard == EStandard.zugferd) {
applyZF1XSLT(fis, baos);
} else if (thestandard == EStandard.facturx) {
//zf2 or fx
applyZF2XSLT(fis, iaos);
doPostProcessing = true;
} else if (thestandard == EStandard.ubl) {
//zf2 or fx
applyUBL2XSLT(fis, iaos);
doPostProcessing = true;
} else if (thestandard == EStandard.ubl_creditnote) {
//zf2 or fx
applyUBLCreditNote2XSLT(fis, iaos);
doPostProcessing = true;
} else if (thestandard == EStandard.orderx) {
//zf2 or fx
applyCIO2XSLT(fis, iaos);
doPostProcessing = true;
} else {
throw new IllegalArgumentException("File does not look like CII or UBL");
}
if (doPostProcessing) {
// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
PipedOutputStream out;
try {
out = new PipedOutputStream(in);
new Thread(new Runnable() {
public void run() {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
iaos.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
}
}
}).start();
applyXSLTToHTML(in, baos);
} catch (IOException e1) {
LOGGER.error("Failed to create HTML", e1);
}
}
return baos.toString(StandardCharsets.UTF_8);
}
protected String toFOP(String xmlFilename)
throws FileNotFoundException, TransformerException {
throws IOException, TransformerException {
FileInputStream fis = new FileInputStream(xmlFilename);
EStandard theStandard = findOutStandardFromRootNode(fis);
@@ -253,7 +218,7 @@ public class ZUGFeRDVisualizer {
}
protected String toFOP(InputStream is, EStandard theStandard)
throws FileNotFoundException, TransformerException {
throws TransformerException, IOException {
try {
if (mXsltPDFTemplate == null) {
@@ -265,7 +230,6 @@ public class ZUGFeRDVisualizer {
}
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//zf2 or fx
if (theStandard == EStandard.facturx) {
@@ -277,33 +241,11 @@ public class ZUGFeRDVisualizer {
}
PipedInputStream in = new PipedInputStream();
PipedOutputStream out;
try {
out = new PipedOutputStream(in);
new Thread(new Runnable() {
public void run() {
try {
// write the original OutputStream to the PipedOutputStream
// note that in order for the below method to work, you need
// to ensure that the data has finished writing to the
// ByteArrayOutputStream
iaos.writeTo(out);
} catch (IOException e) {
LOGGER.error("Failed to write to stream", e);
} finally {
// close the PipedOutputStream here because we're done writing data
// once this thread has completed its run
StreamHelper.close(out);
Optional<InputStream> in = copyStream(iaos);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (in.isPresent()) {
applyXSLTToPDF(in.get(), baos);
}
}
}).start();
applyXSLTToPDF(in, baos);
} catch (IOException e1) {
LOGGER.error("Failed to create PDF", e1);
}
return baos.toString(StandardCharsets.UTF_8);
}
@@ -319,7 +261,7 @@ public class ZUGFeRDVisualizer {
*/
try {
result = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (FileNotFoundException | TransformerException e) {
} catch (TransformerException | IOException e) {
LOGGER.error("Failed to apply FOP", e);
}
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
@@ -373,7 +315,7 @@ public class ZUGFeRDVisualizer {
}
}
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
if (mXsltXRTemplate == null) {
mXsltXRTemplate = mFactory.newTemplates(
@@ -382,10 +324,10 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltXRTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream htmlOutstream)
throws TransformerException {
if (mXsltCIOTemplate == null) {
mXsltCIOTemplate = mFactory.newTemplates(
@@ -393,10 +335,10 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltCIOTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutstream));
}
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates(
@@ -404,10 +346,10 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltUBLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates(
@@ -415,28 +357,30 @@ public class ZUGFeRDVisualizer {
}
Transformer transformer = mXsltUBLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException {
Transformer transformer = mXsltZF1HTMLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
}
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException {
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream htmlOutStream)
throws TransformerException, IOException {
Transformer transformer = mXsltHTMLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
xmlFile.close();
}
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
throws TransformerException {
throws TransformerException, IOException {
Transformer transformer = mXsltPDFTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
xmlFile.close();
}
private static class ClasspathResourceURIResolver implements URIResolver {
@@ -445,7 +389,7 @@ public class ZUGFeRDVisualizer {
}
@Override
public Source resolve(String href, String base) throws TransformerException {
public Source resolve(String href, String base) {
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
}
}

View File

@@ -29,6 +29,7 @@ public class TaxCategoryCodeTypeConstants {
public static final String ZEROTAXPRODUCTS = "Z";
public static final String UNTAXEDSERVICE = "O";
public static final String INTRACOMMUNITY = "K";
public static final String FREEEXPORT = "G";
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT).collect(Collectors.toSet());
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT, FREEEXPORT).collect(Collectors.toSet());
}

Binary file not shown.

Binary file not shown.

View File

@@ -23,15 +23,35 @@
<renderers>
<renderer mime="application/pdf">
<fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
<fonts>
<!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
<auto-detect/>
<font kerning="no" embed-url="classpath:FreeSans.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="normal" />
<font-triplet name="Times-Bold" style="normal" weight="normal" />
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Regular.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="normal" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-It.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="italic" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Bold.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="normal" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-BoldIt.ttf" embedding-mode="subset">
<font-triplet name="SourceSansPro" style="italic" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Regular.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-It.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="italic" weight="400"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Bold.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="normal" weight="700"/>
</font>
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-BoldIt.ttf" embedding-mode="subset">
<font-triplet name="SourceSerifPro" style="italic" weight="700"/>
</font>
</fonts>
<output-profile>classpath:AdobeCompat-v2.icc</output-profile>
</renderer>
</renderers>
</fop>

View File

@@ -98,6 +98,8 @@
select="./rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:ReceivingAdviceReferencedDocument/ram:IssuerAssignedID"/>
<xsl:apply-templates mode="BT-16"
select="./rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:DespatchAdviceReferencedDocument/ram:IssuerAssignedID"/>
<xsl:apply-templates mode="BT-X-202"
select="./rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:DeliveryNoteReferencedDocument/ram:IssuerAssignedID"/>
<xsl:apply-templates mode="BT-17"
select="./rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:AdditionalReferencedDocument/ram:IssuerAssignedID[following-sibling::ram:TypeCode='50']"/>
<xsl:apply-templates mode="BT-18"
@@ -295,6 +297,14 @@
<xr:Receiving_advice_reference>
<xsl:attribute name="xr:id" select="'BT-15'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:choose>
<xsl:when test="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']">
<xsl:call-template name="dateAsAttribute">
<xsl:with-param name="dateString" select="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']"/>
<xsl:with-param name="attributeName" select="'bt-x-201'"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
<xsl:call-template name="document_reference"/>
</xr:Receiving_advice_reference>
</xsl:template>
@@ -303,9 +313,33 @@
<xr:Despatch_advice_reference>
<xsl:attribute name="xr:id" select="'BT-16'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:choose>
<xsl:when test="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']">
<xsl:call-template name="dateAsAttribute">
<xsl:with-param name="dateString" select="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']"/>
<xsl:with-param name="attributeName" select="'bt-x-200'"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
<xsl:call-template name="document_reference"/>
</xr:Despatch_advice_reference>
</xsl:template>
<xsl:template mode="BT-X-202"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery/ram:DeliveryNoteReferencedDocument/ram:IssuerAssignedID">
<xr:Delivery_note_reference>
<xsl:attribute name="xr:id" select="'BT-X-202'"/>
<xsl:attribute name="xr:src" select="xr:src-path(.)"/>
<xsl:choose>
<xsl:when test="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']">
<xsl:call-template name="dateAsAttribute">
<xsl:with-param name="dateString" select="../ram:FormattedIssueDateTime/qdt:DateTimeString[@format = '102']"/>
<xsl:with-param name="attributeName" select="'bt-x-203'"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
<xsl:call-template name="document_reference"/>
</xr:Delivery_note_reference>
</xsl:template>
<xsl:template mode="BT-17"
match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:AdditionalReferencedDocument/ram:IssuerAssignedID[following-sibling::ram:TypeCode='50']">
<xr:Tender_or_lot_reference>
@@ -2437,6 +2471,7 @@
<xsl:template name="text">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template name="date">
<xsl:variable name="normalizeddate" select="normalize-space(replace(., '-', ''))"/>
<xsl:choose>
@@ -2453,6 +2488,24 @@
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="dateAsAttribute">
<xsl:param name="dateString"/>
<xsl:param name="attributeName" select="'date'"/>
<xsl:variable name="normalizeddate" select="normalize-space(replace($dateString, '-', ''))"/>
<xsl:choose>
<xsl:when test="matches($normalizeddate, '^[0-9]{8}$')">
<xsl:attribute name="{$attributeName}"
select="xs:date( concat(substring($normalizeddate,1,4), '-', substring($normalizeddate,5,2), '-', substring($normalizeddate,7,2) ) )"/>
</xsl:when>
<xsl:otherwise>ILLEGAL DATE FORMAT: &lt;para&gt;Mit diesem Datentyp wird ein kalendarisches Datum
abgebildet, wie es in der ISO 8601 Spezifikation &lt;quote&gt;Calendar date complete representation&lt;/quote&gt;
beschrieben ist (siehe ISO 8601:2004, Abschnitt 5.2.1.1). Das Datum beinhaltet keine Zeitangabe. Das
konkret zu verwendende Format ist abhängig von der genutzten Syntax.&lt;/para&gt;
&lt;para&gt;Der Datentyp basiert auf dem Typ &lt;quote&gt;Date Time. Type&lt;/quote&gt;, wie in ISO
15000-5:2014 Anhang B definiert.&lt;/para&gt;
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="identifier-with-scheme-and-version">
<xsl:param name="schemeID" as="element()?"/>
<xsl:if test="@listID | @schemeID">

View File

@@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<!-- Imports -->
<xsl:import href="common-xr.xsl"/>
<xsl:import href="xr-pdf/lib/konstanten.xsl"/>
<!--
FO engine used can be specified.
Specific extensions will be then enabled.
Supported values are:
axf - Antenna House XSL Formatter
fop - Apache FOP
-->
<xsl:param name="foengine"/>
<xsl:param name="axf.extensions"
select="if ($foengine eq 'axf') then true() else false()"/>
<xsl:param name="fop.extensions"
select="if ($foengine eq 'fop') then true() else false()"/>
<xsl:variable name="xml_result_color">
<xsl:if test="/validation/xml/summary/@status = 'valid'">
<xsl:text>green</xsl:text>
</xsl:if>
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
<xsl:text>red</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="pdf_result_color">
<xsl:if test="/validation/pdf/summary/@status = 'valid'">
<xsl:text>green</xsl:text>
</xsl:if>
<xsl:if test="/validation/pdf/summary/@status = 'invalid'">
<xsl:text>red</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="pdf_result_text">
<xsl:if test="/validation/xml/summary/@status = 'valid'">
<xsl:text>Das ZUGFeRD-PDF ist valide.</xsl:text>
</xsl:if>
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
<xsl:text>Das ZUGFeRD-PDF ist nicht valide.</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="result_text">
<xsl:if test="/validation/xml/summary/@status = 'valid'">
<xsl:text>Es wird empfohlen, das Dokument anzunehmen und weiterzuverarbeiten.</xsl:text>
</xsl:if>
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
<xsl:text>Es wird empfohlen, das Dokument zurückzuweisen.</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:template match="validation">
<fo:root language="{$lang}"
font-family="{$fontSans}">
<fo:layout-master-set>
<fo:simple-page-master master-name="DIN-A4"
page-height="297mm"
page-width="210mm">
<fo:region-body region-name="body"
margin="20mm 10mm 20mm 20mm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="DIN-A4">
<fo:flow flow-name="body">
<fo:block font-size="24px"
font-weight="bold">
<xsl:text>Prüfbericht</xsl:text>
</fo:block>
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="&#34;Angaben zum geprüften Dokument&#34;"/>
<xsl:with-param name="color"
select="&#34;black&#34;"/>
</xsl:call-template>
<fo:block>
<fo:block text-align="justify">
<fo:float float="right">
<fo:block>
<xsl:value-of select="/validation/@filename"/>
</fo:block>
</fo:float>
<xsl:text>Referenz:</xsl:text>
</fo:block>
</fo:block>
<fo:block>
<fo:block text-align="justify">
<fo:float float="right">
<fo:block>
<xsl:value-of select="/validation/@datetime"/>
</fo:block>
</fo:float>
<xsl:text>Zeitpunkt der Prüfung:</xsl:text>
</fo:block>
</fo:block>
<fo:block>
<fo:block text-align="justify">
<fo:float float="right">
<fo:block>
<xsl:value-of select="/validation/xml/info/profile"/>
</fo:block>
</fo:float>
<xsl:text>Erkannter Dokumenttyp:</xsl:text>
</fo:block>
</fo:block>
<xsl:apply-templates select="/validation/pdf"/>
<!--
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select='"Konformitätsprüfung:"'/>
<xsl:with-param name="color"
select='"black"'/>
</xsl:call-template>
-->
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="concat('Bewertung: ', $result_text)"/>
<xsl:with-param name="color"
select="$xml_result_color"/>
</xsl:call-template>
<fo:block>Validierungsergebnisse im Detail:</fo:block>
<fo:table>
<fo:table-column border-style="solid"/>
<fo:table-column border-style="solid"/>
<fo:table-column border-style="solid"/>
<fo:table-column column-width="70%"
border-style="solid"/>
<fo:table-header>
<fo:table-row background-color="#E0E0E0"
font-weight="bold">
<fo:table-cell>
<fo:block>Type</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Code</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Schwere</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Text</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:choose>
<xsl:when test="/validation/xml/messages">
<xsl:apply-templates select="/validation/xml/messages"/>
</xsl:when>
<xsl:otherwise>
<fo:table-row font-size="12px"
border-style="solid">
<fo:table-cell number-columns-spanned="4">
<fo:block text-align="center">Es gibt weder Hinweise noch Fehler.</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:otherwise>
</xsl:choose>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="notice|error">
<fo:table-row font-size="12px"
border-style="solid">
<fo:table-cell number-rows-spanned="2">
<fo:block>
<xsl:value-of select="@type"/>
</fo:block>
</fo:table-cell>
<fo:table-cell number-rows-spanned="2">
<fo:block>
<xsl:call-template name="SUBID">
<xsl:with-param name="myparam"
select="substring-after(.,' [ID ')"/>
</xsl:call-template>
</fo:block>
</fo:table-cell>
<fo:table-cell number-rows-spanned="2">
<fo:block>
<xsl:choose>
<xsl:when test="name() = 'error'">
<xsl:attribute name="color">red</xsl:attribute>
<xsl:text>Fehler</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Hinweis</xsl:text>
</xsl:otherwise>
</xsl:choose>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:if test="name() = 'error'">
<xsl:attribute name="color">red</xsl:attribute>
</xsl:if>
<xsl:value-of select="substring-before(.,' [ID')"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row font-size="10px"
border-style="solid">
<fo:table-cell>
<fo:block>
<xsl:if test="name() = 'error'">
<xsl:attribute name="color">red</xsl:attribute>
</xsl:if>
<xsl:value-of select="concat('Pfad: ', @location)"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template match="pdf">
<xsl:call-template name="SubHeader">
<xsl:with-param name="text"
select="concat('ZUGFeRD-PDF: ', $pdf_result_text)"/>
<xsl:with-param name="color"
select="&#34;black&#34;"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="SubHeader">
<xsl:param name="text"/>
<xsl:param name="color"/>
<fo:block color="{$color}"
background-color="#E0E0E0"
margin-bottom="10px"
padding="3px"
font-weight="bold"
margin-top="10px">
<xsl:value-of select="$text"/>
</fo:block>
</xsl:template>
<xsl:template name="SUBID">
<xsl:param name="myparam"/>
<xsl:variable name="myparam.end"
select="substring-before($myparam, ']')"/>
<xsl:value-of select="$myparam.end"/>
</xsl:template>
</xsl:stylesheet>

View File

@@ -12,7 +12,7 @@
== Schriften
=========================================================================== -->
<xsl:variable name="fontSans">SourceSerifPro</xsl:variable>
<xsl:variable name="fontSans">SourceSansPro</xsl:variable>
<xsl:variable name="fontSerif">SourceSerifPro</xsl:variable>
<xsl:variable name="amount-picture" select="xrf:_('amount-format')"/>

View File

@@ -11,6 +11,7 @@
<xsl:variable name="i18n.history" select="'Bearbeitungshistorie'"/>
<xsl:variable name="i18n.disclaimer" select="'Wir übernehmen keine Haftung für die Richtigkeit der Daten.'"/>
<xsl:variable name="i18n.recipientInfo" select="'Informationen zum Käufer'"/>
<xsl:variable name="i18n.dateOf" select="' vom '"/>
<xsl:variable name="i18n.bt50" select="'Straße / Haus-Nr.'"/>
<xsl:variable name="i18n.bt51" select="'Postfach'"/>
<xsl:variable name="i18n.bt163" select="'Adresszusatz'"/>
@@ -197,6 +198,7 @@
<xsl:variable name="i18n.bt17" select="'Vergabenummer'"/>
<xsl:variable name="i18n.bt15" select="'Kennung der Empfangsbestätigung'"/>
<xsl:variable name="i18n.bt16" select="'Kennung der Versandanzeige'"/>
<xsl:variable name="i18n.btx202" select="'Kennung des Lieferscheins'"/>
<xsl:variable name="i18n.bt23" select="'Prozesskennung'"/>
<xsl:variable name="i18n.bt24" select="'Spezifikationskennung'"/>
<xsl:variable name="i18n.bt18" select="'Objektkennung'"/>

View File

@@ -11,6 +11,7 @@
<xsl:variable name="i18n.history" select="'History'"/>
<xsl:variable name="i18n.disclaimer" select="'We accept no liability for the correctness of the data'"/>
<xsl:variable name="i18n.recipientInfo" select="'Buyer Information'"/>
<xsl:variable name="i18n.dateOf" select="' from '"/>
<xsl:variable name="i18n.bt50" select="'Street / house number'"/>
<xsl:variable name="i18n.bt51" select="'PO Box'"/>
<xsl:variable name="i18n.bt163" select="'Address Addition'"/>
@@ -197,6 +198,7 @@
<xsl:variable name="i18n.bt17" select="'Assignment number'"/>
<xsl:variable name="i18n.bt15" select="'Receipt confirmation ID'"/>
<xsl:variable name="i18n.bt16" select="'Dispatch note ID'"/>
<xsl:variable name="i18n.btx202" select="'Delivery note ID'"/>
<xsl:variable name="i18n.bt23" select="'Process ID'"/>
<xsl:variable name="i18n.bt24" select="'Specification ID'"/>
<xsl:variable name="i18n.bt18" select="'Object ID'"/>

View File

@@ -11,6 +11,7 @@
<xsl:variable name="i18n.history" select="'Historique de traitement'"/>
<xsl:variable name="i18n.disclaimer" select="'Nous n''assumons aucune responsabilité quant à l''exactitude des données.'"/>
<xsl:variable name="i18n.recipientInfo" select="'Informations sur l''acheteur'"/>
<xsl:variable name="i18n.dateOf" select="' du '"/>
<xsl:variable name="i18n.bt50" select="'Rue / Numéro de maison'"/>
<xsl:variable name="i18n.bt51" select="'Boîte postale'"/>
<xsl:variable name="i18n.bt163" select="'Supplément d''adresse'"/>
@@ -197,6 +198,7 @@
<xsl:variable name="i18n.bt17" select="'Numéro d''attribution'"/>
<xsl:variable name="i18n.bt15" select="'Identifiant de l''accusé de réception'"/>
<xsl:variable name="i18n.bt16" select="'Identifiant du bordereau d''expédition'"/>
<xsl:variable name="i18n.btx202" select="'Identifiant du bon de livraison'"/>
<xsl:variable name="i18n.bt23" select="'Identifiant processus'"/>
<xsl:variable name="i18n.bt24" select="'Identifiant spécification'"/>
<xsl:variable name="i18n.bt18" select="'Identifiant objet'"/>

View File

@@ -1144,17 +1144,29 @@ function downloadData (element_id) {
<xsl:apply-templates select="./xr:VAT_BREAKDOWN"/>
</div>
<xsl:choose>
<xsl:when test="./xr:DOCUMENT_LEVEL_ALLOWANCES">
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig">
<xsl:apply-templates select="./xr:DOCUMENT_LEVEL_ALLOWANCES"/>
</div>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="./xr:DOCUMENT_LEVEL_CHARGES">
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig">
<xsl:apply-templates select="./xr:DOCUMENT_LEVEL_CHARGES"/>
</div>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="./xr:LOGISTICS_SERVICE_CHARGES">
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig">
<xsl:apply-templates select="./xr:LOGISTICS_SERVICE_CHARGES"/>
</div>
</xsl:when>
</xsl:choose>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig first">
<div class="boxzeile">
@@ -2182,11 +2194,24 @@ function downloadData (element_id) {
</div>
<div class="boxzeile">
<div class="boxdaten legende"><xsl:value-of select="$i18n.bt15"/>:</div>
<div id="BT-15" title="BT-15" class="boxdaten wert"><xsl:value-of select="xr:Receiving_advice_reference"/></div>
<div id="BT-15" title="BT-15" class="boxdaten wert">
<xsl:value-of select="xr:Receiving_advice_reference"/>
<xsl:if test="xr:Receiving_advice_reference/@bt-x-201"><xsl:value-of select="concat($i18n.dateOf, format-date(xr:Receiving_advice_reference/@bt-x-201,'[D].[M].[Y]'))"/></xsl:if>
</div>
</div>
<div class="boxzeile">
<div class="boxdaten legende"><xsl:value-of select="$i18n.bt16"/>:</div>
<div id="BT-16" title="BT-16" class="boxdaten wert"><xsl:value-of select="xr:Despatch_advice_reference"/></div>
<div id="BT-16" title="BT-16" class="boxdaten wert">
<xsl:value-of select="xr:Despatch_advice_reference"/>
<xsl:if test="xr:Despatch_advice_reference/@bt-x-200"><xsl:value-of select="concat($i18n.dateOf, format-date(xr:Despatch_advice_reference/@bt-x-200,'[D].[M].[Y]'))"/></xsl:if>
</div>
</div>
<div class="boxzeile">
<div class="boxdaten legende"><xsl:value-of select="$i18n.btx202"/>:</div>
<div id="BT-X-202" title="BT-X-202" class="boxdaten wert">
<xsl:value-of select="xr:Delivery_note_reference"/>
<xsl:if test="xr:Delivery_note_reference/@bt-x-203"><xsl:value-of select="concat($i18n.dateOf, format-date(xr:Delivery_note_reference/@bt-x-203,'[D].[M].[Y]'))"/></xsl:if>
</div>
</div>
<div class="boxzeile">
<div class="boxdaten legende"><xsl:value-of select="$i18n.bt23"/>:</div>

View File

@@ -166,6 +166,118 @@ public class DeSerializationTest extends ResourceCase {
}
public void testDeserializedFiles() {
File inputUBL = getResourceAsFile("XRECHNUNG_Elektron.ubl.xml");
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
zii.doIgnoreCalculationErrors();
Invoice i = new Invoice();
String exText = null;
try {
zii.fromXML(new String(Files.readAllBytes(inputUBL.toPath()), StandardCharsets.UTF_8));
ObjectMapper mapper = new ObjectMapper();
zii.extractInto(i);
String json = mapper.writeValueAsString(i);
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
assertEquals("181301674", i.getNumber());
assertEquals(newInvoiceFromJSON.getNumber(), i.getNumber());
assertEquals(newInvoiceFromJSON.getAdditionalReferencedDocuments()[0].getFilename(), i.getAdditionalReferencedDocuments()[0].getFilename());
assertEquals(newInvoiceFromJSON.getAdditionalReferencedDocuments().length, 2);
} catch (IOException e) {
exText = e.getMessage();
} catch (XPathExpressionException e) {
exText = e.getMessage();
} catch (ParseException e) {
exText = e.getMessage();
}
assertNull(exText);
}
public void testFileSerialization() {
String base64 = "b25ldHdvdGhyZWU=";
String json = "{\n" +
" \"additionalReferencedDocuments\": [\n" +
" {\n" +
" \"data\": \"" + base64 + "\",\n" +
" \"description\": \"Additional file attachment\",\n" +
" \"filename\": \"text.txt\",\n" +
" \"mimetype\": \"text/plain\",\n" +
" \"relation\": \"Data\"\n" +
" }\n" +
"],\n" +
"\n" +
" \"number\": \"471102\",\n" +
" \"currency\": \"EUR\",\n" +
" \"issueDate\": \"2018-03-04T00:00:00.000+01:00\",\n" +
" \"dueDate\": \"2018-03-04T00:00:00.000+01:00\",\n" +
" \"deliveryDate\": \"2018-03-04T00:00:00.000+01:00\",\n" +
" \"sender\": {\n" +
" \"name\": \"Lieferant GmbH\",\n" +
" \"zip\": \"80333\",\n" +
" \"street\": \"Lieferantenstraße 20\",\n" +
" \"location\": \"München\",\n" +
" \"country\": \"DE\",\n" +
" \"taxID\": \"201/113/40209\",\n" +
" \"vatID\": \"DE123456789\",\n" +
" \"globalID\": \"4000001123452\",\n" +
" \"globalIDScheme\": \"0088\"\n" +
" },\n" +
" \"recipient\": {\n" +
" \"name\": \"Kunden AG Mitte\",\n" +
" \"zip\": \"69876\",\n" +
" \"street\": \"Kundenstraße 15\",\n" +
" \"location\": \"Frankfurt\",\n" +
" \"country\": \"DE\"\n" +
" },\n" +
" \"zfitems\": [\n" +
" {\n" +
" \"price\": 9.9,\n" +
" \"quantity\": 20,\n" +
" \"product\": {\n" +
" \"unit\": \"H87\",\n" +
" \"name\": \"Trennblätter A4\",\n" +
" \"description\": \"\",\n" +
" \"vatpercent\": 19,\n" +
" \"taxCategoryCode\": \"S\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"price\": 5.5,\n" +
" \"quantity\": 50,\n" +
" \"product\": {\n" +
" \"unit\": \"H87\",\n" +
" \"name\": \"Joghurt Banane\",\n" +
" \"description\": \"\",\n" +
" \"vatpercent\": 7,\n" +
" \"taxCategoryCode\": \"S\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}\n";
ObjectMapper mapper = new ObjectMapper();
try {
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
zf2p.setProfile(Profiles.getByName("XRechnung"));
zf2p.generateXML(newInvoiceFromJSON);
String theXML = new String(zf2p.getXML());
assertTrue(theXML.contains("<udt:DateTimeString format=\"102\">20180304</udt:DateTimeString>"));
assertTrue(theXML.contains(base64));
} catch (Exception e) {
fail("No exception expected");
}
}
public void testFull300Roundtrip() {
File inputCII = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml");

View File

@@ -22,10 +22,9 @@ package org.mustangproject.ZUGFeRD;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language;
import org.mustangproject.util.ByteArraySearcher;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
@@ -40,155 +39,34 @@ public class VisualizationTest extends ResourceCase {
final String TARGET_PDF_UBL = "./target/testout-Visualization-cii.pdf";
public void testCIIVisualizationBasic() {
// the writing part
String sourceFilename = "factur-x.xml";
File CIIinputFile = getResourceAsFile(sourceFilename);
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
result = zvi.visualize(CIIinputFile.getAbsolutePath(), ZUGFeRDVisualizer.Language.FR)
.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", "");
File expectedResult = getResourceAsFile("factur-x-vis.fr.html");
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8)
.replace("\r", "")
.replace("\n", "")
.replace("\t", "")
.replace(" ", "");
// remove linebreaks as well...
} 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());
} catch (SAXException e) {
fail("SAXException should not happen: " + e.getMessage());
}
assertNotNull(result);
// Reading ZUGFeRD
assertEquals(expected, result);
this.runZUGFeRDVisualization("factur-x.xml", "factur-x-vis.fr.html", Language.FR);
}
public void testCIIVisualizationExtended() {
// the writing part
String sourceFilename = "factur-x-extended.xml";
File CIIinputFile = getResourceAsFile(sourceFilename);
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
result = zvi.visualize(CIIinputFile.getAbsolutePath(), ZUGFeRDVisualizer.Language.DE).replace("\r", "").replace("\n", "")
.replace("\t", "")
.replace(" ", "");
File expectedResult = getResourceAsFile("factur-x-vis-extended.de.html");
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r", "").replace("\n", "")
.replace("\t", "")
.replace(" ", "");
// remove linebreaks as well...
} 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());
} catch (SAXException e) {
fail("SAXException should not happen: " + e.getMessage());
}
assertNotNull(result);
// Reading ZUGFeRD
assertEquals(expected, result);
this.runZUGFeRDVisualization("factur-x-extended.xml", "factur-x-vis-extended.de.html", Language.DE);
}
public void testUBLCreditNoteVisualizationBasic() {
// the writing part
File UBLinputFile = getResourceAsFile("ubl-creditnote.xml");
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
result = zvi.visualize(UBLinputFile.getAbsolutePath(), ZUGFeRDVisualizer.Language.EN).replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", "");
File expectedResult = getResourceAsFile("factur-x-vis-ubl-creditnote.en.html");
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", "");
// remove linebreaks as well...
} 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());
} catch (SAXException e) {
fail("SAXException should not happen: " + e.getMessage());
this.runZUGFeRDVisualization("ubl-creditnote.xml", "factur-x-vis-ubl-creditnote.en.html", Language.EN);
}
assertNotNull(result);
// Reading ZUGFeRD
assertEquals(expected, result);
}
public void testUBLVisualizationBasic() {
this.runZUGFeRDVisualization("ubl/01.01a-INVOICE.ubl.xml", "factur-x-vis-ubl.en.html", Language.EN);
}
// the writing part
File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.xml");
private void runZUGFeRDVisualization(String inputFilename, String resultFileName, Language lang) {
File CIIinputFile = getResourceAsFile(inputFilename);
String expected = null;
String result = null;
try {
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
result = zvi.visualize(UBLinputFile.getAbsolutePath(), ZUGFeRDVisualizer.Language.EN).replace("\r", "").replace("\n", "")
.replace("\t", "")
.replace(" ", "");
result = zvi.visualize(CIIinputFile.getAbsolutePath(), lang);
Files.write(Paths.get("./target/testout-" + resultFileName), result.getBytes(StandardCharsets.UTF_8));
File expectedResult = getResourceAsFile("factur-x-vis-ubl.en.html");
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r", "").replace("\n", "")
.replace("\t", "")
.replace(" ", "");
// remove linebreaks as well...
File expectedResult = getResourceAsFile(resultFileName);
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8)
;
} catch (UnsupportedOperationException e) {
fail("UnsupportedOperationException should not happen: " + e.getMessage());
@@ -198,16 +76,20 @@ public class VisualizationTest extends ResourceCase {
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());
} catch (SAXException e) {
fail("SAXException should not happen: " + e.getMessage());
}
assertNotNull(result);
// Reading ZUGFeRD
assertEquals(expected, 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() {

View File

@@ -261,7 +261,7 @@ public class ZF2PushTest extends TestCase {
.setNumber(number)
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance(new BigDecimal("0.1"))))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50))))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(2.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("AK")))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(2.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK")))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addCharge(new Charge(new BigDecimal(1))).addAllowance(new Allowance(new BigDecimal("1"))))
);
@@ -542,7 +542,7 @@ public class ZF2PushTest extends TestCase {
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
.addCashDiscount(new CashDiscount(new BigDecimal(2), 14))
.setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE)
.setInvoiceReferencedDocumentID("abc123")
.setInvoiceReferencedDocumentID("abc123").addInvoiceReferencedDocument(new ReferencedDocument("abcd1234"))
);
} catch (ParseException e) {
e.printStackTrace();
@@ -587,6 +587,8 @@ public class ZF2PushTest extends TestCase {
Invoice i = zii.extractInvoice();
assertEquals("abc123", i.getInvoiceReferencedDocumentID());
assertEquals(1, i.getInvoiceReferencedDocuments().size());
assertEquals("abcd1234", i.getInvoiceReferencedDocuments().get(0).getIssuerAssignedID());
assertEquals("4304171000002", i.getRecipient().getGlobalID());
assertEquals("2001015001325", i.getZFItems()[0].getProduct().getGlobalID());
assertEquals(orgID, i.getSender().getID());
@@ -803,24 +805,5 @@ public class ZF2PushTest extends TestCase {
match the read grand total */
}
}
public void testRead() {
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("\\Users\\jstaerk\\temp\\1424413_anonymized.xml");
try {
Invoice i = zii.extractInvoice();
TransactionCalculator tc=new TransactionCalculator(i);
assertEquals(0,tc.getGrandTotal().compareTo(new BigDecimal("442.83")));
} catch (XPathExpressionException e) {
fail("XPathExpressionException should not be raised");
} catch (ParseException e) {
fail("ParseException should not be raised");
/* a parseException would also be fired if the calculated grand total does not
match the read grand total */
}
}
}

View File

@@ -466,4 +466,12 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
assertFalse(invoice.getZFItems()[0].getNotes() == null);
assertEquals(1, invoice.getZFItems()[0].getNotes().length);
}
@Test
public void testImportXRechnungWithoutCalculationErrors() throws FileNotFoundException, XPathExpressionException, ParseException {
File inputFile = getResourceAsFile("cii/02.03a-INVOICE_uncefact.xml");
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(inputFile));
assertEquals("0", zii.importedInvoice.getDuePayable().toPlainString());
}
}

File diff suppressed because one or more lines are too long

View File

@@ -359,6 +359,9 @@ GLN 4304171000002
</ram:ActualDeliverySupplyChainEvent>
<ram:DeliveryNoteReferencedDocument>
<ram:IssuerAssignedID>L87654321012</ram:IssuerAssignedID>
<ram:FormattedIssueDateTime>
<qdt:DateTimeString format="102">20241031</qdt:DateTimeString>
</ram:FormattedIssueDateTime>
</ram:DeliveryNoteReferencedDocument>
</ram:ApplicableHeaderTradeDelivery>
<ram:ApplicableHeaderTradeSettlement>

View File

@@ -1085,7 +1085,6 @@
</div>
<div class="boxabstand"></div>
</div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig">
<div class="boxzeile">
<div id="uebersichtVersandkosten" class="box">
@@ -2248,6 +2247,10 @@
<div class="boxdaten legende">Kennung der Versandanzeige:</div>
<div id="BT-16" title="BT-16" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Kennung des Lieferscheins:</div>
<div id="BT-X-202" title="BT-X-202" class="boxdaten wert">L87654321012 vom 31.10.2024</div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Prozesskennung:</div>
<div id="BT-23" title="BT-23" class="boxdaten wert"></div>

View File

@@ -1359,7 +1359,6 @@
</div>
<div class="boxabstand"></div>
</div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig first">
<div class="boxzeile">
<div id="uebersichtZahlungsinformationen" class="box subBox">
@@ -2355,6 +2354,10 @@
<div class="boxdaten legende">Dispatch note ID:</div>
<div id="BT-16" title="BT-16" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Delivery note ID:</div>
<div id="BT-X-202" title="BT-X-202" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Process ID:</div>
<div id="BT-23" title="BT-23" class="boxdaten wert"></div>

View File

@@ -1219,9 +1219,6 @@
</div>
</div>
</div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig first">
<div class="boxzeile">
<div id="uebersichtZahlungsinformationen" class="box subBox">
@@ -1755,6 +1752,10 @@
<div class="boxdaten legende">Dispatch note ID:</div>
<div id="BT-16" title="BT-16" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Delivery note ID:</div>
<div id="BT-X-202" title="BT-X-202" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Process ID:</div>
<div id="BT-23" title="BT-23" class="boxdaten wert"></div>

View File

@@ -1250,9 +1250,6 @@
</div>
</div>
</div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig"></div>
<div class="boxtabelle boxabstandtop boxtabelleZweispaltig first">
<div class="boxzeile">
<div id="uebersichtZahlungsinformationen" class="box subBox">
@@ -1927,6 +1924,10 @@
<div class="boxdaten legende">Identifiant du bordereau d'expédition:</div>
<div id="BT-16" title="BT-16" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Identifiant du bon de livraison:</div>
<div id="BT-X-202" title="BT-X-202" class="boxdaten wert"></div>
</div>
<div class="boxzeile">
<div class="boxdaten legende">Identifiant processus:</div>
<div id="BT-23" title="BT-23" class="boxdaten wert"></div>

View File

@@ -3,8 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<packaging>pom</packaging>
<version>2.15.3-SNAPSHOT</version> <packaging>pom</packaging>
<name>Mustang</name>
<modules>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
@@ -11,7 +11,7 @@
<name>Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung)</name>
<packaging>jar</packaging>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<repositories>
<repository>
<!-- for jargs -->
@@ -38,7 +38,7 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>library</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>