Merge branch 'master' into buyerorderid
This commit is contained in:
202
History.md
202
History.md
@@ -1,5 +1,203 @@
|
||||
- 461
|
||||
- 463
|
||||
#820/801/815
|
||||
#822
|
||||
#843
|
||||
#854
|
||||
#786
|
||||
add getCalculation on calculatedInvoice
|
||||
Transactioncalculator getTaxDetails to include correct percentage, calculated amounts
|
||||
#764
|
||||
|
||||
2.16.5
|
||||
=======
|
||||
2025-05-22
|
||||
- #819 correct generateXSLTFromSchematron profile
|
||||
- #458 Its not possible to add multiple PaymentTerms when using the extended profile
|
||||
- #788 Invoice taxes aren't acessible
|
||||
- #790 Crash upon importing an invoice with empty name.
|
||||
- upgrade from PDFBOX 3.0.2 to 3.0.5
|
||||
- #817 dded ZUGFeRD.PDF-csharp to list of PDF creators
|
||||
- #811 Fix typo in xpath when extracting buyer trade party address
|
||||
- #833 support zugferd 2.3.3/fx 1.07.3
|
||||
- #835 UBL does not read item allowances/charges
|
||||
- #816 Wrong grand total amount for UBL invoices
|
||||
|
||||
2.16.4
|
||||
=======
|
||||
2025-04-19
|
||||
- #722 extend ValidationLogVisualizer to not use only file system
|
||||
- #774 disable XML parsing entities
|
||||
- #742/#753 "Adresszusatz 1" (LineTwo) showing up as "Postfach" in HTML visualization
|
||||
- #778 Added XEE Protection features (that were missing according to Stackoverflow)
|
||||
- #759 Use the dedicated class instead of var type
|
||||
- #614, #770 Exemption reason text should not be reused
|
||||
- #728 Invoice setCorrection causes duplicate XML output
|
||||
- #776 Fix potential resource leaks in core file processing classes
|
||||
- #741 read position accountingReference
|
||||
- #782/771 prevent NullPointerException on Product Description
|
||||
- #772 TradeParty Name should be optional for ShipToTradeParty
|
||||
- #775 not deleted tmp files
|
||||
- #802 fix: capital letter for ID in listID
|
||||
- #809 invoice reader to support multiple charges per item
|
||||
- #812 fileattachment relation should have a default
|
||||
- #818 need exceptions from files validated with validateExpectValid
|
||||
|
||||
|
||||
2.16.3
|
||||
=======
|
||||
2025-03-03
|
||||
- #558 ZUGFeRDInvoiceImporter does not read BankDetails.accountName
|
||||
- #686 Item: add BillingSpecifiedPeriod
|
||||
- #739 also parse invoiceperiod from ubl
|
||||
- #745 be able to specify legalorganisation id without schema
|
||||
- #747 correct profile detection
|
||||
- #710 Validation Error due to empty elements
|
||||
- #712 Correct bracket setting on condition for output of allowance reason.
|
||||
- #725 Unable to perform XML-oriented attacks
|
||||
- #685 Security Issue: XXE Vulnerability in ZUGFeRDInvoiceImporter (PR #725)
|
||||
- #761 Allow to set item allowance/charges from JSON
|
||||
|
||||
2.16.2
|
||||
=======
|
||||
2025-02-04
|
||||
- #705 specifiedLogisticsCharge is not imported
|
||||
- #707 invoiceimporter may fail if certain values are not set
|
||||
- #708 embedded files cannot be determined
|
||||
- #709 ZUGFeRDInvoiceImporter ignored "first" embedded file in list of pdf attachments
|
||||
- #607 Enable flexible PaymentReference and a DocumentName.
|
||||
- #649 Reuse toPDF method to work without any dependencies to the file system
|
||||
- #650 Add net.sf.offo:fop-hyph
|
||||
- #665 Fix #632: Return ubl_creditnote as Standard for CreditNotes
|
||||
- #684 Optimize validation-report to pdf functionality
|
||||
- #703 Fill TaxExemptionReason during InvoiceImport.
|
||||
- #701 Ensure Base64 decoding can handle newlines when decoding a FileAttachment
|
||||
- #691 Fix current check failures.
|
||||
|
||||
2.16.1
|
||||
=======
|
||||
2025-01-21
|
||||
- #678 some ubl creditnote attributes are not parsed
|
||||
- #679 validation of a XR does not ignore whitespace
|
||||
- #681 IBAN assigned to invoice sender not recipient on direct debit
|
||||
- #689 incorrect element order when both charge reason and reasoncode are specified
|
||||
- be able to set detailedDeliveryPeriodFrom, detailedDeliveryPeriodTo MS188
|
||||
- updated verapdf from 1.26.1 to 1.26.2
|
||||
- cashDiscount JSON now corrently ignores values for cii and xr methods
|
||||
|
||||
2.16.0
|
||||
=======
|
||||
2025-01-10
|
||||
- #657 allow allowancechargereasoncodes on document level
|
||||
- allow to add includedNotes with type
|
||||
- #356 print version of xml report
|
||||
- #645 Fix visualization of validation logs
|
||||
- #639 Fix invoice calculation if rounding amount is present
|
||||
- #633 Bump ch.qos.logback:logback-core from 1.2.13 to 1.5.13
|
||||
- #626 Fix minor java issues
|
||||
- #622 Fix FOP config
|
||||
- #631 multiple invoice referenced documents
|
||||
- #629 Visualizing xml
|
||||
- #630 Fix issue #296 (Validation-Error: Ungültiger Content wurde beginnend mit Element 'ram:DueDateDateTime' ) (duplicate of #565)
|
||||
- #658 prevent nullpointerexception
|
||||
- #648 Fix log visualization
|
||||
- #651 ZUGFeRDInvoiceImporter: Item-Allowances not imported
|
||||
- #652 Discount VAT is not subtracted from duepayable
|
||||
- #620 Fix logback config
|
||||
- #653 ZF2EdgeTest: methods 'getPaymentMeansCode()' & 'getPaymentMeansInformation()' does not override super methods
|
||||
- #654 remove wrong test methods
|
||||
|
||||
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
|
||||
=======
|
||||
2024-12-07
|
||||
- #566 Failed to parse PDF - Could not reproduce the invoice
|
||||
- closes #579 prepaidamount is only read in UBL
|
||||
- #581 parse lineTotalAmount, TaxBasisAmount, duePayableAmount
|
||||
- #576 read lineid, #578 set lineid
|
||||
- log error ids, be able to access them from context
|
||||
- #503 import more ubl
|
||||
- allow jackson to run over more classes, e.g., DirectDebit, bean contructor for direct debit
|
||||
- allow json in document includedNotes and
|
||||
- #591 Import IncludedNotes also on item level
|
||||
- #595 Treat all fatal XR schematron rules as errors, not as warnings.
|
||||
- #577 dom4j: exclude all (optional) dependencies to avoid potential conflicts.
|
||||
- #575 Fix compilation problems.
|
||||
- #573 Feature/category code
|
||||
- #601 exceptions in metrics
|
||||
- #606 also parse BT-25
|
||||
|
||||
|
||||
2.15.0
|
||||
=======
|
||||
2024-11-18
|
||||
- #435 use invoiceimporter as common technical basis also for zugferdimporter
|
||||
- also import delivery address
|
||||
- #527 metrics may raise error on some pdf files
|
||||
- #517 read product GlobalID
|
||||
- #380 Added test for input stream validation
|
||||
- #518 corrently validate more XRechnung versions
|
||||
- make document charges and allowances serializable
|
||||
- #523 Verapdf claims PDF-A/3s created witth visualize are invalid
|
||||
- #530 duedate can not be set directly
|
||||
- #532 support validation warnings!
|
||||
- #534 new signature
|
||||
- #538 Mustang validator always claims PDF is invalid if flavour is PDF/A-3A
|
||||
- #555 be able to validate ubl credit notes
|
||||
- when parsing now distinguishing between the parseExceptions StructureException and ArithmetricException
|
||||
- #554 Import IncludedNotes on invoice extraction
|
||||
- #542 Visualize SpecifiedLogisticsServiceCharge
|
||||
- #531 Support ZUGFeRD 2.3.2
|
||||
- #480 Support netherlands Euro
|
||||
|
||||
2.14.2
|
||||
=======
|
||||
2024-10-14
|
||||
|
||||
- also parse BICs in InvoiceImporter not only IBANs
|
||||
- #509 CLI currently does not write a logfile
|
||||
- #505 crash after invoking ZUGFeRD2PullProvider
|
||||
- #506 Fix POM missing dependencies
|
||||
|
||||
2.14.1
|
||||
=======
|
||||
2024-10-06
|
||||
|
||||
- #481 also be able to convert XRechnung/UBL to PDF not only CII
|
||||
- #494 Quantity/Price Decimal Places
|
||||
- #391 Runden bei Negativwerten
|
||||
- #491/501 non terminating decimal expansion
|
||||
- upgraded en16931 cen schematron to 1.3.12
|
||||
- #499/500 PDF layout corrections
|
||||
|
||||
2.14.0
|
||||
=======
|
||||
2024-09-22
|
||||
|
||||
- #461 UBL import contacts
|
||||
- #463 add support for BT-33, i.e. Tradeparty description #463
|
||||
- #456 Provide a way to set uriUniversalCommunicationId on the TradeParty using JSON deserialization
|
||||
- #467 Fix test using wrong file
|
||||
- #468 Fix validator dependencies
|
||||
- #469 Enable EN16931 schema validation for XRechnung
|
||||
- #471 Fix LegalOrganisation schemeId
|
||||
- #473 Fix UnsupportedOperationException in buildNotes
|
||||
- #476 Add DesignatedProductClassification for SpecifiedTradeProduct
|
||||
- #482 Fix current validation errors
|
||||
- #423 can no longer add attachments via cli
|
||||
- #465 cli version should also be able to combine PDF/A-3 source
|
||||
- #487 update to zugferd 2.3.0
|
||||
- #472 Fix logging implementation missing in CLI
|
||||
- #479 Re-Formatted and re-organized POMs - Part 2
|
||||
|
||||
|
||||
2.13.0
|
||||
=======
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.14.0-SNAPSHOT</version>
|
||||
<version>2.17.0-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.14.0-SNAPSHOT</version>
|
||||
<version>2.17.0-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.14.0-SNAPSHOT</version>
|
||||
<version>2.17.0-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
|
||||
-->
|
||||
@@ -35,6 +35,26 @@
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- logging -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.5.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- This library is needed so that logback stderr output is sent to, well, stderr, otherwise it lands in stdout -->
|
||||
<groupId>org.codehaus.janino</groupId>
|
||||
<artifactId>janino</artifactId>
|
||||
<version>3.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
<!-- end of logging -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
@@ -51,7 +51,9 @@ public class FileChecker {
|
||||
if ((!isPDF) && (!thisRun.shallIgnoreFileExt())) {
|
||||
return false;
|
||||
}
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(filename);
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter();
|
||||
zi.doIgnoreCalculationErrors();
|
||||
zi.setPDFFilename(filename);
|
||||
try {
|
||||
if (zi.canParse()) {
|
||||
thisRun.incZUGFeRDCount(zi.getVersion());
|
||||
|
||||
@@ -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;
|
||||
@@ -72,10 +73,10 @@ public class Main {
|
||||
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED\n"
|
||||
+ " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n"
|
||||
+ " [--no-additional-attachments]: prevent prompting for attachments\n"
|
||||
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
|
||||
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
|
||||
+ " [--source <filename>]: set input XML file\n"
|
||||
+ " [--out <filename>]: set output XML file\n"
|
||||
+ " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
|
||||
+ " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
|
||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||
+ " [--source <filename>]: set input XML ZUGFeRD 1 file\n"
|
||||
+ " [--out <filename>]: set output XML ZUGFeRD 2 file\n"
|
||||
@@ -84,14 +85,19 @@ 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"
|
||||
+ " --action validateExpectInvalid validate directory expecting negative results \n"
|
||||
+ " [--log-as-pdf]: save log output as pdf\n"
|
||||
+ " --action validateExpectInvalid validate directory recursively expecting negative results \n"
|
||||
+ " [--no-notices]: refrain from reporting notices\n"
|
||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||
+ " -d, --directory to check recursively\n"
|
||||
+ " --action validateExpectValid validate directory expecting positive results \n"
|
||||
+ " Additional parameters (optional)\n"
|
||||
+ " --exclude: comma-separated list of filenames to ignore\n"
|
||||
+ " --action validateExpectValid validate directory recursively expecting positive results \n"
|
||||
+ " [--no-notices]: refrain from reporting notices\n"
|
||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||
+ " -d, --directory to check recursively \n"
|
||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||
+ " --exclude: comma-separated list of filenames to ignore\n"
|
||||
+ " --action visualize convert XML to HTML \n"
|
||||
+ " [--language <lang>]: set output lang (en, fr or de)\n"
|
||||
+ " [--source <filename>]: set input XML file\n"
|
||||
@@ -305,12 +311,13 @@ public class Main {
|
||||
// Plain Java
|
||||
// based on https://mkyong.com/java/how-to-convert-inputstream-to-string-in-java/
|
||||
private static String convertInputStreamToString(InputStream is) {
|
||||
int DEFAULT_BUFFER_SIZE = 8192;
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
int length;
|
||||
try {
|
||||
while ((length = is.read(buffer)) != -1) {
|
||||
try (InputStream inputStream = is) {
|
||||
int DEFAULT_BUFFER_SIZE = 8192;
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
int length;
|
||||
|
||||
while ((length = inputStream.read(buffer)) != -1) {
|
||||
result.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
@@ -318,11 +325,10 @@ public class Main {
|
||||
return result.toString(StandardCharsets.UTF_8.name());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
// Java 10
|
||||
// return result.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
return null;
|
||||
// Java 10
|
||||
// return result.toString(StandardCharsets.UTF_8);
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -345,8 +351,11 @@ public class Main {
|
||||
Option attachmentOpt = new Option("attachments", "attachments", true, "File attachments");
|
||||
attachmentOpt.setValueSeparator(',');
|
||||
attachmentOpt.setArgs(Option.UNLIMITED_VALUES);
|
||||
|
||||
options.addOption(attachmentOpt);
|
||||
Option excludeOpt = new Option("exclude", "exclude", true, "Files to exclude from recursive directory traversal");
|
||||
excludeOpt.setValueSeparator(',');
|
||||
excludeOpt.setArgs(Option.UNLIMITED_VALUES);
|
||||
options.addOption(excludeOpt);
|
||||
options.addOption(new Option("source", "source", true, "which source file to use"));
|
||||
options.addOption(new Option("source-xml", "source-xml", true, "which source file to use"));
|
||||
options.addOption(new Option("language", "language", true, "output language (en, de or fr)"));
|
||||
@@ -357,6 +366,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,11 +390,13 @@ 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");
|
||||
|
||||
String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null;
|
||||
String[] excludedFilenames = cmd.hasOption("exclude") ? cmd.getOptionValues("exclude") : null;
|
||||
|
||||
|
||||
ArrayList<FileAttachment> attachments = new ArrayList<>();
|
||||
@@ -427,11 +439,11 @@ 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);
|
||||
optionsRecognized = performValidateExpect(true, directoryName, excludedFilenames);
|
||||
} else if ((action != null) && (action.equals("validateExpectInvalid"))) {
|
||||
optionsRecognized = performValidateExpect(false, directoryName);
|
||||
optionsRecognized = performValidateExpect(false, directoryName, excludedFilenames);
|
||||
}
|
||||
|
||||
} catch (UnrecognizedOptionException ex) {
|
||||
@@ -454,7 +466,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 +478,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);
|
||||
@@ -474,8 +495,8 @@ public class Main {
|
||||
return optionsRecognized;
|
||||
}
|
||||
|
||||
private static boolean performValidateExpect(boolean valid, String dirName) {
|
||||
ValidatorFileWalker zfWalk = new ValidatorFileWalker(valid);
|
||||
private static boolean performValidateExpect(boolean valid, String dirName, String[] excludedFiles) {
|
||||
ValidatorFileWalker zfWalk = new ValidatorFileWalker(valid, excludedFiles);
|
||||
Path startingDir = Paths.get(dirName);
|
||||
try {
|
||||
Files.walkFileTree(startingDir, zfWalk);
|
||||
@@ -732,12 +753,13 @@ public class Main {
|
||||
ze.disableFacturX();
|
||||
}
|
||||
|
||||
ze.setXML(Files.readAllBytes(Paths.get(xmlName)));
|
||||
|
||||
for (FileAttachment attachment : attachments) {
|
||||
ze.attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation());
|
||||
}
|
||||
|
||||
ze.setXML(Files.readAllBytes(Paths.get(xmlName)));
|
||||
|
||||
ze.export(outName);
|
||||
System.out.println("Written to " + outName);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mustangproject.commandline;
|
||||
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
@@ -11,25 +10,29 @@ import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.mustangproject.validator.ZUGFeRDValidator;
|
||||
|
||||
import static org.xmlunit.assertj.XmlAssert.assertThat;
|
||||
|
||||
public class ValidatorFileWalker
|
||||
extends SimpleFileVisitor<Path> {
|
||||
public class ValidatorFileWalker
|
||||
extends SimpleFileVisitor<Path> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorFileWalker.class.getCanonicalName()); // log
|
||||
protected PathMatcher matcher;
|
||||
protected ZUGFeRDValidator zul;
|
||||
protected int fileCount=1;
|
||||
protected boolean expectValid=true;
|
||||
protected boolean allValid=true;
|
||||
protected int fileCount = 1;
|
||||
protected boolean expectValid = true;
|
||||
protected boolean allValid = true;
|
||||
protected String[] excludedFiles = {};
|
||||
|
||||
public ValidatorFileWalker(boolean expectValid) {
|
||||
public ValidatorFileWalker(boolean expectValid, String[] excludedFiles) {
|
||||
this.zul = new ZUGFeRDValidator();
|
||||
this.expectValid=expectValid;
|
||||
this.expectValid = expectValid;
|
||||
this.excludedFiles = excludedFiles;
|
||||
matcher = FileSystems.getDefault().getPathMatcher("glob:*.{pdf,xml}");
|
||||
|
||||
}
|
||||
@@ -37,54 +40,58 @@ public class ValidatorFileWalker
|
||||
public boolean getResult() {
|
||||
return allValid;
|
||||
}
|
||||
// Print information about
|
||||
// each type of file.
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file,
|
||||
BasicFileAttributes attr) {
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
//get current date time with Date()
|
||||
Date date = new Date();
|
||||
String expectedString="valid";
|
||||
if (!expectValid) {
|
||||
expectedString="invalid";
|
||||
}
|
||||
if ((attr!=null)&&(attr.isRegularFile())) {
|
||||
if (matcher.matches(file.getFileName())) {
|
||||
String thisResultString=" valid";
|
||||
try {
|
||||
assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status")
|
||||
.asString()
|
||||
.isEqualTo(expectedString);
|
||||
|
||||
} catch (AssertionError ae) {
|
||||
thisResultString="invalid";
|
||||
allValid=false;
|
||||
}
|
||||
LOGGER.info(String.format("\n@%s Testing file %d: %s (%s)", dateFormat.format(date), fileCount++, thisResultString, file));
|
||||
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
// Print each directory visited.
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir,
|
||||
IOException exc) {
|
||||
LOGGER.info("\nDirectory: %s%n", dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
// Print information about
|
||||
// each type of file.
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file,
|
||||
BasicFileAttributes attr) {
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
//get current date time with Date()
|
||||
Date date = new Date();
|
||||
String expectedString = "valid";
|
||||
if (!expectValid) {
|
||||
expectedString = "invalid";
|
||||
}
|
||||
if ((attr != null) && (attr.isRegularFile())) {
|
||||
if (matcher.matches(file.getFileName())) {
|
||||
// I could have extended the path matcher but an exclusion list is quite simple
|
||||
if ((excludedFiles == null) || (!Arrays.asList(excludedFiles).contains(file.getFileName().toString()))) {
|
||||
|
||||
// If there is some error accessing
|
||||
// the file, let the user know.
|
||||
// If you don't override this method
|
||||
// and an error occurs, an IOException
|
||||
// is thrown.
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file,
|
||||
IOException exc) {
|
||||
LOGGER.error(exc.getMessage(),exc);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
String thisResultString = " valid";
|
||||
try {
|
||||
assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status")
|
||||
.asString()
|
||||
.isEqualTo(expectedString);
|
||||
|
||||
} catch (AssertionError ae) {
|
||||
thisResultString = "invalid";
|
||||
allValid = false;
|
||||
}
|
||||
LOGGER.info(String.format("\n@%s Testing file %d: %s (%s) ", dateFormat.format(date), fileCount++, thisResultString, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
// Print each directory visited.
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir,
|
||||
IOException exc) {
|
||||
LOGGER.info(String.format("\nDirectory: %s \n", dir));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
// If there is some error accessing
|
||||
// the file, let the user know.
|
||||
// If you don't override this method
|
||||
// and an error occurs, an IOException
|
||||
// is thrown.
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file,
|
||||
IOException exc) {
|
||||
LOGGER.error(exc.getMessage(), exc);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender" >
|
||||
<!--<appender name="FILE" class="org.mustangproject.commandline.LazyRollingFileAppender" >-->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- Daily rollover -->
|
||||
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
|
||||
|
||||
<!-- Keep 5 years worth of history -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
</then>
|
||||
</if>
|
||||
|
||||
<!-- Configure so that it outputs to both console and log file -->
|
||||
<root level="INFO">
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<appender-ref ref="FILE" />
|
||||
</then>
|
||||
</if>
|
||||
<appender-ref ref="STDERR" />
|
||||
</root>
|
||||
</configuration>
|
||||
<configuration>
|
||||
<appender name="STDERR"
|
||||
class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<appender name="FILE"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- Daily rollover -->
|
||||
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
|
||||
<!-- Keep 5 years worth of history -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
</then>
|
||||
</if>
|
||||
<!-- Configure so that it outputs to both console and log file -->
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</then>
|
||||
</if>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDERR"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.mustangproject.commandline;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -10,18 +8,43 @@ import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CliIT {
|
||||
|
||||
public static File getResourceAsFile(String resourcePath) {
|
||||
try {
|
||||
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
|
||||
if (in == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(tempFile)) {
|
||||
// copy stream
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
return tempFile;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCii2Ubl() throws Exception {
|
||||
Path output = Paths.get("target/ubl.xml");
|
||||
Files.deleteIfExists(output);
|
||||
Path jar = Files.newDirectoryStream(Paths.get("target"), "Mustang-CLI-*.jar").iterator().next();
|
||||
ProcessBuilder pb = new ProcessBuilder("java", "-jar", jar.toString(),
|
||||
"--action", "ubl", "--source", "src/test/resources/cii.xml", "--out",
|
||||
output.toString());
|
||||
"--action", "ubl", "--source", "src/test/resources/cii.xml", "--out",
|
||||
output.toString());
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
String result = getOutput(process);
|
||||
@@ -43,4 +66,17 @@ public class CliIT {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetric() {
|
||||
StatRun sr = new StatRun();
|
||||
File tempFile = getResourceAsFile("corrupt-factur-x-waytoosmall.pdf");
|
||||
|
||||
FileChecker fc = new FileChecker(tempFile.getAbsolutePath(), sr);
|
||||
|
||||
fc.checkForZUGFeRD();
|
||||
System.out.print(fc.getOutputLine());
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%PDF-1.4
|
||||
%ェォャュ
|
||||
10
README.md
10
README.md
@@ -32,16 +32,14 @@ If you set up a Maven project, you can reference the mustang artifact like this:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>library</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>validator</artifactId>
|
||||
<version>2.17.0</version>
|
||||
<classifier>shaded</classifier>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Further docs on how to develop **with** mustang:
|
||||
|
||||
- [ZugferdDev.en.pdf](https://github.com/ZUGFeRD/mustangproject/blob/master/doc/ZugferdDev.en.pdf?raw=true): The English mustang user documentation.
|
||||
- [ZugferdDev.de.pdf](https://github.com/ZUGFeRD/mustangproject/blob/master/doc/ZugferdDev.de.pdf?raw=true): The German mustang user documentation.
|
||||
- [Usage examples](https://www.mustangproject.org/use/): Read and write electronic invoices.
|
||||
- [Mustang classes](https://www.mustangproject.org/invoice-class/): Using the mustang classes.
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ Additionally, the Mustang validator now also supports XRechnung 2, can now also
|
||||
|
||||
|
||||
### Use on command line
|
||||
`java -jar Mustang-CLI-2.0.0-alpha3.jar --action=combine` embeds a XML into a PDF (A-1) file and exports as PDF/A-3
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=combine` embeds a XML into a PDF (A-1) file and exports as PDF/A-3
|
||||
|
||||
`java -jar mustang-cli.jar --action=extract` extracts XML from a ZUGFeRD file and
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=extract` extracts XML from a ZUGFeRD file and
|
||||
|
||||
`java -jar mustang-cli.jar --action=validate` validates XML or PDF files.
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=validate` validates XML or PDF files.
|
||||
|
||||
`java -jar mustang-cli.jar --help` still outputs the parameters which can be used
|
||||
`java -jar Mustang-CLI-2.14.0.jar --help` still outputs the parameters which can be used
|
||||
to for non-interactive (i.e., batch) processing.
|
||||
|
||||
|
||||
@@ -37,12 +37,13 @@ from `-f` (ZUV) to the usual `--source`.
|
||||
|
||||
We're now on maven central, please remove the old github repository. Additionally, the following changed
|
||||
|
||||
| What | old value | new value |
|
||||
|---|---|---|
|
||||
| Group id | org.mustangproject.zugferd | org.mustangproject|
|
||||
| Artifact ID | mustang | library |
|
||||
| Version | 1.7.8 | 2.0.0-alpha3 |
|
||||
| What | old value | new value |
|
||||
|---|---|--------------------|
|
||||
| Group id | org.mustangproject.zugferd | org.mustangproject |
|
||||
| Artifact ID | mustang | library |
|
||||
| Version | 1.7.8 | 2.14.0 |
|
||||
|
||||
And please use the shaded classifier.
|
||||
If you want you can also embed the validator in your software using validator
|
||||
as artifact ID. "validator" includes the library functionality but is >20 MB
|
||||
bigger due to its dependencies.
|
||||
@@ -200,7 +201,7 @@ read/write ZUGFeRD-invoices like the (smaller) library module.
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>validator</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions are currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x.x | :white_check_mark: |
|
||||
| < 2.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Feel free to submit issues to info at mustangproject.org with [security] indicated in the subject.
|
||||
We may ask back questions but we usually open (or communicate about) an issue (potentially in a private location you would be provided with access to) and decide on the severity within two working days.
|
||||
|
||||
Please indicate
|
||||
* a proof of concept, if possible
|
||||
* If any of the information you submit, e.g. an invoice which can not be [anonymized](https://github.com/ZUGFeRD/einvoice-anonymizer), is confidential
|
||||
* A quick justification why you require a fix in a older version than he most up to date one, if you can not update to the most recent version
|
||||
* If you require encrypted communication (our GPG fingerprint will likely be 68F4 2269 8165 F0F5 63CA A13B 7CB7 1548 B596 66A3)
|
||||
|
||||
|
||||
## After your Report
|
||||
|
||||
We try to fix critical issues in less than a week, and release a fixed version in less than two weeks.
|
||||
|
||||
Thank you for keeping our software safe!
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
package mustangtest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IExportableTransaction;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExporter;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDExporterFromA1;
|
||||
|
||||
class Contact implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
public String getCountry() {
|
||||
return "DE";
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return "Spielkreis";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "Theodor Est";
|
||||
}
|
||||
|
||||
public String getStreet() {
|
||||
return "Bahnstr. 42";
|
||||
}
|
||||
|
||||
public String getVATID() {
|
||||
return "DE999999999";
|
||||
}
|
||||
|
||||
public String getZIP() {
|
||||
return "88802";
|
||||
}
|
||||
}
|
||||
|
||||
class Item implements IZUGFeRDExportableItem {
|
||||
public Item(BigDecimal price, BigDecimal quantity, Product product) {
|
||||
super();
|
||||
this.price = price;
|
||||
this.quantity = quantity;
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
private BigDecimal price, quantity;
|
||||
private Product product;
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(BigDecimal price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
public void setProduct(Product product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceCharge[] getItemAllowances() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceCharge[] getItemCharges() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Product implements IZUGFeRDExportableProduct {
|
||||
public Product(String description, String name, String unit, BigDecimal vATPercent) {
|
||||
super();
|
||||
this.description = description;
|
||||
this.name = name;
|
||||
this.unit = unit;
|
||||
VATPercent = vATPercent;
|
||||
}
|
||||
|
||||
private String description, name, unit;
|
||||
private BigDecimal VATPercent;
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public BigDecimal getVATPercent() {
|
||||
return VATPercent;
|
||||
}
|
||||
|
||||
public void setVATPercent(BigDecimal vATPercent) {
|
||||
VATPercent = vATPercent;
|
||||
}
|
||||
}
|
||||
|
||||
public class MustangWriter implements IExportableTransaction {
|
||||
|
||||
private void apply() {
|
||||
try {
|
||||
System.out.println("Reading Blanko-PDF");
|
||||
IZUGFeRDExporter ze = new ZUGFeRDExporterFromA1().setProducer("My Application")
|
||||
.setCreator(System.getProperty("user.name"))
|
||||
.load("./MustangGnuaccountingBeispielRE-20170509_505blanko.pdf");
|
||||
System.out.println("Generating and attaching ZUGFeRD-Data");
|
||||
ze.setTransaction(this);
|
||||
System.out.println("Writing ZUGFeRD-PDF");
|
||||
ze.export("./MustangGnuaccountingBeispielRE-20170509_505new.pdf");
|
||||
System.out.println("Done.");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MustangWriter mw=new MustangWriter();
|
||||
mw.apply();
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return "EUR";
|
||||
}
|
||||
|
||||
public Date getDeliveryDate() {
|
||||
return new GregorianCalendar(2017, Calendar.NOVEMBER, 17).getTime();
|
||||
}
|
||||
|
||||
public Date getDueDate() {
|
||||
return new GregorianCalendar(2017, Calendar.DECEMBER, 9).getTime();
|
||||
}
|
||||
|
||||
public Date getIssueDate() {
|
||||
return new GregorianCalendar(2017, Calendar.NOVEMBER, 18).getTime();
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
// TODO Auto-generated method stub
|
||||
return "RE-20171118/506";
|
||||
}
|
||||
|
||||
public String getOwnBIC() {
|
||||
// TODO Auto-generated method stub
|
||||
return "COBADEFFXXX";
|
||||
}
|
||||
|
||||
public String getOwnBankName() {
|
||||
// TODO Auto-generated method stub
|
||||
return "Commerzbank";
|
||||
}
|
||||
|
||||
public String getOwnCountry() {
|
||||
// TODO Auto-generated method stub
|
||||
return "DE";
|
||||
}
|
||||
|
||||
public String getOwnIBAN() {
|
||||
// TODO Auto-generated method stub
|
||||
return "DE88 2008 0000 0970 3757 00";
|
||||
}
|
||||
|
||||
public String getOwnLocation() {
|
||||
// TODO Auto-generated method stub
|
||||
return "Stadthausen";
|
||||
}
|
||||
|
||||
public String getOwnOrganisationFullPlaintextInfo() {
|
||||
// TODO Auto-generated method stub
|
||||
return "Bei Spiel GmbH\n" + "Ecke 12\n" + "12345 Stadthausen\n" + "Geschäftsführer: Max Mustermann";
|
||||
}
|
||||
|
||||
public String getOwnOrganisationName() {
|
||||
// TODO Auto-generated method stub
|
||||
return "Bei Spiel GmbH";
|
||||
}
|
||||
|
||||
public String getOwnPaymentInfoText() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getOwnStreet() {
|
||||
return "Ecke 12";
|
||||
}
|
||||
|
||||
public String getOwnTaxID() {
|
||||
return "22/815/0815/4";
|
||||
}
|
||||
|
||||
public String getOwnVATID() {
|
||||
return "DE136695976";
|
||||
}
|
||||
|
||||
public String getOwnZIP() {
|
||||
return "12345";
|
||||
}
|
||||
|
||||
public String getPaymentTermDescription() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public IZUGFeRDExportableTradeParty getRecipient() {
|
||||
return new Contact();
|
||||
}
|
||||
|
||||
public String getReferenceNumber() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceCharge[] getZFAllowances() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceCharge[] getZFCharges() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public IZUGFeRDExportableItem[] getZFItems() {
|
||||
Item[] allItems = new Item[3];
|
||||
Product designProduct = new Product("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung", "HUR",
|
||||
new BigDecimal("7.000000"));
|
||||
Product balloonProduct = new Product("", "Luftballon: Bunt, ca. 500ml", "C62", new BigDecimal("19.000000"));
|
||||
Product airProduct = new Product("", "Heiße Luft pro Liter", "LTR", new BigDecimal("19.000000"));
|
||||
allItems[0] = new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct);
|
||||
allItems[1] = new Item(new BigDecimal("0.79"), new BigDecimal("400"), balloonProduct);
|
||||
allItems[2] = new Item(new BigDecimal("0.10"), new BigDecimal("200"), airProduct);
|
||||
return allItems;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
446
doc/ZugferdDev.de.adoc
Normal file
446
doc/ZugferdDev.de.adoc
Normal file
@@ -0,0 +1,446 @@
|
||||
== [#anchor]####Mustang Project Nutzerdokumentation
|
||||
|
||||
Jochen Stärk
|
||||
|
||||
zu Mustangproject 1.5.1, 13.01.2018
|
||||
|
||||
http://www.mustangproject.org/[http://www.mustangproject.org]
|
||||
|
||||
== [#anchor-1]####Über Mustangproject
|
||||
|
||||
Mustangproject ist eine Java-Bibliothek zur Unterstützung von
|
||||
erweiterten („ZUGFeRD“-)Metadaten in PDF-Rechnungsdateien. Sie verwendet
|
||||
als Eingabe PDF/A-Dateien, benötigt die Apache PDFBox-Bibliothek und
|
||||
unterliegt wie diese der APL-Lizenz. Sie ist daher, entsprechend den
|
||||
Richtlinien der Apache Public License, unter Einbettung einer
|
||||
entsprechenden „Notice“-Datei kostenlos einsetzbar in kommerziellen und
|
||||
nichtkommerziellen Projekten.
|
||||
|
||||
== [#anchor-2]####Übersicht von ZUGFeRD-Lösungen
|
||||
|
||||
[cols=",,,,,,,,,,,",]
|
||||
|===
|
||||
| |Plattform |Lizenz |ZF Versionen |Funktionsumfang |Geeignet für |Preis
|
||||
| | | | |
|
||||
|
||||
|Lesen |XML erzeugen |PDF Schreiben |PDF/A-Umwandlung |Kommerz. Software
|
||||
|Freeware |Open Source | | | | |
|
||||
|
||||
|intarsys |Java |proprietär |1 |Ja |Ja |Ja |Ja |Ja |Ja |Nein |a.A.
|
||||
|
||||
|Konik |Java |AGPL |1 |Ja |Ja |Ja |Nein |Nein |Nein |Ja |0 €
|
||||
|
||||
|Mustang |Java |APL |1 |Ja |Ja |Ja |Nein |Ja |Ja |Ja |0 €
|
||||
|
||||
|https://github.com/akretion/factur-x |Python |BSD |1,2 |Nein |Nein |Ja
|
||||
|Nein |Ja |Ja |Ja |0€
|
||||
|
||||
|https://github.com/stephanstapel/ZUGFeRD-csharp |C# |APL |1 |Ja |Ja
|
||||
|Nein |Nein |Ja |Ja |Ja |0 €
|
||||
|===
|
||||
|
||||
== [#anchor-3]####Download/Projekteinrichtung
|
||||
|
||||
=== [#anchor-4]####Source code
|
||||
|
||||
Heimat der Mustangprojekt-Quelltexte ist
|
||||
https://github.com/ZUGFeRD/mustangproject/
|
||||
|
||||
=== [#anchor-5]####Projekteinrichtung ohne Maven
|
||||
|
||||
Mit installiertem OpenOffice.org oder LibreOffice und Eclipse for Java.
|
||||
|
||||
[arabic]
|
||||
. Starten Sie Eclipse, Neues Java-Eclipse-Projekt erstellen,
|
||||
beispielsweise „MustangSample“.
|
||||
. Wechseln Sie in der Shell in den erstellten Ordner.
|
||||
. Download von
|
||||
[arabic]
|
||||
.. Mustang
|
||||
[arabic]
|
||||
... Downloaden Sie
|
||||
http://mustangproject.org/deploy/mustang-1.5.1.jarfootnote:[Falls Sie
|
||||
ohnehin PDFBox verwenden (pdfbox, fontbox, preflight, xmpbox sowie deren
|
||||
Abhängigkeiten apache-commons-io und apache-commons-logging) können Sie
|
||||
auch die wesentlich kleinere
|
||||
http://mustangproject.org/deploy/original-mustang-1.5.1.jar verwenden]
|
||||
... Downloaden Sie http://mustangproject.org/deploy/NOTICE
|
||||
.. Laden Sie
|
||||
[arabic]
|
||||
... Direkt eine PDF-A/1-Musterrechnung noch ohne
|
||||
ZUGFeRD-Mhttp://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-2017]etadaten
|
||||
von
|
||||
http://www.mustangproject.org/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[1118]http://www.mustangproject.org/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[_50]http://www.mustangproject.org/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[6]http://www.mustangproject.org/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[blanko.pdf]
|
||||
herunter.
|
||||
... oder erzeugen sich alternativ
|
||||
[arabic]
|
||||
.... ein eigenes Beispiel-PDF durch Download der Quelldatei im
|
||||
OpenOffice.org-Format
|
||||
http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506.odt[http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-]http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506.odt[20171118_506.odt]
|
||||
und
|
||||
[arabic]
|
||||
..... Öffnen Sie die Datei in OpenOffice.org
|
||||
..... Datei|Exportieren als PDF: Wichtig ist hier, dass Sie die Checkbox
|
||||
PDF/A-1a setzen
|
||||
..... Speichern Sie die PDF-Datei beispielsweise als
|
||||
„http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[MustangGnuaccountingBeispiel]http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506blanko.pdf[RE-20171118_506blanko.pdf]“
|
||||
im MustangSample-Ordner
|
||||
. Wechseln Sie zurück zu Eclipse. Fügen Sie durch Rechtsklick auf das
|
||||
Projekt („Eigenschaften“) die heruntergeladene JAR-Dateien
|
||||
Projekteigenschaften als „externe Jars“ zum „Java Build Path“ Reiter
|
||||
„Bibliotheken“ hinzu.
|
||||
|
||||
=== [#anchor-6]####Mit Maven
|
||||
|
||||
Mit folgendem Repository
|
||||
|
||||
<repositories>
|
||||
|
||||
<repository>
|
||||
|
||||
<id>mustang-mvn-repo</id>
|
||||
|
||||
<url>https://raw.github.com/ZUGFeRD/mustangproject/mvn-repo/</url>
|
||||
|
||||
</repository>
|
||||
|
||||
</repositories>
|
||||
|
||||
und folgender Dependency
|
||||
|
||||
<dependency>
|
||||
|
||||
<groupId>org.mustangproject.ZUGFeRD</groupId>
|
||||
|
||||
<artifactId>mustang</artifactId>
|
||||
|
||||
<version>1.5.1</version>
|
||||
|
||||
</dependency>
|
||||
|
||||
Bei der Gelegenheit kann man auch gleich Apache Commons Logging und JAXB
|
||||
importieren:
|
||||
|
||||
<dependency>
|
||||
|
||||
<groupId>commons-logging</groupId>
|
||||
|
||||
<artifactId>commons-logging</artifactId>
|
||||
|
||||
<version>1.1.1</version>
|
||||
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
|
||||
<version>2.2.5</version>
|
||||
|
||||
</dependency>
|
||||
|
||||
== [#anchor-7]####Lesen von ZUGFeRD-Daten
|
||||
|
||||
[arabic]
|
||||
. Laden Sie sich eine PDF-Datei mit ZUGFeRD-Daten herunter,
|
||||
beispielsweise
|
||||
http://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20171118_506.pdf
|
||||
. Erstellen Sie eine neue Java-Klasse unterhalb von src, beispielsweise
|
||||
MustangReader. inklusive „Public static void main()“
|
||||
. Geben Sie innerhalb von Main „ZUGFeRDImporter zi=**new**
|
||||
ZUGFeRDImporter();“ ein und lassen Sie den Import durch STRG+SHIFT+O
|
||||
ergänzen
|
||||
. verwenden Sie zi.extract(PDF-Dateiname) und ggf. canParse() um
|
||||
festzustellen ob es sich um ZUGFeRD-Daten handelt.
|
||||
. Nach zi.parse() haben Sie Zugriff auf die getter wie getAmount()
|
||||
. Welche Daten enthalten sind, können Sie der XML-Datei entnehmen die im
|
||||
ZUGFeRD-Beispiel-PDF eingebettet ist
|
||||
|
||||
=== [#anchor-8]####Komplettes Lesebeispiel
|
||||
|
||||
[arabic, start=7]
|
||||
.
|
||||
|
||||
*package* sample;
|
||||
|
||||
*import* org.mustangproject.ZUGFeRD.ZUGFeRDImporter;
|
||||
|
||||
*public* *class* Read \{
|
||||
|
||||
*public* *static* *void* main(String[] args) \{
|
||||
|
||||
ZUGFeRDImporter zi=**new** ZUGFeRDImporter();
|
||||
|
||||
zi.extract("./MustangGnuaccountingBeispielRE-20171118_506.pdf");
|
||||
|
||||
System._out_.println("Lese ZUGFeRD");
|
||||
|
||||
*if* (zi.canParse()) \{
|
||||
|
||||
zi.parse();
|
||||
|
||||
System._out_.println("Fälliger Betrag:"+zi.getAmount());
|
||||
|
||||
System._out_.println("BIC:"+zi.getBIC());
|
||||
|
||||
System._out_.println("IBAN:"+zi.getIBAN());
|
||||
|
||||
System._out_.println("Kontoinhaber:"+zi.getHolder());
|
||||
|
||||
System._out_.println("Rechnungsnr:"+zi.getForeignReference());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
== [#anchor-9]####Schreiben einer ZUGFeRD-PDF-Datei
|
||||
|
||||
Ein Beispielprogramm zum Schreiben ist deshalb umfangreicher, weil
|
||||
erstens mehr Daten in einer differenzierteren Struktur geschrieben
|
||||
werden als derzeit beim Lesen benötigt und zweitens dem Exporter die
|
||||
Daten per Interface zur Verfügung gestellt werden müssen.
|
||||
|
||||
[arabic]
|
||||
. Erstellen Sie eine neue Klasse unterhalb von src, beispielsweise
|
||||
MustangWriter inklusive des obligatorischen „Public static void main()“
|
||||
.
|
||||
. Ändern Sie __*public* *class* MustangWriter __in__ *public* *class*
|
||||
MustangWriter *implements* IZUGFeRDExportableTransaction__
|
||||
. Fügen Sie innerhalb der Klasse MustangWriter folgende Klassen hinzu
|
||||
[arabic]
|
||||
.. *class* _Contact_ *implements* IZUGFeRDExportableContact \{}
|
||||
.. *class* _Item_ *implements* IZUGFeRDExportableItem \{
|
||||
[arabic]
|
||||
... _*private* BigDecimal price, quantity;_
|
||||
|
||||
*private* Product product;
|
||||
|
||||
[arabic, start=4]
|
||||
. {blank}
|
||||
[arabic]
|
||||
.. }
|
||||
.. *class* Product *implements* IZUGFeRDExportableProduct \{
|
||||
[arabic]
|
||||
... *private* String description, name, unit;
|
||||
|
||||
*private* BigDecimal VATPercent;
|
||||
|
||||
}
|
||||
|
||||
[arabic, start=5]
|
||||
. Generieren Sie die Imports durch Drücken von STRG+SHIFT+O
|
||||
. Markieren Sie den Klassennamen MustangWriter und drücken Sie
|
||||
ALT+SHIFT+S, wählen Sie Override/Implement Methods und drücken Return.
|
||||
. Klicken Sie auf Contact und Wiederholen Sie den letzten Schritt.
|
||||
. Klicken Sie auf Item, markieren Sie die Variablen und wählen Sie
|
||||
zuerst „_Generate Getters and Setters_“ nach drücken von ALT+SHIFT+S.
|
||||
Wählen Sie alle Member aus und drücken Sie Return.
|
||||
. Klicken Sie erneut auf Item, drücken von ALT+SHIFT+S und wählen
|
||||
„Generate Constructor using Fields“. Wählen Sie erneut alle Member aus
|
||||
und drücken Sie Return.
|
||||
. Auf Item muss der Quick Fix „add unimplemented methods“ ausgeführt
|
||||
werden, die zwei generierten Funktionen (getItemAllowances und
|
||||
getItemCharges) dürfen null zurück geben.
|
||||
. Wenden Sie die beiden letzten Schritte auch auf Product an: Klicken
|
||||
Sie auf Product, markieren Sie die Variablen und wählen Sie „Generate
|
||||
Getters and Setters“ nach drücken von ALT+SHIFT+S. Wählen Sie alle
|
||||
Member aus und drücken Sie Return.
|
||||
. Item benötigt neben den getter/setter auch noch andere Methode, wählen
|
||||
Sie Item aus, drücken Sie ALT+SHIFT+S, wählen Sie Override/Implement
|
||||
Methods
|
||||
. Klicken Sie erneut auf Product, drücken von ALT+SHIFT+S und wählen
|
||||
„Generate Constructor using Fields“. Wählen Sie erneut alle Member aus
|
||||
und drücken Sie Return.
|
||||
. Folgende Methoden von Contact sollten Folgendes zurückgeben:
|
||||
[arabic]
|
||||
.. getCountry(): "DE"
|
||||
.. getLocation(): "Spielkreis"
|
||||
.. getName(): "Theodor Est"
|
||||
.. getStreet(): "Bahnstr. 42"
|
||||
.. getVATID(): "DE999999999"
|
||||
.. getZIP(): "88802";
|
||||
. Folgende Methoden der Hauptklasse -MustangWriter- sollten folgendes
|
||||
zurückgeben:
|
||||
[arabic]
|
||||
.. getDeliveryDate(): *new*
|
||||
GregorianCalendar(2017,Calendar._NOVEMBER_,17).getTime()
|
||||
.. Zweimaliges CTRL+SHIFT+O importiert die dazu nötige GregorianCalendar
|
||||
und Calendar Klasse
|
||||
.. getDueDate(): *new*
|
||||
GregorianCalendar(2017,Calendar._DECEMBER_,9).getTime()
|
||||
.. getIssueDate(): *new*
|
||||
GregorianCalendar(2017,Calendar._NOVEMBER_,18).getTime()
|
||||
.. __getNumber(): "RE-2017__1118__/50__6__"__
|
||||
.. _getOwnBIC(): "COBADEFFXXX"_
|
||||
.. getOwnBankName(): "Commerzbank"
|
||||
.. getOwnCountry() "DE"
|
||||
.. getOwnIBAN(): "DE88 2008 0000 0970 3757 00"
|
||||
.. getOwnLocation() "Stadthausen"
|
||||
.. _getOwnOrganisationName(): "Bei Spiel GmbH"_
|
||||
.. getOwnStreet() "Ecke 12"
|
||||
.. getOwnTaxID(): "22/815/0815/4"
|
||||
.. getOwnVATID(): "DE136695976"
|
||||
.. getOwnZIP(): "12345"
|
||||
.. getOwnOrganisationFullPlaintextInfo(): "Bei Spiel GmbH\n"+
|
||||
+
|
||||
"Ecke 12\n"+
|
||||
+
|
||||
"12345 Stadthausen\n"+
|
||||
+
|
||||
"Geschäftsführer: Max Mustermann"
|
||||
.. _getRecipient(): *new* Contact()_
|
||||
+
|
||||
.. getZFItems() der Hauptklasse kann jetzt Produkte anlegen und diese
|
||||
als Array von Posten (Items) zurückliefern:
|
||||
+
|
||||
Item[] allItems=**new** Item[3];
|
||||
|
||||
Product designProduct=**new** Product("", "Künstlerische Gestaltung
|
||||
(Stunde): Einer Beispielrechnung", "HUR", *new* BigDecimal("7.000000"));
|
||||
|
||||
Product balloonProduct=**new** Product("", "Luftballon: Bunt, ca.
|
||||
500ml", "C62", *new* BigDecimal("19.000000"));
|
||||
|
||||
Product airProduct=**new** Product("", "Heiße Luft pro Liter", "LTR",
|
||||
*new* BigDecimal("19.000000"));
|
||||
|
||||
allItems[0]=**new** Item(*new* BigDecimal("160"), *new* BigDecimal("1"),
|
||||
designProduct);
|
||||
|
||||
allItems[1]=**new** Item(*new* BigDecimal("0.79"), *new*
|
||||
BigDecimal("400"), balloonProduct);
|
||||
|
||||
allItems[2]=**new** Item(*new* BigDecimal("0.10"), *new*
|
||||
BigDecimal("200"), airProduct);
|
||||
|
||||
*return* allItems;
|
||||
|
||||
[arabic]
|
||||
.
|
||||
|
||||
[arabic]
|
||||
. {blank}
|
||||
[arabic]
|
||||
.. Legen Sie eine neue Funktion, beispielsweise „apply“ an (private
|
||||
reicht).
|
||||
.. In der Main-Methode der Hauptklasse instantiiert man jetzt die Klasse
|
||||
und rufen Sie dort apply() auf.
|
||||
.. In der apply-Methode kann man jetzt
|
||||
[arabic]
|
||||
... eine ZUGFeRDExporterFactory instantiieren,
|
||||
... darauf Producer und Creator setzen (bspw. ZUGFeRDExporter ze=new
|
||||
ZUGFeRDExporterFromA1Factory().setProducer(_"string"_).setCreator(_"string"_))
|
||||
und über load die PDF-A/1-Datei angeben und über den Rückgabewert den
|
||||
ZUGFeRDExporter holen. Die ZUGFeRD-Version kann man in der
|
||||
setProducer.setCreator-Kette mit setZUGFeRDVersion(2) angeben.
|
||||
... Auf dem Exporter ruft man diePDFattachZugferdFile-Methode (mit this
|
||||
als IZUGFeRDExportableTransaction) auf und
|
||||
... benutzt schlussendlich die export-Funktion. Die apply-Methode sieht
|
||||
dann – mit entsprechenden try/catch-Blöcken- beispielsweise so aus:
|
||||
|
||||
* try* \{
|
||||
|
||||
System._out_.println("Lese Blanko-PDF");
|
||||
|
||||
ZUGFeRDExporter ze = *new*
|
||||
ZUGFeRDExporterFromA1Factory().setProducer("My
|
||||
Application").setCreator(System._getProperty_("user.name")).load("./MustangGnuaccountingBeispielRE-20171118_506blanko.pdf");
|
||||
|
||||
System._out_.println("Generiere ZUGFeRD-Daten");
|
||||
|
||||
ze.PDFattachZugferdFile(*this*);
|
||||
|
||||
System._out_.println("Schreibe ZUGFeRD-PDF");
|
||||
|
||||
ze.export("./MustangGnuaccountingBeispielRE-20171118_506new.pdf");
|
||||
|
||||
System._out_.println("Fertig.");
|
||||
|
||||
} *catch* (IOException e) \{
|
||||
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
|
||||
[arabic, start=2]
|
||||
. {blank}
|
||||
[arabic]
|
||||
.. Ein CTRL+SHIFT+O hilft wieder beim Hinzufügen der nötigen Imports
|
||||
.. „My Application“ und __System.getProperty("user.name") __werden in
|
||||
den Metadaten als „Producer“ (in etwa: erstellende Anwendung)
|
||||
beziehungsweise „Creator“ (in etwa: Autor) gespeichert. Bitte passen Sie
|
||||
die Werte entsprechend Ihrer Anwendung an.
|
||||
.. Starten Sie, es sollte eine valide ZUFeRD-Rechnung in
|
||||
_./MustangGnuaccountingBeispielRE-20171118_506new.pdf_
|
||||
+
|
||||
erstellt werden.
|
||||
.. Passen Sie ggf. die NOTICE-Datei an und fügen Sie Ihrer Anwendung
|
||||
hinzu.
|
||||
.. Stellen Sie sicher, dass die XML-Daten in der dem PDF eingebetteten
|
||||
ZUGFeRD-invoice.xml immer den menschenlesbaren Daten im PDF entsprechen.
|
||||
|
||||
Die erzeugte Datei beinhaltet ZUGFeRD-invoice.xml statt factur-x.xml im
|
||||
offiziellen Beispiel so lange die ZUGFeRD-Version in der Factory nicht
|
||||
auf 2 gesetzt wurde.
|
||||
|
||||
[arabic]
|
||||
.
|
||||
|
||||
=== [#anchor-10]####Komplettes Schreibbeispiel
|
||||
|
||||
Siehe MustangWriter.java in diesem Verzeichnis.
|
||||
|
||||
=== [#anchor-11]####Schreiben eigener XML-Daten
|
||||
|
||||
Sollten Sie eine eigene Implementierung verwenden um ZUGFeRD-XML-Daten
|
||||
zu erzeugen können Sie diese mit setZUGFeRDXMLData schreiben:
|
||||
|
||||
ZUGFeRDExporter ze;
|
||||
|
||||
*try* \{
|
||||
|
||||
System.*_out_*.println("Konvertiere zu PDF/A-3u");
|
||||
|
||||
ze = new
|
||||
|
||||
ZUGFeRDExporterFromA1Factory().setProducer("My
|
||||
|
||||
Application").setCreator(System._getProperty_("user.name")).load("./MustangGnuaccountingBeispielRE-20171118_506blanko.pdf");
|
||||
|
||||
System.*_out_*.println("Hänge ZUGFeRD-Datei an");
|
||||
|
||||
String ownZUGFeRDXML =
|
||||
"<rsm:CrossIndustryDocument></rsm:CrossIndustryDocument>";
|
||||
|
||||
ze.setZUGFeRDXMLData(ownZUGFeRDXML.getBytes());
|
||||
|
||||
System.*_out_*.println("Schreibe ZUGFeRD-PDF");
|
||||
|
||||
ze.export("./Target.pdf");
|
||||
|
||||
System.*_out_*.println("Fertig.");
|
||||
|
||||
} *catch* (IOException e) \{
|
||||
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
|
||||
Mustangproject prüft, die Eingabedatei auf einigermaßen korrektes PDF/A
|
||||
ist und dass die XML-Daten mindestens <rsm:CrossIndustry enthalten was
|
||||
auf ZF1- (CrossIndustryDocument) und ZF2-Daten (CrossIndustryInvoice)
|
||||
zutrifft.
|
||||
|
||||
=== [#anchor-12]####Zusatzfunktionen
|
||||
|
||||
* ZUGFeRDExporter.setTest() setzt ein Attribut im ZUGFeRD-XML, das
|
||||
benutzt wird um eine Testrechnung auszuzeichnen.
|
||||
* ZUGFeRDExporter.ignoreA1Errors() überspringt die Überprüfung der
|
||||
Eingangsdatei auf PDF/A-1-Fehler
|
||||
* Ein erster Versuch bestehendes ZUGFeRD1-XML in 2 zu konvertieren kann
|
||||
mit String facturx=new
|
||||
ZUGFeRDMigrator().migrateFromV1ToV2(zugferdInvoice); begonnen werden.
|
||||
182
doc/ZugferdDev.en.adoc
Normal file
182
doc/ZugferdDev.en.adoc
Normal file
@@ -0,0 +1,182 @@
|
||||
== Mustang project user documentation
|
||||
|
||||
Jochen Stärk
|
||||
|
||||
For Mustangproject 2.17.0, 2025-05-28
|
||||
|
||||
http://www.mustangproject.org/[http://www.mustangproject.org]
|
||||
|
||||
== About Mustangproject
|
||||
|
||||
Mustangproject is a Java-Library for invoice („Factur-X/ZUGFeRD“-)metadata in
|
||||
PDF-invoices as well as XRechnung (CII, or on the reading side also UBL).
|
||||
|
||||
It can read, write and validate those files uses PDF/A files as
|
||||
input, underlies the Apache Public License and can
|
||||
be used for free in commercial and noncommercial projects as long as
|
||||
the license conditions are met.
|
||||
|
||||
On link:https://zugferd.org/[zugferd.org] we list other libraries also for other
|
||||
languages
|
||||
|
||||
== Download/Project setup
|
||||
|
||||
=== Source code
|
||||
|
||||
Home of the Mustangproject source code is
|
||||
https://github.com/ZUGFeRD/mustangproject/
|
||||
|
||||
=== With Maven
|
||||
|
||||
E.g. using link:https://www.jetbrains.com/idea/download/?section=windows[IntelliJ].
|
||||
|
||||
Add the following dependency to your project
|
||||
|
||||
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>validator</artifactId>
|
||||
<classifier>shaded</classifier>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
Open the Maven Window and click Sync all Maven projects.
|
||||
|
||||
== Reading Factur-X/ZUGFeRD data
|
||||
|
||||
[arabic]
|
||||
. Download a factur-x sample with metadata like
|
||||
https://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20201121_508.pdf[https://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20201121_508.pdf]
|
||||
. Create a new Maven project. Add the jar in the pom.xml as described above. In
|
||||
----
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
use
|
||||
|
||||
----
|
||||
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("MustangGnuaccountingBeispielRE-20201121_508.pdf");
|
||||
CalculatedInvoice ci=new CalculatedInvoice();
|
||||
try {
|
||||
zii.extractInto(ci);
|
||||
System.out.println("Pay: "+ci.getDuePayable());
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
----
|
||||
|
||||
To find out how much you have to pay.
|
||||
|
||||
=== Complete sample source code for reading ZUGFeRD data
|
||||
|
||||
|
||||
----
|
||||
package de.usegroup;
|
||||
|
||||
import org.mustangproject.CalculatedInvoice;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter;
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("MustangGnuaccountingBeispielRE-20201121_508.pdf");
|
||||
CalculatedInvoice ci=new CalculatedInvoice();
|
||||
try {
|
||||
zii.extractInto(ci);
|
||||
System.out.println("Pay: "+ci.getDuePayable());
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: You can additionally validate the file with two additional lines
|
||||
----
|
||||
ZUGFeRDValidator zv=new ZUGFeRDValidator();
|
||||
System.out.println(zv.validate("MustangGnuaccountingBeispielRE-20201121_508.pdf"));
|
||||
----
|
||||
|
||||
|
||||
== Writing a ZUGFeRD-PDF file
|
||||
|
||||
A sample for writing Factur-X/ZUGFeRD PDFs requires a PDF/A file to attach the XML to,
|
||||
saved
|
||||
e.g. as link:https://www.mustangproject.org/files/MustangGnuaccountingBeispielRE-20190610_507blanko.pdf[blanko.pdf]
|
||||
|
||||
The core is creating an exporter, an invoice and send the invoice through the exporter:
|
||||
----
|
||||
IZUGFeRDExporter ze = new ZUGFeRDExporterFromPDFA().load(sourcePDF).setProducer("My Application").
|
||||
setCreator(System.getProperty("user.name"));
|
||||
|
||||
Invoice i=new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty("ACME co", "teststr", "55232", "teststadt", "DE")
|
||||
.addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815")
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")
|
||||
.setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber("X12")
|
||||
.addItem(new Item(new Product("Testproduct", "", "H87", new BigDecimal(19)),
|
||||
new BigDecimal(2.5), new BigDecimal(1.0)));
|
||||
ze.setTransaction(i);
|
||||
ze.export("factur-x.pdf");
|
||||
----
|
||||
Additionally, you should confirm that Mustangs calculated total matches that of your PDF/A file, for which you can use
|
||||
the TransactionCalculator (which is also used internally in the exporter):
|
||||
|
||||
----
|
||||
TransactionCalculator tc=new TransactionCalculator(i);
|
||||
System.out.println("Confirm "+tc.getDuePayable()+ " is also the final amount in your PDF");
|
||||
----
|
||||
|
||||
NOTE: We're using BigDecimals to avoid rounding errors.
|
||||
|
||||
So the complete write example looks like this:
|
||||
----
|
||||
package de.usegroup;
|
||||
|
||||
import org.mustangproject.*;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExporter;
|
||||
import org.mustangproject.ZUGFeRD.TransactionCalculator;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDExporterFromPDFA;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
|
||||
String sourcePDF="MustangGnuaccountingBeispielRE-20201121_508.pdf";
|
||||
try {
|
||||
IZUGFeRDExporter ze = new ZUGFeRDExporterFromPDFA().load(sourcePDF).setProducer("My Application").
|
||||
setCreator(System.getProperty("user.name"));
|
||||
|
||||
Invoice i=new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty("ACME co", "teststr", "55232", "teststadt", "DE")
|
||||
.addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815")
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")
|
||||
.setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber("X12")
|
||||
.addItem(new Item(new Product("Testproduct", "", "H87", new BigDecimal(19)),
|
||||
new BigDecimal(2.5), new BigDecimal(1.0)));
|
||||
ze.setTransaction(i);
|
||||
ze.export("factur-x.pdf");
|
||||
TransactionCalculator tc=new TransactionCalculator(i);
|
||||
System.out.println("Confirm "+tc.getDuePayable()+ " is also the final amount in your PDF");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
Binary file not shown.
@@ -59,7 +59,7 @@ to validate the XML part of the invoices.
|
||||
|
||||
## New build
|
||||
|
||||
Target platform is java 1.11
|
||||
Target platform is java 1.17
|
||||
|
||||
## Build
|
||||
|
||||
@@ -184,11 +184,11 @@ maybe not yet even existing new release version:
|
||||
|
||||
```
|
||||
cd validator/target
|
||||
mvn install:install-file -Dfile=validator-2.12.0-SNAPSHOT-shaded.jar -Dclassifier=shaded -DgroupId=org.mustangproject -DartifactId=validator -Dversion=2.12.0 -Dpackaging=jar -DgeneratePom=true
|
||||
mvn install:install-file -Dfile=validator-2.17.0-SNAPSHOT-shaded.jar -Dclassifier=shaded -DgroupId="org.mustangproject" -DartifactId=validator -Dversion="2.17.0" -Dpackaging=jar -DgeneratePom=true
|
||||
```
|
||||
In gradle you can use something like
|
||||
```
|
||||
implementation files('libs/validator-2.12.0-shaded.jar')
|
||||
implementation files('libs/validator-2.17.0-shaded.jar')
|
||||
```
|
||||
|
||||
|
||||
@@ -229,3 +229,47 @@ extract, rename xsl file to xslt, move to new version dir and add a if where the
|
||||
* freshcode.club
|
||||
* Submit on openpr.de/.com, https://www.einpresswire.com/
|
||||
|
||||
## Tips&Inspriration
|
||||
|
||||
|
||||
NodeList from a XPath https://github.com/ZUGFeRD/mustangproject/pull/476/files
|
||||
```
|
||||
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
|
||||
NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
nodeMap.getNode("GlobalID").ifPresent(idNode -> {
|
||||
if (idNode.hasAttributes()
|
||||
&& idNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
globalId = new SchemedID()
|
||||
.setScheme(idNode.getAttributes().getNamedItem("schemeID").getNodeValue())
|
||||
.setId(idNode.getTextContent());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
nodeMap.getAsNodeMap and nodeMap.getAsString
|
||||
```
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
if (key != null && value != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
|
||||
```
|
||||
|
||||
128
library/pom.xml
128
library/pom.xml
@@ -3,13 +3,13 @@
|
||||
<parent>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.14.0-SNAPSHOT</version>
|
||||
<version>2.17.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>library</artifactId>
|
||||
<version>2.14.0-SNAPSHOT</version>
|
||||
<version>2.17.0-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
|
||||
@@ -59,12 +59,19 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.offo</groupId>
|
||||
<artifactId>fop-hyph</artifactId>
|
||||
<version>2.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE -->
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>12.4</version>
|
||||
</dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
@@ -74,7 +81,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>fop</artifactId>
|
||||
<version>2.9</version>
|
||||
<version>2.10</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>xml-apis</groupId>
|
||||
@@ -82,33 +89,54 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- JAXB -->
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>4.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.angus</groupId>
|
||||
<artifactId>angus-activation</artifactId>
|
||||
<version>2.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache PDFBox -->
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>preflight</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.5</version>
|
||||
</dependency>
|
||||
<!-- DOM4j -->
|
||||
<dependency>
|
||||
<groupId>org.dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>2.1.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- CII to UBL conversion -->
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
<artifactId>en16931-cii2ubl</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
@@ -121,27 +149,12 @@
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- CII to UBL conversion -->
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
<artifactId>en16931-cii2ubl</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>4.0.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-assertj</artifactId>
|
||||
@@ -193,21 +206,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>
|
||||
@@ -232,11 +275,9 @@
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<minimizeJar>false
|
||||
</minimizeJar><!-- no longer java 11 compatible if set to true because it removes e.g. javax/xml/bind/annotation/XmlSchema-->
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
@@ -245,18 +286,6 @@
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>log4j:log4j</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>commons-logging:commons-logging</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
@@ -265,15 +294,6 @@
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<!--exclude>classworlds:classworlds</exclude> <exclude>junit:junit</exclude>
|
||||
<exclude>jmock:*</exclude> <exclude>*:xml-apis</exclude> <exclude>org.apache.maven:lib:tests</exclude>
|
||||
<exclude>log4j:log4j:jar:</exclude -->
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/***
|
||||
* (absolute) allowances on item and document level
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Allowance extends Charge {
|
||||
|
||||
/***
|
||||
@@ -23,11 +31,28 @@ public class Allowance extends Charge {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) {
|
||||
if(totalAmount != null) {
|
||||
return totalAmount;
|
||||
} else if (percent!=null) {
|
||||
BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
|
||||
// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
|
||||
BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice);
|
||||
return singlePriceDiff;
|
||||
} else {
|
||||
throw new RuntimeException("percent must be set");
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Always to return false for IZUGFeRDAllowanceCharge
|
||||
* @return false since its not supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
|
||||
|
||||
/**
|
||||
* provides e.g. the IBAN to transfer money to :-)
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
/**
|
||||
* the bank account number
|
||||
@@ -15,15 +18,17 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
/**
|
||||
* BIC, I believe it's optional
|
||||
*/
|
||||
protected String BIC=null;
|
||||
protected String BIC = null;
|
||||
/**
|
||||
* the "name" of the bank account (holder)
|
||||
*/
|
||||
protected String accountName=null;
|
||||
protected String accountName = null;
|
||||
|
||||
/***
|
||||
* bean constructor
|
||||
*/
|
||||
public BankDetails() { }
|
||||
public BankDetails() {
|
||||
}
|
||||
|
||||
/***
|
||||
* constructor for IBAN only :-)
|
||||
@@ -32,6 +37,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
public BankDetails(String IBAN) {
|
||||
this.IBAN = IBAN;
|
||||
}
|
||||
|
||||
/***
|
||||
* constructor for normal use :-)
|
||||
* @param IBAN the IBAN as string
|
||||
@@ -55,6 +61,7 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
* identify the IBAN. Of course you will specify your own IBAN in full length but
|
||||
* if you deduct from a customer's account you may e.g. leave out the first or last
|
||||
* digits so that nobody spying on the invoice gets to know the complete number
|
||||
*
|
||||
* @param IBAN the "IBAN ID", i.e. the IBAN or parts of it
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -81,9 +88,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* getOwn... methods will be removed in the future in favor of Tradeparty (e.g. Sender) class
|
||||
* */
|
||||
|
||||
/*
|
||||
I'd really like to get rid of all those getOwn... methods some time but in this case they are in the interface :-(
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
@@ -101,11 +109,12 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
|
||||
/**
|
||||
* set Holder
|
||||
*
|
||||
* @param name account name (usually account holder if != sender)
|
||||
* @return fluent setter
|
||||
*/
|
||||
public BankDetails setAccountName(String name) {
|
||||
accountName=name;
|
||||
accountName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -115,5 +124,4 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
109
library/src/main/java/org/mustangproject/CalculatedInvoice.java
Normal file
109
library/src/main/java/org/mustangproject/CalculatedInvoice.java
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2023 Jochen Stärk, see LICENSE file
|
||||
package org.mustangproject;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.TransactionCalculator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class CalculatedInvoice extends Invoice implements Serializable {
|
||||
|
||||
protected BigDecimal lineTotalAmount=null;
|
||||
protected BigDecimal duePayable=null;
|
||||
protected BigDecimal grandTotal=null;
|
||||
protected BigDecimal taxBasis=null;
|
||||
protected TransactionCalculator tc=null;
|
||||
|
||||
public void calculate() {
|
||||
tc=new TransactionCalculator(this);
|
||||
grandTotal=tc.getGrandTotal();
|
||||
lineTotalAmount=tc.getValue();
|
||||
duePayable=tc.getDuePayable();
|
||||
taxBasis= tc.getTaxBasis();
|
||||
}
|
||||
public BigDecimal getGrandTotal() {
|
||||
if (grandTotal==null) {
|
||||
calculate();
|
||||
}
|
||||
return grandTotal;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* usually one would use calculate, use only if the invoice is parsed
|
||||
* @param grand the gross total
|
||||
* @return fluent setter
|
||||
*/
|
||||
public CalculatedInvoice setGrandTotal(BigDecimal grand) {
|
||||
grandTotal=grand;
|
||||
return this;
|
||||
}
|
||||
public BigDecimal getTaxBasis() {
|
||||
if (taxBasis==null) {
|
||||
calculate();
|
||||
}
|
||||
return taxBasis;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* usually one would use calculate, use only if the invoice is parsed
|
||||
* @param basis taxable net total
|
||||
* @return fluent setter
|
||||
*/
|
||||
public CalculatedInvoice setTaxBasis(BigDecimal basis) {
|
||||
taxBasis=basis;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getDuePayable() {
|
||||
if (duePayable==null) {
|
||||
calculate();
|
||||
}
|
||||
return duePayable;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* usually one would use calculate, use only if the invoice is parsed
|
||||
* @param due the gross total minus prepaid
|
||||
* @return fluent setter
|
||||
*/
|
||||
public CalculatedInvoice setDuePayable(BigDecimal due) {
|
||||
duePayable=due;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getLineTotalAmount() {
|
||||
if (lineTotalAmount==null) {
|
||||
calculate();
|
||||
}
|
||||
return lineTotalAmount;
|
||||
}
|
||||
|
||||
/***
|
||||
* usually one would use calculate, use only if the invoice is parsed
|
||||
* @param total the net total
|
||||
* @return fluent setter
|
||||
*/
|
||||
public CalculatedInvoice setLineTotalAmount(BigDecimal total) {
|
||||
lineTotalAmount=total;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* this can be used to additionally e.g. access the VAT amounts
|
||||
* @return a updated transactioncalulator
|
||||
*/
|
||||
public TransactionCalculator getCalculation() {
|
||||
return tc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDCashDiscount;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -7,6 +10,8 @@ import java.math.BigDecimal;
|
||||
/***
|
||||
* A class to represent discounts for early payments ("Skonto")
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class CashDiscount implements IZUGFeRDCashDiscount {
|
||||
|
||||
/***
|
||||
@@ -31,9 +36,35 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
/***
|
||||
* bean contructor
|
||||
*/
|
||||
public CashDiscount() {
|
||||
|
||||
}
|
||||
|
||||
public BigDecimal getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public CashDiscount setPercent(BigDecimal percent) {
|
||||
this.percent = percent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getDays() {
|
||||
return days;
|
||||
}
|
||||
|
||||
public CashDiscount setDays(Integer days) {
|
||||
this.days = days;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* @return this particular cash discount as cross industry invoice XML
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getAsCII() {
|
||||
return "<ram:SpecifiedTradePaymentTerms>"+
|
||||
"<ram:Description>Cash Discount</ram:Description>"+
|
||||
@@ -49,6 +80,7 @@ public class CashDiscount implements IZUGFeRDCashDiscount {
|
||||
* XRechnung CIUS defined it's own proprietary format for a freetext field
|
||||
* @return this particular cash discount in proprietary xrechnung format
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getAsXRechnung() {
|
||||
return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n";
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/***
|
||||
* Absolute and relative charges for document and item level
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
|
||||
/**
|
||||
@@ -18,6 +24,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
* the absolute value if not percentage
|
||||
*/
|
||||
protected BigDecimal totalAmount;
|
||||
/**
|
||||
* the value the percentage is applied upon
|
||||
*/
|
||||
protected BigDecimal basisAmount;
|
||||
/**
|
||||
* the tax rate the charge belongs to
|
||||
*/
|
||||
@@ -98,6 +108,22 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BigDecimal getBasisAmount() {
|
||||
return basisAmount;
|
||||
}
|
||||
|
||||
/***
|
||||
* sets a potential basis for the potential percentage
|
||||
* @param basis the basis amount
|
||||
* @return fluid setter
|
||||
*/
|
||||
public Charge setBasisAmount(BigDecimal basis) {
|
||||
this.basisAmount = basis;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getReasonCode() {
|
||||
return reasonCode;
|
||||
@@ -116,10 +142,13 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
|
||||
@Override
|
||||
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) {
|
||||
if (percent!=null) {
|
||||
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
|
||||
} else if(totalAmount != null) {
|
||||
if(totalAmount != null) {
|
||||
return totalAmount;
|
||||
} else if (percent!=null) {
|
||||
BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP);
|
||||
// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100))));
|
||||
BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice);
|
||||
return singlePriceDiff;
|
||||
} else {
|
||||
throw new RuntimeException("percent must be set");
|
||||
}
|
||||
@@ -129,7 +158,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
if (totalAmount!=null) {
|
||||
return totalAmount;
|
||||
} else {
|
||||
throw new RuntimeException("totalAmount must be set");
|
||||
if (percent==null) {
|
||||
throw new RuntimeException("totalAmount must be set");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +183,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
* @return true since it is supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return true;
|
||||
}
|
||||
|
||||
115
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
115
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* A schemed classification for products. The scheme can be anything defined in UNTDID 7143.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ClassCode {
|
||||
private String listID;
|
||||
private String code;
|
||||
private String listVersionID;
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
* @param listVersionID an (optional) version of the scheme
|
||||
*/
|
||||
public ClassCode(String listID, String code, String listVersionID) {
|
||||
this.listID = listID;
|
||||
this.code = code;
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
*/
|
||||
public ClassCode(String listID, String code) {
|
||||
this(listID, code, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* bean constructor
|
||||
*/
|
||||
public ClassCode() {
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Set the version for the scheme returned by {@link #getListID()}
|
||||
* @param listVersionID the scheme version
|
||||
*/
|
||||
public void setListVersionID(String listVersionID) {
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheme (according to UNTDID 7143) that describes the value returned by {@link #getCode()},
|
||||
* potentially versioned by {@link #getListVersionID()}
|
||||
*
|
||||
* @return the scheme
|
||||
*/
|
||||
public String getListID() {
|
||||
return listID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code that (following the scheme returned by {@link #getListID()}) describes the product
|
||||
*
|
||||
* @return the classification code itself
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (optional) version for the scheme returned by {@link #getListID()}
|
||||
*
|
||||
* @return the version or {@code null} if not set
|
||||
*/
|
||||
public String getListVersionID() {
|
||||
return listVersionID;
|
||||
}
|
||||
|
||||
public static ClassCode fromNode(Node node) {
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
if (attrs != null && attrs.getNamedItem("listID") != null) {
|
||||
ClassCode classCode = new ClassCode(attrs.getNamedItem("listID").getNodeValue(), node.getTextContent());
|
||||
if (attrs.getNamedItem("listVersionID") != null) {
|
||||
classCode.setListVersionID(attrs.getNamedItem("listVersionID").getNodeValue());
|
||||
}
|
||||
return classCode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -11,6 +12,7 @@ import org.w3c.dom.NodeList;
|
||||
* @see TradeParty
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Contact implements IZUGFeRDExportableContact {
|
||||
|
||||
/**
|
||||
@@ -112,7 +114,9 @@ public class Contact implements IZUGFeRDExportableContact {
|
||||
if (currentItemNode.getLocalName() != null) {
|
||||
|
||||
if (currentItemNode.getLocalName().equals("PersonName")/*CII*/||currentItemNode.getLocalName().equals("Name")/*UBL*/) {
|
||||
setName(currentItemNode.getFirstChild().getNodeValue());
|
||||
if (currentItemNode.getFirstChild()!=null) {
|
||||
setName(currentItemNode.getFirstChild().getNodeValue());
|
||||
}
|
||||
}
|
||||
if (currentItemNode.getLocalName().equals("TelephoneUniversalCommunication")) { /*CII*/
|
||||
NodeList tel = currentItemNode.getChildNodes();
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
|
||||
/**
|
||||
* An implementation of {@link IDesignatedProductClassification} for describing a {@link org.mustangproject.Product}
|
||||
*
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DesignatedProductClassification implements IDesignatedProductClassification {
|
||||
private ClassCode classCode;
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
* @param className a verbal description of the class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode, String className) {
|
||||
this.classCode = classCode;
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode) {
|
||||
this(classCode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean constructor for schemed product descriptor
|
||||
*/
|
||||
public DesignatedProductClassification() {
|
||||
classCode = null;// we need this constructor for jackson, i.e. to be able to JSON
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassCode getClassCode() {
|
||||
return classCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the human-readable name of the class code
|
||||
*
|
||||
* @param className the name of the class code (can be {@code null})
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,42 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit;
|
||||
|
||||
/**
|
||||
* provides e.g. the IBAN to transfer money to :-)
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
|
||||
/**
|
||||
* Debited account identifier (BT-91)
|
||||
*/
|
||||
protected final String IBAN;
|
||||
protected String IBAN;
|
||||
|
||||
/**
|
||||
* Mandate reference identifier (BT-89)
|
||||
*/
|
||||
protected final String mandate;
|
||||
protected String mandate;
|
||||
|
||||
/**
|
||||
* payment means code (BT-81 / UNTDID 4461)
|
||||
*/
|
||||
protected String paymentMeansCode = "59";
|
||||
|
||||
/**
|
||||
* Payment means description (BT-82)
|
||||
*/
|
||||
protected String paymentMeansInformation = "SEPA direct debit";
|
||||
|
||||
/**
|
||||
* bean constructor
|
||||
*/
|
||||
public DirectDebit() {
|
||||
this.IBAN = "";
|
||||
this.mandate = "";
|
||||
}
|
||||
|
||||
/***
|
||||
* constructor for normal use :-)
|
||||
@@ -26,6 +48,16 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
|
||||
this.mandate = mandate;
|
||||
}
|
||||
|
||||
public DirectDebit setPaymentMeansCode(String paymentMeansCode) {
|
||||
this.paymentMeansCode = paymentMeansCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DirectDebit setPaymentMeansInformation(String paymentMeansInformation) {
|
||||
this.paymentMeansInformation = paymentMeansInformation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* getter for the IBAN
|
||||
* @return IBAN
|
||||
@@ -35,8 +67,28 @@ public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
|
||||
return this.IBAN;
|
||||
}
|
||||
|
||||
public DirectDebit setIBAN(String iBAN) {
|
||||
this.IBAN = iBAN;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMandate() {
|
||||
return this.mandate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansCode() {
|
||||
return this.paymentMeansCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansInformation() {
|
||||
return this.paymentMeansInformation;
|
||||
}
|
||||
|
||||
public DirectDebit setMandate(String mandate) {
|
||||
this.mandate = mandate;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.mustangproject;
|
||||
|
||||
public enum EStandard {
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl, ubl_creditnote
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.mustangproject.Exceptions;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
/***
|
||||
* will be thrown if an invoice cant be reproduced numerically
|
||||
*/
|
||||
public class ArithmetricException extends ParseException {
|
||||
public ArithmetricException() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public ArithmetricException(String details) {
|
||||
super("Could not reproduce the invoice. " + details, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.mustangproject.Exceptions;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
/***
|
||||
* will be thrown if a invoice cant be read
|
||||
*/
|
||||
public class StructureException extends ParseException {
|
||||
public StructureException(String message, int line) {
|
||||
super(message, line);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,39 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class FileAttachment {
|
||||
|
||||
protected String filename;
|
||||
protected String mimetype;
|
||||
protected String relation;
|
||||
protected String relation = "Unspecified";
|
||||
protected String description;
|
||||
protected byte[] data;
|
||||
|
||||
|
||||
/***
|
||||
* bean contructor
|
||||
*/
|
||||
public FileAttachment() {
|
||||
|
||||
}
|
||||
|
||||
public FileAttachment(String filename, String mimetype, String relation, byte[] data) {
|
||||
this.filename = filename;
|
||||
this.mimetype = mimetype;
|
||||
this.relation = relation;
|
||||
this.data = data;
|
||||
description = "Additional file attachment";
|
||||
this.description = "Additional file attachment";
|
||||
}
|
||||
|
||||
public FileAttachment(String filename, String mimetype, byte[] data) {
|
||||
this.filename = filename;
|
||||
this.mimetype = mimetype;
|
||||
this.data = data;
|
||||
this.description = "Additional file attachment";
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
@@ -47,6 +67,28 @@ public class FileAttachment {
|
||||
return relation;
|
||||
}
|
||||
|
||||
/***
|
||||
* only needed when embedded in PDF described
|
||||
*
|
||||
* values
|
||||
* - Source shall be used if this file specification is the original
|
||||
* source material for the associated content.
|
||||
* - Data shall be used if this file specification represents information
|
||||
* used to derive a visual presentation, such as for a table or a
|
||||
* graph.
|
||||
* - Alternative shall be used if this file specification is an alternative
|
||||
* representation of content, for example audio.
|
||||
* - Supplement shall be used if this file specification represents a
|
||||
* supplemental representation of the original source or data that
|
||||
* may be more easily consumable (e.g. A MathML version of an
|
||||
* equation).
|
||||
* - Unspecified shall be used when the relationship is not known
|
||||
* or cannot be described using one of the other values.
|
||||
* @param relation String: either : Source, Data or Alternative. Usually Data, except source if the file attachment
|
||||
* is the basis for the pdf (xrechnung2fx) or Alternative if it contains the same content (e.g. the
|
||||
* factur-x.xml file in a factur-x PDF)
|
||||
* @return fluent setter
|
||||
*/
|
||||
public FileAttachment setRelation(String relation) {
|
||||
this.relation = relation;
|
||||
return this;
|
||||
@@ -60,8 +102,4 @@ public class FileAttachment {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,68 +1,100 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class IncludedNote {
|
||||
private final String content;
|
||||
private final SubjectCode subjectCode;
|
||||
private String content;
|
||||
|
||||
private static final String INCLUDE_START = "<ram:IncludedNote>";
|
||||
private static final String INCLUDE_END = "</ram:IncludedNote>";
|
||||
private static final String CONTENT_START = "<ram:Content>";
|
||||
private static final String CONTENT_END = "</ram:Content>";
|
||||
private static final String SUBJECT_CODE_START = "<ram:SubjectCode>";
|
||||
private static final String SUBJECT_CODE_END = "</ram:SubjectCode>";
|
||||
private SubjectCode subjectCode;
|
||||
|
||||
private IncludedNote(String content, SubjectCode subjectCode) {
|
||||
this.content = content;
|
||||
this.subjectCode = subjectCode;
|
||||
}
|
||||
private static final String INCLUDE_START = "<ram:IncludedNote>";
|
||||
private static final String INCLUDE_END = "</ram:IncludedNote>";
|
||||
private static final String CONTENT_START = "<ram:Content>";
|
||||
private static final String CONTENT_END = "</ram:Content>";
|
||||
private static final String SUBJECT_CODE_START = "<ram:SubjectCode>";
|
||||
private static final String SUBJECT_CODE_END = "</ram:SubjectCode>";
|
||||
|
||||
public static IncludedNote generalNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.AAI);
|
||||
}
|
||||
public static IncludedNote regulatoryNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.REG);
|
||||
}
|
||||
public static IncludedNote legalNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.ABL);
|
||||
}
|
||||
public static IncludedNote customsNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.CUS);
|
||||
}
|
||||
public static IncludedNote sellerNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.SUR);
|
||||
}
|
||||
public static IncludedNote taxNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.TXD);
|
||||
}
|
||||
public static IncludedNote introductionNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.ACY);
|
||||
}
|
||||
public static IncludedNote discountBonusNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.AAK);
|
||||
}
|
||||
public IncludedNote(String content, SubjectCode subjectCode) {
|
||||
this.content = content;
|
||||
this.subjectCode = subjectCode;
|
||||
}
|
||||
|
||||
public static IncludedNote unspecifiedNote(String content) {
|
||||
return new IncludedNote(content, null);
|
||||
}
|
||||
/**
|
||||
* bean constructor
|
||||
*/
|
||||
public IncludedNote() {
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
public static IncludedNote generalNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.AAI);
|
||||
}
|
||||
|
||||
public static IncludedNote regulatoryNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.REG);
|
||||
}
|
||||
|
||||
public static IncludedNote legalNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.ABL);
|
||||
}
|
||||
|
||||
public static IncludedNote customsNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.CUS);
|
||||
}
|
||||
|
||||
public static IncludedNote sellerNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.SUR);
|
||||
}
|
||||
|
||||
public static IncludedNote taxNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.TXD);
|
||||
}
|
||||
|
||||
public static IncludedNote introductionNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.ACY);
|
||||
}
|
||||
|
||||
public static IncludedNote discountBonusNote(String content) {
|
||||
return new IncludedNote(content, SubjectCode.AAK);
|
||||
}
|
||||
|
||||
public static IncludedNote unspecifiedNote(String content) {
|
||||
return new IncludedNote(content, null);
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public SubjectCode getSubjectCode() {
|
||||
return subjectCode;
|
||||
}
|
||||
|
||||
|
||||
public IncludedNote setSubjectCode(SubjectCode subjectCode) {
|
||||
this.subjectCode = subjectCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IncludedNote setContent(String content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public String toCiiXml() {
|
||||
String result = INCLUDE_START + CONTENT_START +
|
||||
XMLTools.encodeXML(getContent()) + CONTENT_END;
|
||||
if (getSubjectCode() != null) {
|
||||
result += SUBJECT_CODE_START + getSubjectCode() + SUBJECT_CODE_END;
|
||||
}
|
||||
return result + INCLUDE_END;
|
||||
}
|
||||
|
||||
public SubjectCode getSubjectCode() {
|
||||
return subjectCode;
|
||||
}
|
||||
|
||||
public String toCiiXml(){
|
||||
String result = INCLUDE_START + CONTENT_START +
|
||||
XMLTools.encodeXML(getContent() )+ CONTENT_END;
|
||||
if (getSubjectCode() != null) {
|
||||
result += SUBJECT_CODE_START + getSubjectCode() + SUBJECT_CODE_END;
|
||||
}
|
||||
return result + INCLUDE_END;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.*;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
|
||||
@@ -37,11 +35,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* @see IExportableTransaction if you want to implement an interface instead
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Invoice implements IExportableTransaction {
|
||||
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected Date issueDate = null, dueDate = null, deliveryDate = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null;
|
||||
protected ArrayList<CashDiscount> cashDiscounts = null;
|
||||
@JsonDeserialize(contentAs = Item.class)
|
||||
protected ArrayList<IZUGFeRDExportableItem> ZFItems = null;
|
||||
@@ -57,13 +56,20 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
|
||||
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
|
||||
protected IZUGFeRDPaymentTerms paymentTerms = null;
|
||||
protected ArrayList<IZUGFeRDPaymentTerms> paymentTerms = new ArrayList<>();
|
||||
|
||||
protected String invoiceReferencedDocumentID = null;
|
||||
protected Date invoiceReferencedIssueDate;
|
||||
// New field for storing Invoiced Object Identifier (BG-3)
|
||||
protected ArrayList<ReferencedDocument> invoiceReferencedDocuments = null;
|
||||
|
||||
protected String specifiedProcuringProjectID = null;
|
||||
protected String specifiedProcuringProjectName = null;
|
||||
protected String despatchAdviceReferencedDocumentID = null;
|
||||
protected String vatDueDateTypeCode = null;
|
||||
protected String creditorReferenceID; // required when direct debit is used.
|
||||
private BigDecimal roundingAmount=null;
|
||||
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
|
||||
|
||||
public Invoice() {
|
||||
ZFItems = new ArrayList<>();
|
||||
@@ -113,6 +119,20 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
* setter in case e.g. jackson tries to map attachments (normal use embedFileInXML)
|
||||
* @param fileArr Array of FileAttachments
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setAdditionalReferencedDocuments(FileAttachment[] fileArr) {
|
||||
if (fileArr!=null) {
|
||||
xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
|
||||
} else {
|
||||
xmlEmbeddedFiles = new ArrayList<>();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDCashDiscount[] getCashDiscounts() {
|
||||
return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
|
||||
@@ -410,6 +430,11 @@ public class Invoice implements IExportableTransaction {
|
||||
return includedNotes;
|
||||
}
|
||||
|
||||
public Invoice setNotesWithSubjectCode(List<IncludedNote> theList) {
|
||||
includedNotes=theList;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
@@ -465,6 +490,26 @@ public class Invoice implements IExportableTransaction {
|
||||
return sender;
|
||||
}
|
||||
|
||||
/***
|
||||
* for currency rounding differences to 5ct e.g. in Netherlands ("Rappenrundung")
|
||||
* @return null if not set, otherwise BigDecimal of Euros
|
||||
*/
|
||||
@Override
|
||||
public BigDecimal getRoundingAmount() {
|
||||
return roundingAmount;
|
||||
}
|
||||
|
||||
/***
|
||||
* set the cent e.g. to reach the next 5ct mark for currencies in certain countries
|
||||
* e.g. in the Netherlands ("Rappenrundung")
|
||||
* @param amount
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setRoundingAmount(BigDecimal amount) {
|
||||
roundingAmount=amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* sets a named sender contact
|
||||
* @deprecated use setSender
|
||||
@@ -511,6 +556,26 @@ public class Invoice implements IExportableTransaction {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Getter for BG-3
|
||||
@Override
|
||||
public ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
|
||||
return invoiceReferencedDocuments;
|
||||
}
|
||||
|
||||
// Setter for BG-3
|
||||
public void setInvoiceReferencedDocuments(ArrayList<ReferencedDocument> invoiceReferencedDocuments) {
|
||||
this.invoiceReferencedDocuments = invoiceReferencedDocuments;
|
||||
}
|
||||
|
||||
// Method to add a single ReferencedDocument for BG-3
|
||||
public Invoice addInvoiceReferencedDocument(ReferencedDocument doc) {
|
||||
if (invoiceReferencedDocuments == null) {
|
||||
invoiceReferencedDocuments = new ArrayList<>();
|
||||
}
|
||||
invoiceReferencedDocuments.add(doc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFAllowances() {
|
||||
if (Allowances.isEmpty()) {
|
||||
@@ -520,6 +585,20 @@ public class Invoice implements IExportableTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* this is wrong and only used from jackson
|
||||
* @param iza the Array of allowances/charges
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setZFAllowances(Allowance[] iza) {
|
||||
Allowances=new ArrayList<>();
|
||||
|
||||
for (IZUGFeRDAllowanceCharge cz:iza) {
|
||||
Allowances.add(cz);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFCharges() {
|
||||
@@ -530,6 +609,18 @@ public class Invoice implements IExportableTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* this is wrong and only used from jackson
|
||||
* @param iza the array of charges
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setZFCharges(Charge[] iza) {
|
||||
Charges=new ArrayList<>();
|
||||
for (IZUGFeRDAllowanceCharge cz:iza) {
|
||||
Charges.add(cz);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
|
||||
@@ -555,11 +646,49 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
@Override
|
||||
public IZUGFeRDPaymentTerms getPaymentTerms() {
|
||||
return paymentTerms;
|
||||
if (!paymentTerms.isEmpty()) {
|
||||
return paymentTerms.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerms) {
|
||||
this.paymentTerms = paymentTerms;
|
||||
public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
|
||||
if (paymentTerms.isEmpty()) {
|
||||
paymentTerms.add(paymentTerm);
|
||||
}
|
||||
else {
|
||||
paymentTerms.set(0, paymentTerm);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDPaymentTerms[] getExtendedPaymentTerms() {
|
||||
return paymentTerms.toArray(new IZUGFeRDPaymentTerms[0]);
|
||||
}
|
||||
|
||||
public Invoice setExtendedPaymentTerms(IZUGFeRDPaymentTerms[] paymentTerms) {
|
||||
this.paymentTerms.clear();
|
||||
this.paymentTerms.addAll(Arrays.asList(paymentTerms));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple payment terms when using the EXTENDED profile.
|
||||
* @return
|
||||
*/
|
||||
public Invoice addPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
|
||||
paymentTerms.add(paymentTerm);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public String getPaymentReference() {
|
||||
return paymentReference;
|
||||
}
|
||||
|
||||
public Invoice setPaymentReference(String paymentReference) {
|
||||
this.paymentReference = paymentReference;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -577,6 +706,22 @@ public class Invoice implements IExportableTransaction {
|
||||
this.deliveryAddress = deliveryAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradeParty getPayee() {
|
||||
return this.payee;
|
||||
}
|
||||
|
||||
/***
|
||||
* if the payee is not the seller, it can be specified here
|
||||
* @param payee the payment receiving organisation
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setPayee(TradeParty payee) {
|
||||
this.payee = payee;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* Adds a cash discount (skonto)
|
||||
* @param c the CashDiscount percent/period combination
|
||||
@@ -680,11 +825,20 @@ public class Invoice implements IExportableTransaction {
|
||||
return detailedDeliveryDateStart;
|
||||
}
|
||||
|
||||
public Invoice setDetailedDeliveryPeriodFrom(Date dt) {
|
||||
detailedDeliveryDateStart=dt;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return detailedDeliveryPeriodEnd;
|
||||
}
|
||||
|
||||
public Invoice setDetailedDeliveryPeriodTo(Date dt) {
|
||||
detailedDeliveryPeriodEnd=dt;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* describes any invoice line
|
||||
*/
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Item implements IZUGFeRDExportableItem {
|
||||
protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount;
|
||||
protected BigDecimal price = BigDecimal.ZERO;
|
||||
protected BigDecimal quantity;
|
||||
protected BigDecimal tax;
|
||||
protected BigDecimal grossPrice;
|
||||
protected BigDecimal lineTotalAmount;
|
||||
protected BigDecimal basisQuantity = BigDecimal.ONE;
|
||||
protected Date detailedDeliveryPeriodFrom = null, detailedDeliveryPeriodTo = null;
|
||||
protected Date detailedDeliveryPeriodFrom = null;
|
||||
protected Date detailedDeliveryPeriodTo = null;
|
||||
protected String id;
|
||||
protected String buyerOrderReferencedDocumentLineID = null;
|
||||
protected String buyerOrderReferencedDocumentID = null;
|
||||
@@ -28,8 +40,11 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
protected ArrayList<String> notes = null;
|
||||
protected ArrayList<ReferencedDocument> referencedDocuments = null;
|
||||
protected ArrayList<ReferencedDocument> additionalReference = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
|
||||
Charges = new ArrayList<>();
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
||||
protected List<IncludedNote> includedNotes = null;
|
||||
protected String accountingReference;
|
||||
//protected HashMap<String, String> attributes = new HashMap<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -43,7 +58,6 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
* do not use, but might be used e.g. by jackson
|
||||
@@ -52,100 +66,67 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
}
|
||||
|
||||
public Item(NodeList itemChilds, boolean recalcPrice) {
|
||||
String price = "0";
|
||||
String basisQuantity = "1";
|
||||
String name = "";
|
||||
String sellerAssignedID = null;
|
||||
String description = "";
|
||||
SchemedID gid = null;
|
||||
String quantity = "0";
|
||||
String vatPercent = null;
|
||||
String lineTotal = "0";
|
||||
String unitCode = "0";
|
||||
String buyerOrderReferencedDocumentLineID = null;
|
||||
String buyerOrderReferencedDocumentID = null;
|
||||
NodeMap itemMap = new NodeMap(itemChilds);
|
||||
|
||||
ArrayList<ReferencedDocument> rdocs = null;
|
||||
ArrayList<ReferencedDocument> addRefs = null;
|
||||
itemMap.getAsNodeMap("Item").ifPresent(icnm -> {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
|
||||
// nodes.item(i).getTextContent())) {
|
||||
setProduct(new Product(itemMap.getNode("Item").get()));
|
||||
icnm.getAsString("Name").ifPresent(product::setName);
|
||||
icnm.getAsString("Description").ifPresent(product::setDescription);
|
||||
|
||||
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) {
|
||||
String lineTrade = itemChilds.item(itemChildIndex).getLocalName();
|
||||
if ((lineTrade != null) && (lineTrade.equals("Item"))) {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
NodeList UBLitemChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLItemChildNode : XMLTools.asList(UBLitemChilds)) {
|
||||
icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> {
|
||||
SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID);
|
||||
});
|
||||
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("Name"))) {
|
||||
name = currentUBLItemChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("ClassifiedTaxCategory"))) {
|
||||
for (Node currentUBLTaxChildNode : XMLTools.asList(currentUBLItemChildNode.getChildNodes())) {
|
||||
if ((currentUBLTaxChildNode.getLocalName() != null) && (currentUBLTaxChildNode.getLocalName().equals("Percent"))) {
|
||||
vatPercent = currentUBLTaxChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("Price"))) {
|
||||
// ubl
|
||||
// PriceAmount with currencyID and BaseQuantity with unitCode
|
||||
NodeList UBLpriceChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLPriceChildNode : XMLTools.asList(UBLpriceChilds)) {
|
||||
icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> {
|
||||
BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID);
|
||||
});
|
||||
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("PriceAmount"))) {
|
||||
price = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("BaseQuantity"))) {
|
||||
basisQuantity = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
});
|
||||
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> {
|
||||
icnm.getAsString("LineID").ifPresent(this::setId);
|
||||
});
|
||||
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("InvoicedQuantity"))) {
|
||||
// ubl
|
||||
quantity = itemChilds.item(itemChildIndex).getTextContent();
|
||||
unitCode = itemChilds.item(itemChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeAgreement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeAgreement"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
||||
// ubl
|
||||
// PriceAmount with currencyID and BaseQuantity with unitCode
|
||||
icnm.getAsBigDecimal("PriceAmount").ifPresent(this::setPrice);
|
||||
icnm.getAsBigDecimal("BaseQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
|
||||
String IssuerAssignedID = "";
|
||||
String TypeCode = "";
|
||||
String ReferenceTypeCode = "";
|
||||
itemMap.getNode("InvoicedQuantity").ifPresent(icn -> {
|
||||
// ubl
|
||||
setQuantity(new BigDecimal(icn.getTextContent().trim()));
|
||||
product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
|
||||
});
|
||||
|
||||
NodeList refDocChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int refDocIndex = 0; refDocIndex < refDocChilds.getLength(); refDocIndex++) {
|
||||
String localName = refDocChilds.item(refDocIndex).getLocalName();
|
||||
if ((localName != null) && (localName.equals("IssuerAssignedID"))) {
|
||||
IssuerAssignedID = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("TypeCode"))) {
|
||||
TypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("ReferenceTypeCode"))) {
|
||||
ReferenceTypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
itemMap.getAllNodes("DocumentReference").map(ReferencedDocument::fromNode)
|
||||
.forEach(this::addAdditionalReference);
|
||||
|
||||
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
|
||||
ReferenceTypeCode);
|
||||
if (rdocs == null) {
|
||||
rdocs = new ArrayList<>();
|
||||
}
|
||||
rdocs.add(rd);
|
||||
// ubl
|
||||
|
||||
}
|
||||
itemMap.getAsNodeMap("OrderLineReference")
|
||||
// ubl
|
||||
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
|
||||
.ifPresent(this::addReferencedLineID);
|
||||
|
||||
itemMap.getAsString("ID")
|
||||
.ifPresent(this::setId);
|
||||
|
||||
|
||||
itemMap.getAsString("Note")
|
||||
.ifPresent(this::addNote);
|
||||
|
||||
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
|
||||
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
|
||||
.ifPresent(this::addReferencedLineID);
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds.item(tradeLineChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
|
||||
NodeList docChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
@@ -159,166 +140,155 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
}
|
||||
}
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("NetPriceProductTradePrice")) {
|
||||
NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) {
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& (netChilds.item(netIndex).getLocalName().equals("ChargeAmount"))) {
|
||||
price = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
|
||||
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
|
||||
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
}
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& ((netChilds.item(netIndex).getLocalName().equals("BasisQuantity")) || (netChilds.item(netIndex).getLocalName().equals("InvoicedQuantity")))) {
|
||||
basisQuantity = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).
|
||||
forEach(this::addReferencedDocument);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//CII
|
||||
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//UBL
|
||||
|
||||
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery")
|
||||
.flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity"))
|
||||
.ifPresent(bq -> {
|
||||
setQuantity(new BigDecimal(bq.getTextContent().trim()));
|
||||
if (bq.hasAttributes()) {
|
||||
Node unitAttr = bq.getAttributes().getNamedItem("unitCode");
|
||||
if (unitAttr != null) {
|
||||
product.setUnit(unitAttr.getNodeValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeSettlement", "SpecifiedSupplyChainTradeSettlement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("ApplicableTradeTax")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
icnm.getAsNodeMap("ApplicableTradeTax")
|
||||
.flatMap(cnm -> cnm.getAsString("ExemptionReason"))
|
||||
.ifPresent(product::setTaxExemptionReason);
|
||||
|
||||
icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> {
|
||||
stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> {
|
||||
String isChargeString = ci.getAsString("Indicator").get();
|
||||
String percentString = stac.getAsStringOrNull("CalculationPercent");
|
||||
String amountString = stac.getAsStringOrNull("ActualAmount");
|
||||
String basisAmountString = stac.getAsStringOrNull("BasisAmount");
|
||||
String reason = stac.getAsStringOrNull("Reason");
|
||||
Charge izac = new Charge();
|
||||
if (isChargeString.equalsIgnoreCase("false")) {
|
||||
izac = new Allowance();
|
||||
} else {
|
||||
izac = new Charge();
|
||||
}
|
||||
if (amountString != null) {
|
||||
izac.setTotalAmount(new BigDecimal(amountString));
|
||||
if (percentString!=null&&(percentString!="0")) {
|
||||
izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity()));
|
||||
}
|
||||
}
|
||||
if (basisAmountString != null) {
|
||||
izac.setBasisAmount(new BigDecimal(basisAmountString));
|
||||
}
|
||||
if (percentString != null) {
|
||||
izac.setPercent(new BigDecimal(percentString));
|
||||
}
|
||||
if (reason != null) {
|
||||
izac.setReason(reason);
|
||||
}
|
||||
|
||||
if (isChargeString.equalsIgnoreCase("false")) {
|
||||
addAllowance(izac);
|
||||
} else {
|
||||
addCharge(izac);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
if (recalcPrice && !BigDecimal.ZERO.equals(quantity)) {
|
||||
icnm.getAsNodeMap("SpecifiedTradeSettlementLineMonetarySummation")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("LineTotalAmount"))
|
||||
.ifPresent(lineTotal -> setPrice(lineTotal.divide(quantity, 4, RoundingMode.HALF_UP)));
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeDelivery")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeDelivery"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
String tradeName = tradeLineChilds.item(tradeLineChildIndex).getLocalName();
|
||||
if ((tradeName != null)
|
||||
&& (tradeName.equals("BilledQuantity") || tradeName.equals("RequestedQuantity")
|
||||
|| tradeName.equals("DespatchedQuantity"))) {
|
||||
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
|
||||
quantity = tradeLineChilds.item(tradeLineChildIndex).getTextContent();
|
||||
unitCode = tradeLineChilds.item(tradeLineChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
|
||||
|
||||
icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim());
|
||||
|
||||
icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> {
|
||||
Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
|
||||
Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
|
||||
setDetailedDeliveryPeriod(start, end);
|
||||
});
|
||||
});
|
||||
|
||||
itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //UBL
|
||||
|
||||
String isChargeString = stac.getAsString("ChargeIndicator").get();
|
||||
String percentString = stac.getAsStringOrNull("MultiplierFactorNumeric");
|
||||
String amountString = stac.getAsStringOrNull("Amount");
|
||||
String reason = stac.getAsStringOrNull("AllowanceChargeReason");
|
||||
Charge izac = new Charge();
|
||||
if (isChargeString.equalsIgnoreCase("false")) {
|
||||
izac = new Allowance();
|
||||
} else {
|
||||
izac = new Charge();
|
||||
}
|
||||
if (amountString != null) {
|
||||
izac.setTotalAmount(new BigDecimal(amountString));
|
||||
}
|
||||
if (percentString != null) {
|
||||
izac.setPercent(new BigDecimal(percentString));
|
||||
}
|
||||
if (reason != null) {
|
||||
izac.setReason(reason);
|
||||
}
|
||||
|
||||
if (isChargeString.equalsIgnoreCase("false")) {
|
||||
addAllowance(izac);
|
||||
} else {
|
||||
addCharge(izac);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> {
|
||||
List<IncludedNote> includedNotes = new ArrayList<>();
|
||||
adld.getAllNodes("IncludedNote").forEach(item -> {
|
||||
String subjectCode = "";
|
||||
String content = null;
|
||||
NodeList includedNodeChilds = item.getChildNodes();
|
||||
for (int issueDateChildIndex = 0; issueDateChildIndex < includedNodeChilds.getLength(); issueDateChildIndex++) {
|
||||
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
|
||||
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("Content"))) {
|
||||
content = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
|
||||
}
|
||||
if ((includedNodeChilds.item(issueDateChildIndex).getLocalName() != null)
|
||||
&& (includedNodeChilds.item(issueDateChildIndex).getLocalName().equals("SubjectCode"))) {
|
||||
subjectCode = XMLTools.trimOrNull(includedNodeChilds.item(issueDateChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedTradeProduct"))) {
|
||||
NodeList tradeProductChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeProductChildIndex = 0; tradeProductChildIndex < tradeProductChilds
|
||||
.getLength(); tradeProductChildIndex++) {
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("Name"))) {
|
||||
name = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("SellerAssignedID"))) {
|
||||
sellerAssignedID = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("GlobalID"))) {
|
||||
if (tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID") != null) {
|
||||
gid = new SchemedID()
|
||||
.setScheme(tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue())
|
||||
.setId(tradeProductChilds.item(tradeProductChildIndex).getTextContent());
|
||||
}
|
||||
|
||||
boolean foundCode = false;
|
||||
for (SubjectCode code : SubjectCode.values()) {
|
||||
if (code.toString().equals(subjectCode)) {
|
||||
includedNotes.add(new IncludedNote(content, code));
|
||||
foundCode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeSettlement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeSettlement"))) {
|
||||
NodeList tradeSettlementChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeSettlementChildIndex = 0; tradeSettlementChildIndex < tradeSettlementChilds
|
||||
.getLength(); tradeSettlementChildIndex++) {
|
||||
|
||||
String tradeSettlementName = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getLocalName();
|
||||
if (tradeSettlementName != null) {
|
||||
if (tradeSettlementName.equals("ApplicableTradeTax")) {
|
||||
NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds
|
||||
.getLength(); taxChildIndex++) {
|
||||
String taxChildName = taxChilds.item(taxChildIndex).getLocalName();
|
||||
if ((taxChildName != null) && (taxChildName.equals("RateApplicablePercent")
|
||||
|| taxChildName.equals("ApplicablePercent"))) {
|
||||
vatPercent = taxChilds.item(taxChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradeSettlementName.equals("SpecifiedTradeSettlementLineMonetarySummation")) {
|
||||
NodeList totalChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int totalChildIndex = 0; totalChildIndex < totalChilds
|
||||
.getLength(); totalChildIndex++) {
|
||||
if ((totalChilds.item(totalChildIndex).getLocalName() != null) && (totalChilds
|
||||
.item(totalChildIndex).getLocalName().equals("LineTotalAmount"))) {
|
||||
lineTotal = totalChilds.item(totalChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradeSettlementName.equals("AdditionalReferencedDocument")) {
|
||||
String IssuerAssignedID = "";
|
||||
String TypeCode = "";
|
||||
String ReferenceTypeCode = "";
|
||||
|
||||
NodeList refDocChilds = tradeSettlementChilds.item(tradeSettlementChildIndex).getChildNodes();
|
||||
for (int refDocIndex = 0; refDocIndex < refDocChilds.getLength(); refDocIndex++) {
|
||||
String localName = refDocChilds.item(refDocIndex).getLocalName();
|
||||
if ((localName != null) && (localName.equals("IssuerAssignedID"))) {
|
||||
IssuerAssignedID = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("TypeCode"))) {
|
||||
TypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("ReferenceTypeCode"))) {
|
||||
ReferenceTypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
|
||||
ReferenceTypeCode);
|
||||
if (addRefs == null) {
|
||||
addRefs = new ArrayList<>();
|
||||
}
|
||||
addRefs.add(rd);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (!foundCode) {
|
||||
includedNotes.add(new IncludedNote(content, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
BigDecimal prc = new BigDecimal(price.trim());
|
||||
BigDecimal qty = new BigDecimal(quantity.trim());
|
||||
if ((recalcPrice) && (!qty.equals(BigDecimal.ZERO))) {
|
||||
prc = new BigDecimal(lineTotal.trim()).divide(qty, 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
Product p = new Product(name, description, unitCode,
|
||||
vatPercent == null ? null : new BigDecimal(vatPercent.trim()));
|
||||
if (gid != null) {
|
||||
p.addGlobalID(gid);
|
||||
}
|
||||
if (sellerAssignedID != null) {
|
||||
p.setSellerAssignedID(sellerAssignedID);
|
||||
}
|
||||
setProduct(p);
|
||||
setPrice(prc);
|
||||
setQuantity(qty);
|
||||
setBasisQuantity(new BigDecimal(basisQuantity));
|
||||
if (rdocs != null) {
|
||||
for (ReferencedDocument rdoc : rdocs) {
|
||||
addReferencedDocument(rdoc);
|
||||
}
|
||||
}
|
||||
if (addRefs != null) {
|
||||
for (ReferencedDocument rdoc : addRefs) {
|
||||
addAdditionalReference(rdoc);
|
||||
}
|
||||
}
|
||||
addBuyerOrderReferencedDocumentLineID( buyerOrderReferencedDocumentLineID );
|
||||
addBuyerOrderReferencedDocumentID( buyerOrderReferencedDocumentID );
|
||||
});
|
||||
addNotes(includedNotes);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public Item addBuyerOrderReferencedDocumentLineID(String s) {
|
||||
buyerOrderReferencedDocumentLineID = s;
|
||||
return this;
|
||||
@@ -339,6 +309,20 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public IZUGFeRDAllowanceCharge[] getAllowances() {
|
||||
IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()];
|
||||
return Allowances.toArray(izac);
|
||||
}
|
||||
|
||||
@Override public IZUGFeRDAllowanceCharge[] getCharges() {
|
||||
IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()];
|
||||
return Charges.toArray(izac);
|
||||
}
|
||||
|
||||
/***
|
||||
* BT 132 (issue https://github.com/ZUGFeRD/mustangproject/issues/247)
|
||||
* @return the line ID of the order (BT132)
|
||||
*/
|
||||
@Override
|
||||
public String getBuyerOrderReferencedDocumentLineID() {
|
||||
return buyerOrderReferencedDocumentLineID;
|
||||
@@ -348,6 +332,11 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
return lineTotalAmount;
|
||||
}
|
||||
|
||||
public Item setNotesWithSubjectCode(List<IncludedNote> theList) {
|
||||
includedNotes = theList;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* should only be set by calculator classes or maybe when reading from XML
|
||||
*
|
||||
@@ -438,6 +427,30 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
|
||||
}
|
||||
|
||||
/***
|
||||
* jackson convenience method
|
||||
*/
|
||||
public void setItemAllowances(ArrayList<Allowance> theAllowances) {
|
||||
if (theAllowances != null) {
|
||||
Allowances.clear();
|
||||
for (Allowance theAllowance : theAllowances) {
|
||||
Allowances.add(theAllowance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* jackson convenience method
|
||||
*/
|
||||
public void setItemCharges(ArrayList<Charge> theCharges) {
|
||||
if (theCharges != null) {
|
||||
Charges.clear();
|
||||
for (Charge theCharge : theCharges) {
|
||||
Charges.add(theCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getItemCharges() {
|
||||
if (Charges.isEmpty()) {
|
||||
@@ -463,7 +476,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
|
||||
/***
|
||||
* Adds a item level addition to the price (will be multiplied by quantity)
|
||||
* @see org.mustangproject.Charge
|
||||
* @see Charge
|
||||
* @param izac a relative or absolute charge
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -474,11 +487,12 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
|
||||
/***
|
||||
* Adds a item level reduction the price (will be multiplied by quantity)
|
||||
* @see org.mustangproject.Allowance
|
||||
* @see Allowance
|
||||
* @param izac a relative or absolute allowance
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Item addAllowance(IZUGFeRDAllowanceCharge izac) {
|
||||
|
||||
Allowances.add(izac);
|
||||
return this;
|
||||
}
|
||||
@@ -493,6 +507,26 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
notes = new ArrayList<>();
|
||||
}
|
||||
notes.add(text);
|
||||
|
||||
addNote(IncludedNote.unspecifiedNote(text));
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* adds categorized item level freetext fields (includednote)
|
||||
* @param theNote IncludedNote to add
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Item addNote(IncludedNote theNote) {
|
||||
|
||||
if (includedNotes == null) {
|
||||
includedNotes = new ArrayList<>();
|
||||
}
|
||||
includedNotes.add(theNote);
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -519,7 +553,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
|
||||
|
||||
/***
|
||||
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
|
||||
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
|
||||
* @param doc the ReferencedDocument to add
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -538,8 +572,8 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
}
|
||||
return additionalReference.toArray(new IReferencedDocument[0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* specify a item level delivery period
|
||||
* (apart from the document level delivery period, and the document level
|
||||
@@ -560,6 +594,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the beginning of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
return detailedDeliveryPeriodFrom;
|
||||
}
|
||||
@@ -569,8 +604,29 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the end of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return detailedDeliveryPeriodTo;
|
||||
}
|
||||
|
||||
public IZUGFeRDExportableItem addNotes(Collection<IncludedNote> notes) {
|
||||
if (notes == null) {
|
||||
return this;
|
||||
}
|
||||
if (includedNotes == null) {
|
||||
includedNotes = new ArrayList<>();
|
||||
}
|
||||
includedNotes.addAll(notes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IncludedNote> getNotesWithSubjectCode() {
|
||||
return includedNotes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountingReference() {
|
||||
return accountingReference;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.*;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -9,6 +10,7 @@ import org.w3c.dom.NodeList;
|
||||
* A organisation, i.e. usually a company
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
|
||||
|
||||
protected SchemedID schemedID = null;
|
||||
@@ -18,12 +20,16 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
|
||||
}
|
||||
|
||||
public LegalOrganisation(String ID, String scheme) {
|
||||
this.schemedID = new SchemedID(ID, scheme);
|
||||
this.schemedID = new SchemedID(scheme, ID);
|
||||
}
|
||||
|
||||
public LegalOrganisation(String ID) {
|
||||
this.schemedID = new SchemedID(null, ID);
|
||||
}
|
||||
|
||||
public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) {
|
||||
this.schemedID = schemedID;
|
||||
this.tradingBusinessName=tradingBusinessName;
|
||||
this.tradingBusinessName = tradingBusinessName;
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -48,8 +54,8 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
|
||||
this.setSchemedID(gid);
|
||||
}
|
||||
}
|
||||
if (currentItemNode.getLocalName().equals("TradingBusinessName")) {
|
||||
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
|
||||
if ((currentItemNode.getLocalName().equals("TradingBusinessName")) && (currentItemNode.getFirstChild() != null)) {
|
||||
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* describes a product, good or service used in an invoice item line
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
|
||||
public class Product implements IZUGFeRDExportableProduct {
|
||||
protected String unit, name, sellerAssignedID, buyerAssignedID;
|
||||
protected String description="";
|
||||
protected String description = "";
|
||||
protected String taxExemptionReason = null;
|
||||
protected String taxCategoryCode = null;
|
||||
protected BigDecimal VATPercent;
|
||||
protected boolean isReverseCharge = false;
|
||||
protected boolean isIntraCommunitySupply = false;
|
||||
protected SchemedID globalId = null;
|
||||
protected String countryOfOrigin = null;
|
||||
protected HashMap<String, String> attributes = null;
|
||||
protected ArrayList<Charge> charges=new ArrayList<>();
|
||||
protected ArrayList<Allowance> allowances=new ArrayList<>();
|
||||
protected HashMap<String, String> attributes = new HashMap<>();
|
||||
|
||||
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -34,6 +52,63 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
this.VATPercent = VATPercent;
|
||||
}
|
||||
|
||||
public Product(Node node) {
|
||||
NodeMap nodeMap = new NodeMap(node);
|
||||
|
||||
nodeMap.getNode("GlobalID").ifPresent(idNode -> {
|
||||
if (idNode.hasAttributes()
|
||||
&& idNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
globalId = new SchemedID()
|
||||
.setScheme(idNode.getAttributes().getNamedItem("schemeID").getNodeValue())
|
||||
.setId(idNode.getTextContent());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
|
||||
nodeMap.getAllNodes("ApplicableProductCharacteristic").map(NodeMap::new).forEach(apcNodes -> { // ApplicableProductCharacteristic is 0 .. unbounded
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
if (key != null && value != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
//UBL
|
||||
nodeMap.getAsNodeMap("AdditionalItemProperty").ifPresent(aipNodes -> {
|
||||
String name = aipNodes.getAsStringOrNull("Name");
|
||||
String val = aipNodes.getAsStringOrNull("Value");
|
||||
if (name != null && val != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(name, val);
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsNodeMap("CommodityClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ItemClassificationCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
//UBL
|
||||
nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
|
||||
}
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
@@ -67,6 +142,48 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. intra-commnunity supply or small business
|
||||
*/
|
||||
@Override
|
||||
public String getTaxExemptionReason() {
|
||||
return taxExemptionReason;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param taxExemptionReasonText String e.g. Kleinunternehmer gemäß §19 UStG https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxExemptionReason(String taxExemptionReasonText) {
|
||||
taxExemptionReason = taxExemptionReasonText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply)
|
||||
*/
|
||||
@Override
|
||||
public String getTaxCategoryCode() {
|
||||
if (taxCategoryCode == null) {
|
||||
return IZUGFeRDExportableProduct.super.getTaxCategoryCode();
|
||||
}
|
||||
return taxCategoryCode;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param code e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply) see also https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxCategoryCode(String code) {
|
||||
taxCategoryCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getSellerAssignedID() {
|
||||
return sellerAssignedID;
|
||||
@@ -125,6 +242,8 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
public Product setIntraCommunitySupply() {
|
||||
isIntraCommunitySupply = true;
|
||||
setVATPercent(BigDecimal.ZERO);
|
||||
setTaxExemptionReason("Intra-community supply");
|
||||
setTaxCategoryCode("K");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -186,35 +305,132 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setVATPercent(BigDecimal VATPercent) {
|
||||
this.VATPercent = VATPercent;
|
||||
if (VATPercent == null) {
|
||||
this.VATPercent = BigDecimal.ZERO;
|
||||
} else {
|
||||
this.VATPercent = VATPercent;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCountryOfOrigin() {
|
||||
return this.countryOfOrigin;
|
||||
return this.countryOfOrigin;
|
||||
}
|
||||
|
||||
public Product setCountryOfOrigin(String countryOfOrigin) {
|
||||
this.countryOfOrigin = countryOfOrigin;
|
||||
return this;
|
||||
this.countryOfOrigin = countryOfOrigin;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, String> getAttributes() {
|
||||
return this.attributes;
|
||||
if (attributes.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public Product setAttributes(HashMap<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
return this;
|
||||
public Product setAttributes(Map<String, String> attributes) {
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addAttribute(String name, String value ) {
|
||||
if ( this.attributes == null ) {
|
||||
this.attributes = new HashMap<>();
|
||||
}
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
public Product addAttribute(String name, String value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDesignatedProductClassification[] getClassifications() {
|
||||
if (classifications.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return classifications.toArray(new IDesignatedProductClassification[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the current set of {@link IDesignatedProductClassification}s with a new set
|
||||
*
|
||||
* @param classifications the new set of classifications
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product setClassifications(IDesignatedProductClassification[] classifications) {
|
||||
this.classifications.clear();
|
||||
if (classifications != null) {
|
||||
this.classifications.addAll(Arrays.asList(classifications));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provide Jackson a hint as to use DesignatedProductClassification for the
|
||||
* IDesignatedProductClassification product classifications
|
||||
*
|
||||
* @param classifications the new set of classifications
|
||||
* @return fluent setter
|
||||
*/
|
||||
@JsonSetter("classifications")
|
||||
public Product setClassificationsClass(DesignatedProductClassification[] classifications) {
|
||||
this.classifications.clear();
|
||||
if (classifications != null) {
|
||||
this.classifications.addAll(Arrays.asList(classifications));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link IDesignatedProductClassification} classification
|
||||
*
|
||||
* @param classification the classification
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product addClassification(IDesignatedProductClassification classification) {
|
||||
this.classifications.add(classification);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addCharge(Charge e) {
|
||||
charges.add(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addAllowance(Allowance a) {
|
||||
allowances.add(a);
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* returns the AppliedTradeAllowanceCharges of this product which are actually Charges
|
||||
* @return array of or null, if none
|
||||
*/
|
||||
@Override
|
||||
public Charge[] getCharges() {
|
||||
if (charges.size()==0) {
|
||||
return null;
|
||||
}
|
||||
Charge[] chargeArr = new Charge[charges.size()];
|
||||
return charges.toArray(chargeArr);
|
||||
}
|
||||
|
||||
@Override
|
||||
/***
|
||||
* returns the AppliedTradeAllowanceCharges of this product which are actually Allowances
|
||||
* @return array of or null, if none
|
||||
*/
|
||||
public Allowance[] getAllowances() {
|
||||
if (allowances.size()==0) {
|
||||
return null;
|
||||
}
|
||||
Allowance[] allowanceArr = new Allowance[allowances.size()];
|
||||
return allowances.toArray(allowanceArr);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import java.util.Date;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ReferencedDocument implements IReferencedDocument {
|
||||
|
||||
String issuerAssignedID;
|
||||
String typeCode;
|
||||
String referenceTypeCode;
|
||||
Date formattedIssueDateTime;
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode) {
|
||||
this.issuerAssignedID = issuerAssignedID;
|
||||
this(issuerAssignedID);
|
||||
this.typeCode = typeCode;
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode, Date formattedIssueDateTime) {
|
||||
this(issuerAssignedID, typeCode, referenceTypeCode);
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID, String referenceTypeCode) {
|
||||
this(issuerAssignedID, "916", referenceTypeCode); // additional invoice related document
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssingedID, Date formattedIssueDateTime) {
|
||||
this(issuerAssingedID);
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID) {
|
||||
this.issuerAssignedID = issuerAssignedID;
|
||||
this.typeCode = "916"; // additional invoice related document
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -44,6 +65,15 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* issue date of this line
|
||||
*
|
||||
* @param formattedIssueDateTime as Date
|
||||
*/
|
||||
public void setFormattedIssueDateTime(Date formattedIssueDateTime) {
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIssuerAssignedID() {
|
||||
return issuerAssignedID;
|
||||
@@ -58,4 +88,21 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
public String getReferenceTypeCode() {
|
||||
return referenceTypeCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getFormattedIssueDateTime()
|
||||
{
|
||||
return formattedIssueDateTime;
|
||||
}
|
||||
|
||||
public static ReferencedDocument fromNode(Node node) {
|
||||
if (!node.hasChildNodes()) {
|
||||
return null;
|
||||
}
|
||||
NodeMap nodes = new NodeMap(node);
|
||||
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
|
||||
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"),
|
||||
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package org.mustangproject;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class SchemedID {
|
||||
protected String scheme;
|
||||
protected String id;
|
||||
@@ -31,4 +37,9 @@ public class SchemedID {
|
||||
setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,5 +36,9 @@ public enum SubjectCode {
|
||||
/**
|
||||
* Discount and bonus agreements
|
||||
*/
|
||||
AAK
|
||||
AAK,
|
||||
/**
|
||||
* Vehicle licence number
|
||||
*/
|
||||
ABZ
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.mustangproject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation;
|
||||
@@ -12,15 +14,18 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
|
||||
/***
|
||||
* A organisation, i.e. usually a company
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
protected String name, zip, street, location, country;
|
||||
protected String name, zip, street, location, country, taxScheme;
|
||||
protected String taxID = null, vatID = null;
|
||||
protected String ID = null;
|
||||
protected String description = null;
|
||||
@@ -59,6 +64,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected void parseFromUBL(NodeList nodes) {
|
||||
if (nodes.getLength() > 0) {
|
||||
|
||||
@@ -68,8 +74,34 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
if (currentItemNode.getLocalName() != null) {
|
||||
String currentUBLChild = currentItemNode.getLocalName();
|
||||
if (currentUBLChild.equals("Party")) {
|
||||
// if (currentUBLChild.equals("Delivery")) {
|
||||
// NodeList delivery = currentItemNode.getChildNodes();
|
||||
// for (int deliveryIndex = 0; deliveryIndex < delivery.getLength(); deliveryIndex++) {
|
||||
// if (delivery.item(deliveryIndex).getLocalName() != null) {
|
||||
// Node currentNode = delivery.item(deliveryIndex);
|
||||
// if (currentNode.getLocalName().equals("DeliveryLocation")) {
|
||||
// NodeList deliveryLocation = currentNode.getChildNodes();
|
||||
// for (int deliveryLocationIndex = 0; deliveryLocationIndex < deliveryLocation.getLength(); deliveryLocationIndex++) {
|
||||
// if (deliveryLocation.item(deliveryLocationIndex).getLocalName() != null) {
|
||||
// if (deliveryLocation.item(deliveryLocationIndex).getLocalName().equals("ID")) {
|
||||
// //Node currentNode = partyID.item(partyIDIndex);
|
||||
// setID(deliveryLocation.item(deliveryLocationIndex).getTextContent());
|
||||
// if ((deliveryLocation.item(deliveryLocationIndex).getAttributes() != null &&
|
||||
// (deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID") != null))
|
||||
// ) {
|
||||
// SchemedID sID = new SchemedID().setScheme(deliveryLocation.item(deliveryLocationIndex).getAttributes().getNamedItem("schemeID").getTextContent());
|
||||
// addGlobalID(sID);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (currentUBLChild.equals("Party")) {
|
||||
NodeList party = currentItemNode.getChildNodes();
|
||||
for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) {
|
||||
if (party.item(partyIndex).getLocalName() != null) {
|
||||
@@ -87,19 +119,102 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (party.item(partyIndex).getLocalName().equals("EndpointID")) {
|
||||
Node currentNode = party.item(partyIndex);
|
||||
if ((currentNode.getAttributes() != null &&
|
||||
(currentNode.getAttributes().getNamedItem("schemeID") != null))
|
||||
&& (party.item(partyIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("EM"))
|
||||
) {
|
||||
setEmail(currentNode.getTextContent());
|
||||
}
|
||||
|
||||
}
|
||||
if (currentTopElementName.equals("PartyIdentification")) {
|
||||
NodeList partyID = party.item(partyIndex).getChildNodes();
|
||||
for (int partyIDIndex = 0; partyIDIndex < partyID.getLength(); partyIDIndex++) {
|
||||
if (partyID.item(partyIDIndex).getLocalName() != null) {
|
||||
if (partyID.item(partyIDIndex).getLocalName().equals("ID")) {
|
||||
Node currentNode = partyID.item(partyIDIndex);
|
||||
if ((currentNode.getAttributes() != null &&
|
||||
(currentNode.getAttributes().getNamedItem("schemeID") != null))
|
||||
) {
|
||||
SchemedID sID = new SchemedID().setScheme(currentNode.getAttributes().getNamedItem("schemeID").getTextContent()).setId(currentNode.getTextContent());
|
||||
addGlobalID(sID);
|
||||
|
||||
}
|
||||
else {
|
||||
setID(currentNode.getTextContent());
|
||||
}
|
||||
|
||||
// UBL only: formally it can have a name as well but BT27 party name *should* be stored in
|
||||
// so overwrite if one exists
|
||||
if (currentTopElementName.equals("PartyLegalEntity")) {
|
||||
NodeList legal = party.item(partyIndex).getChildNodes();
|
||||
for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) {
|
||||
if (legal.item(legalChildIndex).getLocalName() != null) {
|
||||
if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) {
|
||||
setName(legal.item(legalChildIndex).getTextContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTopElementName.equals("PartyTaxScheme")) {
|
||||
NodeList partyTaxScheme = party.item(partyIndex).getChildNodes();
|
||||
String CompanyId = null;
|
||||
for (int partyTaxSchemeIndex = 0; partyTaxSchemeIndex < partyTaxScheme.getLength(); partyTaxSchemeIndex++) {
|
||||
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName() != null) {
|
||||
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("CompanyID")) {
|
||||
CompanyId = (partyTaxScheme.item(partyTaxSchemeIndex).getTextContent());
|
||||
}
|
||||
if (partyTaxScheme.item(partyTaxSchemeIndex).getLocalName().equals("TaxScheme")) {
|
||||
NodeList taxSchemechilds = partyTaxScheme.item(partyTaxSchemeIndex).getChildNodes();
|
||||
for (int taxSchemechildsIndex = 0; taxSchemechildsIndex < taxSchemechilds.getLength(); taxSchemechildsIndex++) {
|
||||
if (taxSchemechilds.item(taxSchemechildsIndex).getLocalName() != null) {
|
||||
if (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("FC") || (taxSchemechilds.item(taxSchemechildsIndex).getTextContent().equals("NOVAT"))) {
|
||||
setTaxID(CompanyId);
|
||||
} else {
|
||||
setVATID(CompanyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
UBL only: formally it can have a name as well but BT27 party name *should* be stored in
|
||||
so overwrite if one exists
|
||||
*/
|
||||
|
||||
if (currentTopElementName.equals("PartyLegalEntity")) {
|
||||
NodeList legal = party.item(partyIndex).getChildNodes();
|
||||
LegalOrganisation lo = null;
|
||||
for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) {
|
||||
if (legal.item(legalChildIndex).getLocalName() != null) {
|
||||
|
||||
if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) {
|
||||
if (lo == null) {
|
||||
lo = new LegalOrganisation();
|
||||
}
|
||||
lo.setTradingBusinessName(legal.item(legalChildIndex).getTextContent());
|
||||
}
|
||||
if (legal.item(legalChildIndex).getLocalName().equals("CompanyLegalForm")) {
|
||||
setDescription(legal.item(legalChildIndex).getTextContent());
|
||||
}
|
||||
if (legal.item(legalChildIndex).getLocalName().equals("CompanyID")) {
|
||||
if (lo == null) {
|
||||
lo = new LegalOrganisation();
|
||||
}
|
||||
if (legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID")!=null) {
|
||||
SchemedID sid = new SchemedID(legal.item(legalChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue(), legal.item(legalChildIndex).getTextContent());
|
||||
lo.setSchemedID(sid);
|
||||
}
|
||||
}
|
||||
// we dont have that attribute yet in the legalorganisation: CompanyLegalForm
|
||||
if (lo != null) {
|
||||
setLegalOrganisation(lo);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTopElementName.equals("PostalAddress")) {
|
||||
|
||||
NodeList postal = party.item(partyIndex).getChildNodes();
|
||||
@@ -147,8 +262,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("Name")) {
|
||||
setName(postal.item(postalChildIndex).getTextContent());
|
||||
@@ -166,7 +279,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (currentUBLChild.equals("GlobalID")) {
|
||||
@@ -180,56 +292,6 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
NodeList contact = nodes.item(nodeIndex).getChildNodes();
|
||||
setContact(new Contact(contact));
|
||||
}
|
||||
|
||||
if (currentUBLChild.equals("PostalTradeAddress")) {
|
||||
NodeList postal = nodes.item(nodeIndex).getChildNodes();
|
||||
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) {
|
||||
if (postal.item(postalChildIndex).getLocalName() != null) {
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("LineOne")) {
|
||||
setStreet(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("LineTwo")) {
|
||||
setAdditionalAddress(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("LineThree")) {
|
||||
setAdditionalAddressExtension(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("CityName")) {
|
||||
setLocation(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("PostcodeCode")) {
|
||||
setZIP(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
if (postal.item(postalChildIndex).getLocalName().equals("CountryID")) {
|
||||
setCountry(postal.item(postalChildIndex).getTextContent());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (currentUBLChild.equals("SpecifiedTaxRegistration")) {
|
||||
NodeList taxChilds = nodes.item(nodeIndex).getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
|
||||
if (taxChilds.item(taxChildIndex).getLocalName() != null) {
|
||||
if ((taxChilds.item(taxChildIndex).getLocalName().equals("ID"))) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
|
||||
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
|
||||
if (firstChild != null) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
|
||||
setVATID(firstChild.getNodeValue());
|
||||
}
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
|
||||
setTaxID(firstChild.getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,6 +486,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationID() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getID();
|
||||
@@ -433,6 +496,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationIDScheme() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getScheme();
|
||||
@@ -497,6 +561,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
*
|
||||
@@ -508,6 +573,15 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* primarily for invoiceimporter and JSON
|
||||
*
|
||||
* @return the list of sepa mandates
|
||||
*/
|
||||
public List<DirectDebit> getDebitDetails() {
|
||||
return debitDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDLegalOrganisation getLegalOrganisation() {
|
||||
return legalOrg;
|
||||
@@ -667,6 +741,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return contact;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
|
||||
if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@@ -4,15 +4,13 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.RandomAccess;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
@@ -23,36 +21,11 @@ public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
public String escapeElementEntities(String s) {
|
||||
return super.escapeElementEntities(s);
|
||||
|
||||
}
|
||||
|
||||
public static List<Node> asList(NodeList n) {
|
||||
return n.getLength() == 0 ?
|
||||
Collections.<Node>emptyList() : new NodeListWrapper(n);
|
||||
}
|
||||
|
||||
static final class NodeListWrapper extends AbstractList<Node>
|
||||
implements RandomAccess {
|
||||
private final NodeList list;
|
||||
|
||||
NodeListWrapper(NodeList l) {
|
||||
list = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node get(int index) {
|
||||
return list.item(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
public static String nDigitFormat(BigDecimal value, int scale) {
|
||||
/*
|
||||
* I needed 123,45, locale independent.I tried
|
||||
* I needed 123.45, locale independent.I tried
|
||||
* NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is locale
|
||||
* specific.I also tried DecimalFormat df = new DecimalFormat( "0,00" );
|
||||
* df.setDecimalSeparatorAlwaysShown(true); df.setGroupingUsed(false);
|
||||
@@ -70,7 +43,114 @@ public class XMLTools extends XMLWriter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the value of an node
|
||||
*
|
||||
* @param node the Node to get the value from
|
||||
* @return A String or empty String, if no value was found
|
||||
*/
|
||||
public static String getNodeValue(Node node) {
|
||||
if (node != null && node.getFirstChild() != null) {
|
||||
return node.getFirstChild().getNodeValue();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* tries to convert a String to BigDecimal.
|
||||
*
|
||||
* @param nodeValue The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
public static BigDecimal tryBigDecimal(String nodeValue) {
|
||||
try {
|
||||
return new BigDecimal(nodeValue);
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
return BigDecimal.valueOf(Float.valueOf(nodeValue));
|
||||
} catch (final Exception ex) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* tries to convert a Node to a BigDecimal.
|
||||
*
|
||||
* @param node The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
public static BigDecimal tryBigDecimal(Node node) {
|
||||
final String nodeValue = XMLTools.getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return XMLTools.tryBigDecimal(nodeValue);
|
||||
}
|
||||
/***
|
||||
* formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e.
|
||||
* cuts potential 0s off the end until minDecimals
|
||||
* @param value the value to be formatted
|
||||
* @param maxDecimals number of maximal scale
|
||||
* @param minDecimals number of minimal scale
|
||||
* @return value as String with decimals in the specified range
|
||||
*/
|
||||
public static String nDigitFormatDecimalRange(BigDecimal value, int maxDecimals, int minDecimals) {
|
||||
if ((maxDecimals<minDecimals)||(maxDecimals<0)||(minDecimals<0)) {
|
||||
throw new IllegalArgumentException("Invalid scale range provided");
|
||||
}
|
||||
int curDecimals=maxDecimals;
|
||||
while ( (curDecimals>minDecimals) && (value.setScale(curDecimals, RoundingMode.HALF_UP).compareTo(value.setScale(curDecimals-1, RoundingMode.HALF_UP))==0)) {
|
||||
curDecimals--;
|
||||
}
|
||||
return value.setScale(curDecimals, RoundingMode.HALF_UP).toPlainString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns a util.Date from a 102 String yyyymmdd in a node
|
||||
* @param node the node
|
||||
* @return a util.Date, or null, if not parseable
|
||||
*/
|
||||
public static Date tryDate(Node node) {
|
||||
final String nodeValue = XMLTools.getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return tryDate(nodeValue);
|
||||
}
|
||||
|
||||
/***
|
||||
* returns a util.Date from a 102 String yyyymmdd
|
||||
* @param toParse the string
|
||||
* @return a util.Date, or null, if not parseable
|
||||
*/
|
||||
public static Date tryDate(String toParse) {
|
||||
SimpleDateFormat formatter = null;
|
||||
if (toParse==null) {
|
||||
return null;
|
||||
}
|
||||
if (toParse.contains("-")) {
|
||||
// from ubl
|
||||
formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
} else {
|
||||
formatter = ZUGFeRDDateFormat.DATE.getFormatter();
|
||||
}
|
||||
try {
|
||||
return formatter.parse(toParse);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* relplaces some entities like < , > and & with their escaped pendant like &lt;
|
||||
* @param s the string
|
||||
* @return the "safe" string
|
||||
*/
|
||||
public static String encodeXML(CharSequence s) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
@@ -140,7 +220,20 @@ public class XMLTools extends XMLWriter {
|
||||
}
|
||||
|
||||
public static byte[] getBytesFromStream(InputStream fileinput) throws IOException {
|
||||
return IOUtils.toByteArray (fileinput);
|
||||
// Stream closing responsibility is with the caller
|
||||
return IOUtils.toByteArray(fileinput);
|
||||
}
|
||||
|
||||
|
||||
public static String trimOrNull(Node node) {
|
||||
if (node != null) {
|
||||
String textContent = node.getTextContent();
|
||||
if (textContent != null) {
|
||||
return textContent.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.ClassCode;
|
||||
|
||||
/**
|
||||
* A product classification that allows to describe a product. Classification codes are schemed by the available systems in UNTDID 7143.
|
||||
*/
|
||||
public interface IDesignatedProductClassification {
|
||||
/**
|
||||
* Classification code
|
||||
*
|
||||
* @return the classification code
|
||||
*/
|
||||
ClassCode getClassCode();
|
||||
|
||||
/**
|
||||
* an optional, human-readable description of the classifcation code
|
||||
*
|
||||
* @return the name or {@code null} if not set
|
||||
*/
|
||||
default String getClassName() { return null; }
|
||||
}
|
||||
@@ -30,11 +30,13 @@ package org.mustangproject.ZUGFeRD;
|
||||
* */
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.ReferencedDocument;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
|
||||
/***
|
||||
@@ -303,8 +305,7 @@ public interface IExportableTransaction {
|
||||
}
|
||||
|
||||
/**
|
||||
* get payment terms. if set, getPaymentTermDescription() and getDueDate() are
|
||||
* ignored
|
||||
* get payment terms. if set, getPaymentTermDescription() and getDueDate() are ignored
|
||||
*
|
||||
* @return the IZUGFeRDPaymentTerms of the invoice
|
||||
*/
|
||||
@@ -312,6 +313,18 @@ public interface IExportableTransaction {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getPaymentReference() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment terms for the EXTENDED profile (multiple terms are allowed)
|
||||
* @return
|
||||
*/
|
||||
default IZUGFeRDPaymentTerms[] getExtendedPaymentTerms() {
|
||||
return new IZUGFeRDPaymentTerms[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns if a rebate agreements exists
|
||||
*
|
||||
@@ -322,10 +335,20 @@ public interface IExportableTransaction {
|
||||
}
|
||||
|
||||
/**
|
||||
* get reference document number typically used for Invoice Corrections Will be
|
||||
* added as IncludedNote in comfort profile
|
||||
* get the rounding amount
|
||||
* (only to be usef for NL whose currency requires a rounding to 5ct)
|
||||
*
|
||||
* @return the ID of the document this document refers to
|
||||
* @return the Bigdecimal
|
||||
*/
|
||||
default BigDecimal getRoundingAmount() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get BuyerReference (BT-10) an identifier assigned by the buyer and used
|
||||
* for internal routing. Used for the Leitweg-ID.
|
||||
*
|
||||
* @return the BuyerReference of this document
|
||||
*/
|
||||
default String getReferenceNumber() {
|
||||
return null;
|
||||
@@ -415,13 +438,23 @@ public interface IExportableTransaction {
|
||||
*
|
||||
* @return the ID of the document
|
||||
*/
|
||||
@Deprecated
|
||||
default String getInvoiceReferencedDocumentID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default Date getInvoiceReferencedIssueDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for BG-3
|
||||
* @return list of documents
|
||||
*/
|
||||
default ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the issue timestamp of the BuyerOrderReferencedDocument, which sits in
|
||||
@@ -450,11 +483,19 @@ public interface IExportableTransaction {
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty delivery address
|
||||
*/
|
||||
|
||||
default IZUGFeRDExportableTradeParty getDeliveryAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* payee / payment receiver, if different from seller, ram:Payee (only supported for zf2)
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty payment receiver, if different from sellver
|
||||
*/
|
||||
default IZUGFeRDExportableTradeParty getPayee() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* specifies the document level delivery period, will be included in a
|
||||
* BillingSpecifiedPeriod element
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ public interface IZUGFeRDAllowanceCharge {
|
||||
*/
|
||||
default BigDecimal getPercent() {return null;}
|
||||
|
||||
/***
|
||||
* returns a basis the precentage is calculated from
|
||||
* @return null or the basis
|
||||
*/
|
||||
default BigDecimal getBasisAmount() {return null;}
|
||||
|
||||
/***
|
||||
* get a description for the allowance/charge
|
||||
* @return the description
|
||||
|
||||
@@ -28,7 +28,9 @@ package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.Item;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
@@ -41,7 +43,11 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
* item level discounts
|
||||
* @return array of the discounts on a single item
|
||||
*/
|
||||
@Deprecated
|
||||
default IZUGFeRDAllowanceCharge[] getItemAllowances() {
|
||||
if (getProduct()!=null) {
|
||||
return getProduct().getAllowances();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,7 +55,25 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
* item level price additions
|
||||
* @return array of the additional charges on the item
|
||||
*/
|
||||
@Deprecated
|
||||
default IZUGFeRDAllowanceCharge[] getItemCharges() {
|
||||
if (getProduct()!=null) {
|
||||
return getProduct().getCharges();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default IZUGFeRDAllowanceCharge[] getAllowances() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* item level price additions
|
||||
* @return array of the additional charges on the item
|
||||
*/
|
||||
@Deprecated
|
||||
default IZUGFeRDAllowanceCharge[] getCharges() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -90,7 +114,7 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
|
||||
/**
|
||||
* how many items units per price
|
||||
*
|
||||
*
|
||||
* @return item units per price
|
||||
*/
|
||||
default BigDecimal getBasisQuantity() {
|
||||
@@ -150,14 +174,35 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
default Date getDetailedDeliveryPeriodTo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* specify allowances amount for the line item total
|
||||
* get all (allowances and charges) SpecifiedTradeAllowanceCharges
|
||||
*
|
||||
* @return the sum of allowances for this item
|
||||
* @return the real item lecel SpecifiedTradeAllowanceCharges
|
||||
*/
|
||||
default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the line ID
|
||||
*/
|
||||
default String getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A grouping of business terms to indicate accounting-relevant free texts including a qualification of these.
|
||||
*
|
||||
* The information are written to the same xml nodes like {@link #getNotes()} but with explicit subjectCode.
|
||||
* @return list of the notes
|
||||
*/
|
||||
default List<IncludedNote> getNotesWithSubjectCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getAccountingReference() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,4 +165,30 @@ public interface IZUGFeRDExportableProduct {
|
||||
default HashMap<String, String> getAttributes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* product level discounts (AppliedTradeAllowanceCharge, will change net price)
|
||||
* @return array of the discounts on a single product
|
||||
*/
|
||||
default IZUGFeRDAllowanceCharge[] getAllowances() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* product level charges (AppliedTradeAllowanceCharge, will change net price)
|
||||
* @return array of the additional charges on the product
|
||||
*/
|
||||
default IZUGFeRDAllowanceCharge[] getCharges() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detailed information about the product
|
||||
*
|
||||
* @return an array containing the product classifications or {@code null} if not set
|
||||
*/
|
||||
default IDesignatedProductClassification[] getClassifications() { return null; }
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
|
||||
@Override
|
||||
default String getSettlementXML() {
|
||||
String xml = "<ram:SpecifiedTradeSettlementPaymentMeans>"
|
||||
+ "<ram:TypeCode>59</ram:TypeCode>"
|
||||
+ "<ram:Information>SEPA direct debit</ram:Information>"
|
||||
+ "<ram:TypeCode>" + XMLTools.encodeXML(getPaymentMeansCode()) + "</ram:TypeCode>"
|
||||
+ "<ram:Information>" + XMLTools.encodeXML(getPaymentMeansInformation()) + "</ram:Information>"
|
||||
+ "<ram:PayerPartyDebtorFinancialAccount>"
|
||||
+ "<ram:IBANID>" + XMLTools.encodeXML(getIBAN()) + "</ram:IBANID>"
|
||||
+ "</ram:PayerPartyDebtorFinancialAccount>";
|
||||
@@ -51,4 +51,14 @@ public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
|
||||
*/
|
||||
String getMandate();
|
||||
|
||||
/***
|
||||
* @return payment means code (BT-81 / UNTDID 4461)
|
||||
*/
|
||||
String getPaymentMeansCode();
|
||||
|
||||
/***
|
||||
* @return payment means description (BT-82) (optional)
|
||||
*/
|
||||
String getPaymentMeansInformation();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,30 +2,49 @@ package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
* the linecalculator does the math within an item line, and e.g. calculates quantity*price.
|
||||
* @see TransactionCalculator
|
||||
*/
|
||||
public class LineCalculator {
|
||||
private final BigDecimal price;
|
||||
private final BigDecimal priceGross;
|
||||
private final BigDecimal itemTotalNetAmount;
|
||||
private final BigDecimal itemTotalVATAmount;
|
||||
private BigDecimal allowance = BigDecimal.ZERO;
|
||||
private BigDecimal charge = BigDecimal.ZERO;
|
||||
private BigDecimal allowanceItemTotal = BigDecimal.ZERO;
|
||||
protected BigDecimal price;
|
||||
protected BigDecimal priceGross;
|
||||
protected BigDecimal itemTotalNetAmount;
|
||||
protected BigDecimal itemTotalVATAmount;
|
||||
protected BigDecimal lineAllowance = BigDecimal.ZERO;
|
||||
protected BigDecimal lineCharge = BigDecimal.ZERO;
|
||||
protected BigDecimal itemAllowance = BigDecimal.ZERO;
|
||||
protected BigDecimal itemCharge = BigDecimal.ZERO;
|
||||
protected BigDecimal allowanceItemTotal = BigDecimal.ZERO;
|
||||
|
||||
public LineCalculator(IZUGFeRDExportableItem currentItem) {
|
||||
|
||||
if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
|
||||
for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
|
||||
addAllowance(allowance.getTotalAmount(currentItem));
|
||||
BigDecimal factor=BigDecimal.ONE;
|
||||
BigDecimal singleAllowance=allowance.getTotalAmount(currentItem);
|
||||
addItemAllowance(singleAllowance);
|
||||
|
||||
if ((allowance.getPercent()!=null)&&(allowance.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
|
||||
factor=currentItem.getQuantity();
|
||||
}
|
||||
addAllowanceItemTotal(singleAllowance.multiply(factor));
|
||||
|
||||
}
|
||||
}
|
||||
if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
|
||||
for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
|
||||
addCharge(charge.getTotalAmount(currentItem));
|
||||
BigDecimal factor=BigDecimal.ONE;
|
||||
BigDecimal singleCharge=charge.getTotalAmount(currentItem);
|
||||
addItemCharge(singleCharge);
|
||||
if ((charge.getPercent()!=null)&&(charge.getPercent().compareTo(BigDecimal.ZERO)!=0)) {
|
||||
factor=currentItem.getQuantity();
|
||||
}
|
||||
subtractAllowanceItemTotal(singleCharge.multiply(factor));
|
||||
|
||||
}
|
||||
}
|
||||
if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) {
|
||||
@@ -34,16 +53,47 @@ public class LineCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal vatPercent = currentItem.getProduct().getVATPercent();
|
||||
if (vatPercent == null)
|
||||
BigDecimal vatPercent = null;
|
||||
if (currentItem.getProduct()!=null) {
|
||||
vatPercent = currentItem.getProduct().getVATPercent();
|
||||
}
|
||||
if (vatPercent == null) {
|
||||
vatPercent = BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100));
|
||||
priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159
|
||||
price = priceGross.subtract(allowance).add(charge);
|
||||
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity())
|
||||
.subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
|
||||
|
||||
BigDecimal quantity=BigDecimal.ZERO;
|
||||
if ((currentItem!=null)&&(currentItem.getQuantity()!=null)) {
|
||||
quantity=currentItem.getQuantity();
|
||||
}
|
||||
|
||||
price=currentItem.getPrice();
|
||||
priceGross=price;
|
||||
// price=price.subtract(itemAllowance).add(itemCharge);
|
||||
// BigDecimal delta=charge.subtract(allowanceItemTotal).subtract(allowance);
|
||||
// delta=delta.divide(currentItem.getQuantity(), 18, RoundingMode.HALF_UP);
|
||||
|
||||
BigDecimal delta=BigDecimal.ZERO;
|
||||
if (currentItem.getProduct().getAllowances()!=null) {
|
||||
for (IZUGFeRDAllowanceCharge ccaf:currentItem.getProduct().getAllowances()) {
|
||||
delta=delta.subtract(ccaf.getTotalAmount(currentItem));
|
||||
}
|
||||
}
|
||||
if (currentItem.getProduct().getCharges()!=null) {
|
||||
for (IZUGFeRDAllowanceCharge ccaf : currentItem.getProduct().getCharges()) {
|
||||
delta = delta.subtract(ccaf.getTotalAmount(currentItem));
|
||||
}
|
||||
}
|
||||
|
||||
price=price.add(delta);
|
||||
// Division/Zero occurred here.
|
||||
// Used the setScale only because that's also done in getBasisQuantity
|
||||
BigDecimal basisQuantity = currentItem.getBasisQuantity().compareTo(BigDecimal.ZERO) == 0
|
||||
? BigDecimal.ONE.setScale(4)
|
||||
: currentItem.getBasisQuantity();
|
||||
itemTotalNetAmount = quantity.multiply(price).divide(basisQuantity, 18, RoundingMode.HALF_UP)
|
||||
.add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);//.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
@@ -66,16 +116,27 @@ public class LineCalculator {
|
||||
return priceGross;
|
||||
}
|
||||
|
||||
public void addAllowance(BigDecimal b) {
|
||||
allowance = allowance.add(b);
|
||||
public void addLineAllowance(BigDecimal b) {
|
||||
lineAllowance = lineAllowance.add(b);
|
||||
}
|
||||
|
||||
public void addCharge(BigDecimal b) {
|
||||
charge = charge.add(b);
|
||||
public void addLineCharge(BigDecimal b) {
|
||||
lineCharge = lineCharge.add(b);
|
||||
}
|
||||
|
||||
public void addItemAllowance(BigDecimal b) {
|
||||
itemAllowance = itemAllowance.add(b);
|
||||
}
|
||||
|
||||
public void addItemCharge(BigDecimal b) {
|
||||
itemCharge = itemCharge.add(b);
|
||||
}
|
||||
|
||||
public void addAllowanceItemTotal(BigDecimal b) {
|
||||
allowanceItemTotal = allowanceItemTotal.add(b);
|
||||
}
|
||||
public void subtractAllowanceItemTotal(BigDecimal b) {
|
||||
allowanceItemTotal = allowanceItemTotal.subtract(b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.Map;
|
||||
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.ReferencedDocument;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
@@ -459,6 +460,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
|
||||
xml += "<ram:InvoiceReferencedDocument>"
|
||||
+ "<ram:IssuerAssignedID>"
|
||||
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
|
||||
if (doc.getFormattedIssueDateTime() != null) {
|
||||
xml += "<ram:FormattedIssueDateTime>"
|
||||
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
|
||||
+ "</ram:FormattedIssueDateTime>";
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
}
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeSettlement>";
|
||||
// + " <IncludedSupplyChainTradeLineItem>"
|
||||
@@ -492,36 +506,44 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
|
||||
private String buildPaymentTermsXml() {
|
||||
|
||||
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
|
||||
if (paymentTerms == null) {
|
||||
final IZUGFeRDPaymentTerms[] paymentTerms = trans.getExtendedPaymentTerms();
|
||||
|
||||
String paymentTermsXml = "";
|
||||
if (paymentTerms == null || paymentTerms.length == 0) {
|
||||
return "";
|
||||
}
|
||||
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
|
||||
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
|
||||
if (discountTerms != null) {
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
for (IZUGFeRDPaymentTerms pt : paymentTerms)
|
||||
{
|
||||
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
|
||||
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
|
||||
if (discountTerms != null)
|
||||
{
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
+ "</ram:CalculationPercent>";
|
||||
|
||||
if (discountTerms.getBaseDate() != null) {
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
if (discountTerms.getBaseDate() != null)
|
||||
{
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
return paymentTermsXml;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
// Copied from PDFBox preflight 2.0.x
|
||||
// Copied from PDFBox preflight 2.0.x
|
||||
final class ByteArrayDataSource implements DataSource
|
||||
{
|
||||
private ByteArrayOutputStream data;
|
||||
private String type = null;
|
||||
private String name = null;
|
||||
|
||||
public ByteArrayDataSource (InputStream is) throws IOException
|
||||
public ByteArrayDataSource (final InputStream is) throws IOException
|
||||
{
|
||||
data = new ByteArrayOutputStream ();
|
||||
IOUtils.copy (is, data);
|
||||
@@ -36,7 +36,7 @@ final class ByteArrayDataSource implements DataSource
|
||||
* @param type
|
||||
* the type to set
|
||||
*/
|
||||
public void setType (String type)
|
||||
public void setType (final String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ final class ByteArrayDataSource implements DataSource
|
||||
* @param name
|
||||
* the name to set
|
||||
*/
|
||||
public void setName (String name)
|
||||
public void setName (final String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
@@ -70,12 +70,13 @@ final class ByteArrayDataSource implements DataSource
|
||||
// Try to create an API similar to the 2.x one
|
||||
final class PreflightParserHelper
|
||||
{
|
||||
private static File createTmpFile (InputStream input) throws IOException
|
||||
private static File createTmpFile (final InputStream input) throws IOException
|
||||
{
|
||||
FileOutputStream fos = null;
|
||||
try
|
||||
{
|
||||
File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
|
||||
final File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
|
||||
tmpFile.deleteOnExit ();
|
||||
fos = new FileOutputStream (tmpFile);
|
||||
IOUtils.copy (input, fos);
|
||||
return tmpFile;
|
||||
@@ -87,7 +88,7 @@ final class PreflightParserHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static PreflightParser createPreflightParser (DataSource dataSource) throws IOException
|
||||
public static PreflightParser createPreflightParser (final DataSource dataSource) throws IOException
|
||||
{
|
||||
return new PreflightParser (createTmpFile (dataSource.getInputStream ()));
|
||||
}
|
||||
|
||||
@@ -4,13 +4,17 @@ import static java.math.BigDecimal.ZERO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/***
|
||||
* The Transactioncalculator e.g. adds the line totals and applies VAT on whole
|
||||
* invoices
|
||||
*
|
||||
*
|
||||
* @see LineCalculator
|
||||
*/
|
||||
public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
@@ -27,7 +31,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
/***
|
||||
* if something had already been paid in advance, this will get it from the
|
||||
* transaction
|
||||
*
|
||||
*
|
||||
* @return prepaid amount
|
||||
*/
|
||||
protected BigDecimal getTotalPrepaid() {
|
||||
@@ -39,21 +43,21 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
}
|
||||
|
||||
/***
|
||||
* the invoice total with VAT, corrected by prepaid amount, allowances and
|
||||
* charges
|
||||
*
|
||||
* the invoice total with VAT, allowances and
|
||||
* charges, WITHOUT considering prepaid amount
|
||||
*
|
||||
* @return the invoice total including taxes
|
||||
*/
|
||||
public BigDecimal getGrandTotal() {
|
||||
|
||||
final BigDecimal res = getTaxBasis();
|
||||
BigDecimal basis = getTaxBasis();
|
||||
return getVATPercentAmountMap().values().stream().map(VATAmount::getCalculated)
|
||||
.map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(res);
|
||||
.map(p -> p.setScale(2, RoundingMode.HALF_UP)).reduce(BigDecimal.ZERO, BigDecimal::add).add(basis);
|
||||
}
|
||||
|
||||
/***
|
||||
* returns total of charges for this tax rate
|
||||
*
|
||||
*
|
||||
* @param percent a specific rate, or null for any rate
|
||||
* @return the total amount
|
||||
*/
|
||||
@@ -62,6 +66,24 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
return sumAllowanceCharge(percent, charges);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns information about every tax that is involved in the current transaction.
|
||||
*
|
||||
* @return transaction taxes.
|
||||
*/
|
||||
public Set<VATAmount> getTaxDetails() {
|
||||
return getVATPercentAmountMap().entrySet().stream()
|
||||
.map(entry ->
|
||||
new VATAmount(
|
||||
entry.getValue().getBasis(),
|
||||
entry.getValue().getCalculated(),
|
||||
entry.getValue().getCategoryCode()
|
||||
).setApplicablePercent(entry.getKey())
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
|
||||
BigDecimal res = BigDecimal.ZERO;
|
||||
if ((charges != null) && (charges.length > 0)) {
|
||||
@@ -77,7 +99,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
/***
|
||||
* returns a (potentially concatenated) string of charge reasons, or "Charges"
|
||||
* if none are defined
|
||||
*
|
||||
*
|
||||
* @param percent a specific rate, or null for any rate
|
||||
* @return the space separated String
|
||||
*/
|
||||
@@ -91,23 +113,20 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
}
|
||||
|
||||
private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
|
||||
String res = " ";
|
||||
if ((charges != null) && (charges.length > 0)) {
|
||||
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
|
||||
if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)
|
||||
&& currentCharge.getReason() != null) {
|
||||
res += currentCharge.getReason() + " ";
|
||||
}
|
||||
}
|
||||
if (charges == null) {
|
||||
return "";
|
||||
}
|
||||
res = res.substring(0, res.length() - 1);
|
||||
return res;
|
||||
return Arrays.stream(charges)
|
||||
.filter(currentCharge -> (percent == null || currentCharge.getTaxPercent().compareTo(percent) == 0))
|
||||
.map(IZUGFeRDAllowanceCharge::getReason)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
/***
|
||||
* returns a (potentially concatenated) string of allowance reasons, or
|
||||
* "Allowances", if none are defined
|
||||
*
|
||||
*
|
||||
* @param percent a specific rate, or null for any rate
|
||||
* @return the space separated String
|
||||
*/
|
||||
@@ -122,7 +141,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
|
||||
/***
|
||||
* returns total of allowances for this tax rate
|
||||
*
|
||||
*
|
||||
* @param percent a specific rate, or null for any rate
|
||||
* @return the total amount
|
||||
*/
|
||||
@@ -134,25 +153,25 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
/***
|
||||
* returns the total net value of all items, without document level
|
||||
* charges/allowances
|
||||
*
|
||||
*
|
||||
* @return item sum
|
||||
*/
|
||||
protected BigDecimal getTotal() {
|
||||
BigDecimal dec = Stream.of(trans.getZFItems()).map(LineCalculator::new)
|
||||
.map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add);
|
||||
.map(LineCalculator::getItemTotalNetAmount).reduce(ZERO, BigDecimal::add);
|
||||
return dec;
|
||||
}
|
||||
|
||||
/***
|
||||
* returns the total net value of the invoice, including charges/allowances on
|
||||
* document level
|
||||
*
|
||||
*
|
||||
* @return item sum +- charges/allowances
|
||||
*/
|
||||
protected BigDecimal getTaxBasis() {
|
||||
public BigDecimal getTaxBasis() {
|
||||
return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP))
|
||||
.subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
.subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,11 +186,18 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
final String vatDueDateTypeCode = trans.getVATDueDateTypeCode();
|
||||
|
||||
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
|
||||
BigDecimal percent = currentItem.getProduct().getVATPercent();
|
||||
BigDecimal percent = null;
|
||||
if (currentItem.getProduct() != null) {
|
||||
percent = currentItem.getProduct().getVATPercent();
|
||||
}
|
||||
if (percent != null) {
|
||||
LineCalculator lc = new LineCalculator(currentItem);
|
||||
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
|
||||
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
|
||||
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
|
||||
String reasonText = currentItem.getProduct().getTaxExemptionReason();
|
||||
if (reasonText != null) {
|
||||
itemVATAmount.setVatExemptionReasonText(reasonText);
|
||||
}
|
||||
VATAmount current = hm.get(percent.stripTrailingZeros());
|
||||
if (current == null) {
|
||||
hm.put(percent.stripTrailingZeros(), itemVATAmount);
|
||||
@@ -189,8 +215,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
|
||||
if (theAmount == null) {
|
||||
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
|
||||
currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S",
|
||||
vatDueDateTypeCode);
|
||||
currentCharge.getCategoryCode() != null ? currentCharge.getCategoryCode() : "S",
|
||||
vatDueDateTypeCode);
|
||||
}
|
||||
theAmount.setBasis(theAmount.getBasis().add(currentCharge.getTotalAmount(this)));
|
||||
BigDecimal factor = taxPercent.divide(new BigDecimal(100));
|
||||
@@ -207,8 +233,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
VATAmount theAmount = hm.get(taxPercent.stripTrailingZeros());
|
||||
if (theAmount == null) {
|
||||
theAmount = new VATAmount(BigDecimal.ZERO, BigDecimal.ZERO,
|
||||
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
|
||||
vatDueDateTypeCode);
|
||||
currentAllowance.getCategoryCode() != null ? currentAllowance.getCategoryCode() : "S",
|
||||
vatDueDateTypeCode);
|
||||
}
|
||||
theAmount.setBasis(theAmount.getBasis().subtract(currentAllowance.getTotalAmount(this)));
|
||||
BigDecimal factor = taxPercent.divide(new BigDecimal(100));
|
||||
@@ -235,4 +261,11 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
return getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
public BigDecimal getDuePayable() {
|
||||
BigDecimal res = getGrandTotal().subtract(getTotalPrepaid());
|
||||
if (trans.getRoundingAmount() != null) {
|
||||
res = res.add(trans.getRoundingAmount());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,16 @@ import java.math.RoundingMode;
|
||||
*/
|
||||
public class VATAmount {
|
||||
|
||||
|
||||
protected BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
protected String categoryCode;
|
||||
|
||||
protected String vatExemptionReasonText;
|
||||
|
||||
protected String dueDateTypeCode;
|
||||
|
||||
|
||||
public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode) {
|
||||
super();
|
||||
this.basis = basis;
|
||||
@@ -48,35 +58,41 @@ public class VATAmount {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
}
|
||||
|
||||
BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
String categoryCode;
|
||||
|
||||
String dueDateTypeCode;
|
||||
|
||||
public BigDecimal getApplicablePercent() {
|
||||
return applicablePercent;
|
||||
}
|
||||
|
||||
public void setApplicablePercent(BigDecimal applicablePercent) {
|
||||
public VATAmount setApplicablePercent(BigDecimal applicablePercent) {
|
||||
|
||||
this.applicablePercent = applicablePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getBasis() {
|
||||
return basis;
|
||||
}
|
||||
|
||||
public void setBasis(BigDecimal basis) {
|
||||
public VATAmount setBasis(BigDecimal basis) {
|
||||
this.basis = basis.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getCalculated() {
|
||||
return calculated;
|
||||
}
|
||||
|
||||
public void setCalculated(BigDecimal calculated) {
|
||||
public VATAmount setCalculated(BigDecimal calculated) {
|
||||
this.calculated = calculated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVatExemptionReasonText() {
|
||||
return vatExemptionReasonText;
|
||||
}
|
||||
|
||||
public VATAmount setVatExemptionReasonText(String theText) {
|
||||
this.vatExemptionReasonText = theText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,34 +108,38 @@ public class VATAmount {
|
||||
/**
|
||||
* @param documentCode as String
|
||||
* @deprecated Use {@link #setCategoryCode(String)} instead
|
||||
* @return fluent setter
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDocumentCode(String documentCode) {
|
||||
public VATAmount setDocumentCode(String documentCode) {
|
||||
this.categoryCode = documentCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCategoryCode() {
|
||||
return categoryCode;
|
||||
}
|
||||
|
||||
public void setCategoryCode(String categoryCode) {
|
||||
public VATAmount setCategoryCode(String categoryCode) {
|
||||
this.categoryCode = categoryCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDueDateTypeCode() {
|
||||
return dueDateTypeCode;
|
||||
}
|
||||
|
||||
public void setDueDateTypeCode(String dueDateTypeCode) {
|
||||
public VATAmount setDueDateTypeCode(String dueDateTypeCode) {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VATAmount add(VATAmount v) {
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText() != null ? v.getVatExemptionReasonText(): this.vatExemptionReasonText);
|
||||
}
|
||||
|
||||
public VATAmount subtract(VATAmount v) {
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import org.apache.fop.apps.*;
|
||||
import org.apache.fop.apps.io.ResourceResolverFactory;
|
||||
import org.apache.fop.configuration.Configuration;
|
||||
import org.apache.fop.configuration.ConfigurationException;
|
||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||
import org.apache.xmlgraphics.util.MimeConstants;
|
||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ValidationLogVisualizer {
|
||||
public enum Language {
|
||||
EN,
|
||||
FR,
|
||||
DE
|
||||
}
|
||||
|
||||
static final ClassLoader CLASS_LOADER = ValidationLogVisualizer.class.getClassLoader();
|
||||
private static final String RESOURCE_PATH = "";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationLogVisualizer.class);
|
||||
|
||||
private TransformerFactory mFactory = null;
|
||||
private Templates mXsltPDFTemplate = null;
|
||||
|
||||
|
||||
public ValidationLogVisualizer() {
|
||||
mFactory = new net.sf.saxon.TransformerFactoryImpl();
|
||||
// fact = TransformerFactory.newInstance();
|
||||
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
|
||||
}
|
||||
|
||||
protected void applyXSLTToPDF(final String xmlContent, final OutputStream PDFOutstream)
|
||||
throws TransformerException {
|
||||
Transformer transformer = mXsltPDFTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(new StringReader(xmlContent)), new StreamResult(PDFOutstream));
|
||||
}
|
||||
|
||||
protected String toFOP(final String xmlContent)
|
||||
throws TransformerException {
|
||||
|
||||
try {
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/result-pdf.xsl")));
|
||||
}
|
||||
} catch (TransformerConfigurationException ex) {
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
|
||||
applyXSLTToPDF(xmlContent, baos);
|
||||
|
||||
} catch (Exception e1) {
|
||||
LOGGER.error("Failed to create PDF", e1);
|
||||
}
|
||||
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public byte[] createPDFBytes(String xmlLogfileContent) {
|
||||
|
||||
// the writing part
|
||||
|
||||
String result = null;
|
||||
|
||||
/* remove file endings so that tests can also pass after checking
|
||||
out from git with arbitrary options (which may include CSRF changes)
|
||||
*/
|
||||
try {
|
||||
result = this.toFOP(xmlLogfileContent);
|
||||
} catch ( TransformerException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
|
||||
Configuration cfg = null;
|
||||
try {
|
||||
cfg = cfgBuilder.build(CLASS_LOADER.getResourceAsStream("fop-config.xconf"));
|
||||
} catch (ConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
FopFactoryBuilder builder = new FopFactoryBuilder(new File(".").toURI(), new ClasspathResolverURIAdapter()).setConfiguration(cfg);
|
||||
// Step 1: Construct a FopFactory by specifying a reference to the configuration file
|
||||
// (reuse if you plan to render multiple documents!)
|
||||
|
||||
FopFactory fopFactory = builder.build();
|
||||
|
||||
fopFactory.getFontManager().setResourceResolver(
|
||||
ResourceResolverFactory.createInternalResourceResolver(
|
||||
new File(".").toURI(),
|
||||
new ClasspathResolverURIAdapter()));
|
||||
|
||||
FOUserAgent userAgent = fopFactory.newFOUserAgent();
|
||||
|
||||
userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-3b");
|
||||
|
||||
// Step 2: Set up output stream.
|
||||
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (OutputStream out = new BufferedOutputStream(baos)) {
|
||||
|
||||
// Step 3: Construct fop with desired output format
|
||||
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
|
||||
|
||||
// Step 4: Setup JAXP using identity transformer
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
|
||||
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
Transformer transformer = factory.newTransformer(); // identity transformer
|
||||
|
||||
// Step 5: Setup input and output for XSLT transformation
|
||||
// Setup input stream
|
||||
Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
// Resulting SAX events (the generated FO) must be piped through to FOP
|
||||
Result res = new SAXResult(fop.getDefaultHandler());
|
||||
|
||||
// Step 6: Start XSLT transformation and FOP processing
|
||||
transformer.transform(src, res);
|
||||
|
||||
} catch (FOPException | IOException | TransformerException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public byte[] toPDF(String xmlLogfileContent) {
|
||||
return createPDFBytes(xmlLogfileContent);
|
||||
}
|
||||
|
||||
public void toPDF(String xmlLogfileContent, String pdfFilename) {
|
||||
byte[] pdfData = createPDFBytes(xmlLogfileContent);
|
||||
try (FileOutputStream fos = new FileOutputStream(pdfFilename)) {
|
||||
fos.write(pdfData);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write PDF to file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClasspathResourceURIResolver implements URIResolver {
|
||||
ClasspathResourceURIResolver() {
|
||||
// Do nothing, just prevents synthetic access warning.
|
||||
}
|
||||
|
||||
@Override
|
||||
public Source resolve(String href, String base) throws TransformerException {
|
||||
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,44 +433,57 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
|
||||
}
|
||||
|
||||
private String buildPaymentTermsXml() {
|
||||
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
|
||||
final IZUGFeRDPaymentTerms[] paymentTerms = trans.getExtendedPaymentTerms();
|
||||
|
||||
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
|
||||
final Date dueDate = paymentTerms.getDueDate();
|
||||
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
|
||||
throw new IllegalStateException(
|
||||
String paymentTermsXml = "";
|
||||
if (paymentTerms == null || paymentTerms.length == 0) {
|
||||
return paymentTermsXml;
|
||||
}
|
||||
|
||||
for (IZUGFeRDPaymentTerms pt : paymentTerms)
|
||||
{
|
||||
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
|
||||
final Date dueDate = pt.getDueDate();
|
||||
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null)
|
||||
{
|
||||
throw new IllegalStateException(
|
||||
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
|
||||
}
|
||||
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
|
||||
if (dueDate != null) {
|
||||
paymentTermsXml += "<ram:DueDateDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(dueDate);
|
||||
paymentTermsXml += "</ram:DueDateDateTime>";
|
||||
}
|
||||
|
||||
if (discountTerms != null) {
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
+ "</ram:CalculationPercent>";
|
||||
|
||||
if (discountTerms.getBaseDate() != null) {
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
|
||||
}
|
||||
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
|
||||
if (dueDate != null)
|
||||
{
|
||||
paymentTermsXml += "<ram:DueDateDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(dueDate);
|
||||
paymentTermsXml += "</ram:DueDateDateTime>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
}
|
||||
if (discountTerms != null)
|
||||
{
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
+ "</ram:CalculationPercent>";
|
||||
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
if (discountTerms.getBaseDate() != null)
|
||||
{
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
}
|
||||
return paymentTermsXml;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,15 @@ import org.dom4j.io.OutputFormat;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.ReferencedDocument;
|
||||
import org.mustangproject.XMLTools;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (ZUGFeRD2PullProvider.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRD2PullProvider.class);
|
||||
|
||||
protected byte[] zugferdData;
|
||||
protected IExportableTransaction trans;
|
||||
@@ -76,11 +78,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String priceFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
// 18 decimals are max for price and qty due to xml restrictions,
|
||||
// see Chapter 3.2.3 of https://www.w3.org/TR/xmlschema-2/
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
protected String quantityFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,7 +97,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
try {
|
||||
document = DocumentHelper.parseText(new String(zugferdData));
|
||||
} catch (final DocumentException e1) {
|
||||
LOGGER.error ("Failed to parse ZUGFeRD data", e1);
|
||||
LOGGER.error("Failed to parse ZUGFeRD data", e1);
|
||||
}
|
||||
try {
|
||||
final OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
@@ -103,7 +107,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
res = sw.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error ("Failed to write ZUGFeRD data", e);
|
||||
LOGGER.error("Failed to write ZUGFeRD data", e);
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -137,14 +141,25 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
xml += "<ram:GlobalID schemeID=\"" + XMLTools.encodeXML(party.getGlobalIDScheme()) + "\">"
|
||||
+ XMLTools.encodeXML(party.getGlobalID()) + "</ram:GlobalID>";
|
||||
}
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
if (party.getName() != null && !party.getName().isEmpty()) {
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
}
|
||||
if (party.getDescription() != null) {
|
||||
xml += "<ram:Description>" + XMLTools.encodeXML(party.getDescription()) + "</ram:Description>";
|
||||
}
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
if (profile == Profiles.getByName("Minimum")) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
} else {
|
||||
String schemeAttribute = "";
|
||||
if ((party.getLegalOrganisation().getSchemedID().getScheme() != null) && (party.getLegalOrganisation().getSchemedID().getScheme().length() > 0)) {
|
||||
schemeAttribute = "schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\"";
|
||||
|
||||
}
|
||||
xml += "<ram:ID " + schemeAttribute + ">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
}
|
||||
if (party.getLegalOrganisation().getTradingBusinessName() != null) {
|
||||
xml += "<ram:TradingBusinessName>" + XMLTools.encodeXML(party.getLegalOrganisation().getTradingBusinessName()) + "</ram:TradingBusinessName>";
|
||||
@@ -155,11 +170,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("EN16931") || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
xml += "<ram:DefinedTradeContact>";
|
||||
if (party.getContact().getName() != null) {
|
||||
xml += "<ram:PersonName>" + XMLTools.encodeXML(party.getContact().getName())
|
||||
xml += "<ram:PersonName>"
|
||||
+ XMLTools.encodeXML(party.getContact().getName())
|
||||
+ "</ram:PersonName>";
|
||||
}
|
||||
if (party.getContact().getPhone() != null) {
|
||||
|
||||
xml += "<ram:TelephoneUniversalCommunication><ram:CompleteNumber>"
|
||||
+ XMLTools.encodeXML(party.getContact().getPhone()) + "</ram:CompleteNumber>"
|
||||
+ "</ram:TelephoneUniversalCommunication>";
|
||||
@@ -200,7 +215,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "</ram:CityName>";
|
||||
}
|
||||
|
||||
//country IS mandatory
|
||||
//country IS mandatory
|
||||
xml += "<ram:CountryID>" + XMLTools.encodeXML(party.getCountry())
|
||||
+ "</ram:CountryID>"
|
||||
+ "</ram:PostalTradeAddress>";
|
||||
@@ -228,6 +243,30 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
}
|
||||
|
||||
protected String getTradePartyPayeeAsXML(IZUGFeRDExportableTradeParty party) {
|
||||
String xml = "";
|
||||
// According EN16931 either GlobalID or seller assigned ID might be present for a Payee
|
||||
if (party.getID() != null) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getID()) + "</ram:ID>";
|
||||
}
|
||||
if ((party.getGlobalIDScheme() != null) && (party.getGlobalID() != null)) {
|
||||
xml += "<ram:GlobalID schemeID=\"" + XMLTools.encodeXML(party.getGlobalIDScheme()) + "\">"
|
||||
+ XMLTools.encodeXML(party.getGlobalID())
|
||||
+ "</ram:GlobalID>";
|
||||
}
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
xml += "</ram:SpecifiedLegalOrganization>";
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns the XML for a charge or allowance on item level
|
||||
@@ -247,20 +286,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
|
||||
// only in extended profile
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung") || profile == Profiles.getByName("EN16931"))) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
|
||||
if (allowance.getReasonCode() != null) {
|
||||
// only in XRechnung profile
|
||||
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
final String allowanceChargeStr = "<ram:AppliedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
|
||||
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
|
||||
"<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
|
||||
reason +
|
||||
reasonCode +
|
||||
reason +
|
||||
"</ram:AppliedTradeAllowanceCharge>";
|
||||
return allowanceChargeStr;
|
||||
}
|
||||
@@ -283,20 +321,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
|
||||
// only in extended profile
|
||||
boolean isEN16931=(profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931"));
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
|
||||
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("Extended") || isEN16931)) {
|
||||
// only in XRechnung profile
|
||||
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
final String itemTotalAllowanceChargeStr = "<ram:SpecifiedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
|
||||
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
|
||||
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
|
||||
reason +
|
||||
reasonCode +
|
||||
reason +
|
||||
"</ram:SpecifiedTradeAllowanceCharge>";
|
||||
return itemTotalAllowanceChargeStr;
|
||||
}
|
||||
@@ -306,11 +344,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
this.trans = trans;
|
||||
this.calc = new TransactionCalculator(trans);
|
||||
|
||||
boolean hasDueDate = false;
|
||||
boolean hasDueDate = trans.getDueDate() != null;
|
||||
final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
|
||||
|
||||
String exemptionReason = "";
|
||||
|
||||
if (trans.getPaymentTermDescription() != null) {
|
||||
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription());
|
||||
}
|
||||
@@ -325,7 +361,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
paymentTermsDescription += discount.getAsXRechnung();
|
||||
}
|
||||
} else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) {
|
||||
if ( trans.getDueDate() != null ) {
|
||||
if (trans.getDueDate() != null) {
|
||||
paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate());
|
||||
}
|
||||
}
|
||||
@@ -360,27 +396,27 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "</ram:GuidelineSpecifiedDocumentContextParameter>"
|
||||
+ "</rsm:ExchangedDocumentContext>"
|
||||
+ "<rsm:ExchangedDocument>"
|
||||
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>"
|
||||
// + "<ram:Name>RECHNUNG</ram:Name>"
|
||||
// + "<ram:TypeCode>380</ram:TypeCode>"
|
||||
+ "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
|
||||
+ "<ram:IssueDateTime>"
|
||||
+ DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
|
||||
+ buildNotes(trans)
|
||||
|
||||
+ "</rsm:ExchangedDocument>"
|
||||
+ "<rsm:SupplyChainTradeTransaction>";
|
||||
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>";
|
||||
if (profile == Profiles.getByName("Extended") && trans.getDocumentName() != null) {
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(trans.getDocumentName()) + "</ram:Name>";
|
||||
}
|
||||
xml += "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
|
||||
+ "<ram:IssueDateTime>" + DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
|
||||
+ buildNotes(trans)
|
||||
+ "</rsm:ExchangedDocument>"
|
||||
+ "<rsm:SupplyChainTradeTransaction>";
|
||||
int lineID = 0;
|
||||
for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
|
||||
lineID++;
|
||||
if (currentItem.getProduct().getTaxExemptionReason() != null) {
|
||||
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
|
||||
String lineIDStr = Integer.toString(lineID);
|
||||
if (currentItem.getId() != null) {
|
||||
lineIDStr = currentItem.getId();
|
||||
}
|
||||
final LineCalculator lc = new LineCalculator(currentItem);
|
||||
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
|
||||
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
|
||||
"<ram:AssociatedDocumentLineDocument>"
|
||||
+ "<ram:LineID>" + lineID + "</ram:LineID>"
|
||||
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>"
|
||||
+ buildItemNotes(currentItem)
|
||||
+ "</ram:AssociatedDocumentLineDocument>"
|
||||
|
||||
@@ -398,33 +434,52 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "</ram:BuyerAssignedID>";
|
||||
}
|
||||
String allowanceChargeStr = "";
|
||||
if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
|
||||
if (currentItem.getProduct().getAllowances() != null && currentItem.getProduct().getAllowances().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge allowance : currentItem.getProduct().getAllowances()) {
|
||||
allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem);
|
||||
}
|
||||
}
|
||||
if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
|
||||
if (currentItem.getProduct().getCharges() != null && currentItem.getProduct().getCharges().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge charge : currentItem.getProduct().getCharges()) {
|
||||
allowanceChargeStr += getAllowanceChargeStr(charge, currentItem);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
String itemTotalAllowanceChargeStr = "";
|
||||
if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getItemTotalAllowances()) {
|
||||
if (currentItem.getAllowances() != null && currentItem.getAllowances().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getAllowances()) {
|
||||
itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalAllowance, currentItem);
|
||||
}
|
||||
}
|
||||
if (currentItem.getCharges() != null && currentItem.getCharges().length > 0) {
|
||||
for (final IZUGFeRDAllowanceCharge itemTotalCharges : currentItem.getCharges()) {
|
||||
itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalCharges, currentItem);
|
||||
}
|
||||
}
|
||||
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(currentItem.getProduct().getName()) + "</ram:Name>";
|
||||
if (currentItem.getProduct().getDescription().length() > 0) {
|
||||
if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) {
|
||||
xml += "<ram:Description>" +
|
||||
XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
|
||||
"</ram:Description>";
|
||||
}
|
||||
if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
|
||||
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
|
||||
xml += "<ram:DesignatedProductClassification>"
|
||||
+ "<ram:ClassCode listID=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
|
||||
if (classification.getClassCode().getListVersionID() != null) {
|
||||
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
|
||||
}
|
||||
xml += ">" + classification.getClassCode().getCode() + "</ram:ClassCode>";
|
||||
if (classification.getClassName() != null) {
|
||||
xml += "<ram:ClassName>" + XMLTools.encodeXML(classification.getClassName()) + "</ram:ClassName>";
|
||||
}
|
||||
xml += "</ram:DesignatedProductClassification>";
|
||||
}
|
||||
}
|
||||
if (currentItem.getProduct().getAttributes() != null) {
|
||||
for ( Entry<String, String> entry : currentItem.getProduct().getAttributes().entrySet() ) {
|
||||
for (Entry<String, String> entry : currentItem.getProduct().getAttributes().entrySet()) {
|
||||
xml += "<ram:ApplicableProductCharacteristic>" +
|
||||
"<ram:Description>" + XMLTools.encodeXML(entry.getKey()) + "</ram:Description>" +
|
||||
"<ram:Value>" + XMLTools.encodeXML(entry.getValue()) + "</ram:Value>" +
|
||||
@@ -433,8 +488,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
if (currentItem.getProduct().getCountryOfOrigin() != null) {
|
||||
xml += "<ram:OriginTradeCountry><ram:ID>" +
|
||||
XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) +
|
||||
"</ram:ID></ram:OriginTradeCountry>";
|
||||
XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) +
|
||||
"</ram:ID></ram:OriginTradeCountry>";
|
||||
}
|
||||
xml += "</ram:SpecifiedTradeProduct>"
|
||||
|
||||
@@ -489,12 +544,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "</ram:SpecifiedLineTradeDelivery>"
|
||||
+ "<ram:SpecifiedLineTradeSettlement>"
|
||||
+ "<ram:ApplicableTradeTax>"
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>"
|
||||
+ exemptionReason
|
||||
+ "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>"
|
||||
+ "<ram:RateApplicablePercent>"
|
||||
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>"
|
||||
+ "</ram:ApplicableTradeTax>";
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>";
|
||||
if (currentItem.getProduct().getTaxExemptionReason() != null) {
|
||||
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
|
||||
}
|
||||
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
|
||||
if (!currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
|
||||
xml += "<ram:RateApplicablePercent>"
|
||||
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>";
|
||||
}
|
||||
xml += "</ram:ApplicableTradeTax>";
|
||||
|
||||
if ((currentItem.getDetailedDeliveryPeriodFrom() != null) || (currentItem.getDetailedDeliveryPeriodTo() != null)) {
|
||||
xml += "<ram:BillingSpecifiedPeriod>";
|
||||
if (currentItem.getDetailedDeliveryPeriodFrom() != null) {
|
||||
@@ -506,7 +566,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
xml += "</ram:BillingSpecifiedPeriod>";
|
||||
}
|
||||
|
||||
xml += itemTotalAllowanceChargeStr;
|
||||
// item charges/allowances
|
||||
if (!itemTotalAllowanceChargeStr.isEmpty()) {
|
||||
xml += itemTotalAllowanceChargeStr ;
|
||||
}
|
||||
|
||||
xml += "<ram:SpecifiedTradeSettlementLineMonetarySummation>"
|
||||
+ "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
|
||||
@@ -619,13 +682,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeDelivery>";
|
||||
xml += "<ram:ApplicableHeaderTradeSettlement>";
|
||||
|
||||
if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:CreditorReferenceID>" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + "</ram:CreditorReferenceID>";
|
||||
}
|
||||
if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>";
|
||||
if ((trans.getPaymentReference() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getPaymentReference()) + "</ram:PaymentReference>";
|
||||
}
|
||||
xml += "<ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>";
|
||||
if (this.trans.getPayee() != null) {
|
||||
xml += "<ram:PayeeTradeParty>" +
|
||||
getTradePartyPayeeAsXML(this.trans.getPayee()) +
|
||||
"</ram:PayeeTradeParty>";
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlementPayment() != null) {
|
||||
for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
|
||||
@@ -661,17 +730,25 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
|
||||
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
|
||||
if (getProfile() != Profiles.getByName("Minimum")) {
|
||||
String exemptionReasonTextXML = "";
|
||||
if ((displayExemptionReason) && (amount.getVatExemptionReasonText() != null)) {
|
||||
exemptionReasonTextXML = "<ram:ExemptionReason>" + XMLTools.encodeXML(amount.getVatExemptionReasonText()) + "</ram:ExemptionReason>";
|
||||
|
||||
}
|
||||
|
||||
xml += "<ram:ApplicableTradeTax>"
|
||||
+ "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated())
|
||||
+ "</ram:CalculatedAmount>" //currencyID=\"EUR\"
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>"
|
||||
+ (displayExemptionReason ? exemptionReason : "")
|
||||
+ exemptionReasonTextXML
|
||||
+ "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
|
||||
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
|
||||
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
|
||||
+ "<ram:RateApplicablePercent>"
|
||||
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent></ram:ApplicableTradeTax>";
|
||||
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "");
|
||||
if (!amountCategoryCode.equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
|
||||
xml += "<ram:RateApplicablePercent>"
|
||||
+ vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>";
|
||||
}
|
||||
xml += "</ram:ApplicableTradeTax>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -687,23 +764,23 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for(IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
|
||||
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
|
||||
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>true</udt:Indicator>" +
|
||||
"</ram:ChargeIndicator>" +
|
||||
"<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
|
||||
if (charge.getReason() != null) {
|
||||
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
if (charge.getReasonCode() != null) {
|
||||
xml += "<ram:ReasonCode>" + charge.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
if (charge.getReason() != null) {
|
||||
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
xml += "<ram:CategoryTradeTax>" +
|
||||
"<ram:TypeCode>VAT</ram:TypeCode>" +
|
||||
"<ram:CategoryCode>" + charge.getCategoryCode() + "</ram:CategoryCode>";
|
||||
if (charge.getTaxPercent() != null) {
|
||||
if (charge.getTaxPercent() != null && !charge.getCategoryCode().equals(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE)) {
|
||||
xml += "<ram:RateApplicablePercent>" + vatFormat(charge.getTaxPercent()) + "</ram:RateApplicablePercent>";
|
||||
}
|
||||
xml += "</ram:CategoryTradeTax>" +
|
||||
@@ -731,7 +808,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for(IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>false</udt:Indicator>" +
|
||||
@@ -772,7 +849,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((trans.getPaymentTerms() == null) && (getProfile() != Profiles.getByName("Minimum")) && ((paymentTermsDescription != null) || (trans.getTradeSettlement() != null) || (hasDueDate))) {
|
||||
xml += "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
@@ -780,6 +856,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
xml += "<ram:Description>" + paymentTermsDescription + "</ram:Description>";
|
||||
}
|
||||
|
||||
if (trans.getDueDate() != null) {
|
||||
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
|
||||
+ DATE.udtFormat(trans.getDueDate())
|
||||
+ "</ram:DueDateDateTime>";// 20130704
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlement() != null) {
|
||||
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
|
||||
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
|
||||
@@ -788,12 +870,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDueDate && (trans.getDueDate() != null)) {
|
||||
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
|
||||
+ DATE.udtFormat(trans.getDueDate())
|
||||
+ "</ram:DueDateDateTime>";// 20130704
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
} else {
|
||||
xml += buildPaymentTermsXml();
|
||||
@@ -811,7 +887,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
final String chargesTotalLine = "<ram:ChargeTotalAmount>" + currencyFormat(calc.getChargesForPercent(null)) + "</ram:ChargeTotalAmount>";
|
||||
|
||||
xml += "<ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
|
||||
if (getProfile() != Profiles.getByName("Minimum")) {
|
||||
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BASICWL"))) {
|
||||
xml += "<ram:LineTotalAmount>" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>";
|
||||
xml += chargesTotalLine
|
||||
+ allowanceTotalLine;
|
||||
@@ -820,14 +896,18 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
// //
|
||||
// currencyID=\"EUR\"
|
||||
+ "<ram:TaxTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
|
||||
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>"
|
||||
+ "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
|
||||
+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>";
|
||||
if (trans.getRoundingAmount() != null) {
|
||||
xml += "<ram:RoundingAmount>" + currencyFormat(trans.getRoundingAmount()) + "</ram:RoundingAmount>";
|
||||
}
|
||||
|
||||
xml += "<ram:GrandTotalAmount>" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>";
|
||||
// //
|
||||
// currencyID=\"EUR\"
|
||||
if (getProfile() != Profiles.getByName("Minimum")) {
|
||||
xml += "<ram:TotalPrepaidAmount>" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>";
|
||||
}
|
||||
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>"
|
||||
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getDuePayable()) + "</ram:DuePayableAmount>"
|
||||
+ "</ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
|
||||
if (trans.getInvoiceReferencedDocumentID() != null) {
|
||||
xml += "<ram:InvoiceReferencedDocument>"
|
||||
@@ -840,6 +920,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
|
||||
xml += "<ram:InvoiceReferencedDocument>"
|
||||
+ "<ram:IssuerAssignedID>"
|
||||
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
|
||||
if (doc.getFormattedIssueDateTime() != null) {
|
||||
xml += "<ram:FormattedIssueDateTime>"
|
||||
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
|
||||
+ "</ram:FormattedIssueDateTime>";
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
}
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeSettlement>";
|
||||
// + "<IncludedSupplyChainTradeLineItem>\n"
|
||||
@@ -871,8 +964,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String buildNotes(IExportableTransaction exportableTransaction) {
|
||||
final List<IncludedNote> includedNotes = Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode())
|
||||
.orElse(new ArrayList<>());
|
||||
final List<IncludedNote> includedNotes = new ArrayList<>();
|
||||
Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode()).ifPresent(includedNotes::addAll);
|
||||
|
||||
if (exportableTransaction.getNotes() != null) {
|
||||
for (final String currentNote : exportableTransaction.getNotes()) {
|
||||
includedNotes.add(IncludedNote.unspecifiedNote(currentNote));
|
||||
@@ -896,56 +990,76 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
private String buildPaymentTermsXml() {
|
||||
final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
|
||||
if (paymentTerms == null) {
|
||||
|
||||
ArrayList<IZUGFeRDPaymentTerms> paymentTerms = new ArrayList<IZUGFeRDPaymentTerms>(Arrays.asList(trans.getExtendedPaymentTerms()));
|
||||
|
||||
IZUGFeRDPaymentTerms izpt= trans.getPaymentTerms();
|
||||
if (izpt!=null) {
|
||||
paymentTerms.add(izpt);
|
||||
}
|
||||
|
||||
String paymentTermsXml = "";
|
||||
if (paymentTerms.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
String paymentTermsXml = "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
|
||||
final Date dueDate = paymentTerms.getDueDate();
|
||||
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
|
||||
throw new IllegalStateException(
|
||||
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
|
||||
}
|
||||
paymentTermsXml += "<ram:Description>" + paymentTerms.getDescription() + "</ram:Description>";
|
||||
for (IZUGFeRDPaymentTerms pt : paymentTerms)
|
||||
{
|
||||
|
||||
if (dueDate != null) {
|
||||
paymentTermsXml += "<ram:DueDateDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(dueDate);
|
||||
paymentTermsXml += "</ram:DueDateDateTime>";
|
||||
}
|
||||
paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";
|
||||
|
||||
if (trans.getTradeSettlement() != null) {
|
||||
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
|
||||
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
|
||||
paymentTermsXml += payment.getPaymentXML();
|
||||
final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
|
||||
final Date dueDate = pt.getDueDate();
|
||||
if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null)
|
||||
{
|
||||
throw new IllegalStateException(
|
||||
"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
|
||||
}
|
||||
paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
|
||||
|
||||
if (dueDate != null)
|
||||
{
|
||||
paymentTermsXml += "<ram:DueDateDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(dueDate);
|
||||
paymentTermsXml += "</ram:DueDateDateTime>";
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlement() != null)
|
||||
{
|
||||
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement())
|
||||
{
|
||||
if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit))
|
||||
{
|
||||
paymentTermsXml += payment.getPaymentXML();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discountTerms != null) {
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
+ "</ram:CalculationPercent>";
|
||||
if (discountTerms != null)
|
||||
{
|
||||
paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
|
||||
final String currency = trans.getCurrency();
|
||||
final String basisAmount = currencyFormat(calc.getGrandTotal());
|
||||
paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
|
||||
paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
|
||||
+ "</ram:CalculationPercent>";
|
||||
|
||||
if (discountTerms.getBaseDate() != null) {
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
if (discountTerms.getBaseDate() != null)
|
||||
{
|
||||
final Date baseDate = discountTerms.getBaseDate();
|
||||
paymentTermsXml += "<ram:BasisDateTime>";
|
||||
paymentTermsXml += DATE.udtFormat(baseDate);
|
||||
paymentTermsXml += "</ram:BasisDateTime>";
|
||||
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
|
||||
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
|
||||
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
}
|
||||
|
||||
paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
return paymentTermsXml;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
@@ -559,6 +560,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
// https://github.com/ZUGFeRD/mustangproject/issues/249
|
||||
|
||||
COSName cidSet = COSName.getPDFName("CIDSet");
|
||||
COSName resources = COSName.getPDFName("Resources");
|
||||
|
||||
// iterate over all pdf pages
|
||||
|
||||
@@ -567,29 +569,45 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
|
||||
PDPage page = (PDPage) object;
|
||||
PDResources res = page.getResources();
|
||||
for (COSName fontName : res.getFontNames()) {
|
||||
try {
|
||||
PDFont pdFont = res.getFont(fontName);
|
||||
if (pdFont instanceof PDType0Font) {
|
||||
PDType0Font typedFont = (PDType0Font) pdFont;
|
||||
|
||||
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
|
||||
@SuppressWarnings("unused")
|
||||
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
|
||||
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
|
||||
|
||||
fontDescriptor.getCOSObject().removeItem(cidSet);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
// Check for fonts in PDXObjects:
|
||||
for (COSName xObjectName : res.getXObjectNames()) {
|
||||
PDXObject xObject = res.getXObject(xObjectName);
|
||||
COSDictionary d = xObject.getCOSObject().getCOSDictionary(resources);
|
||||
if (d != null) {
|
||||
PDResources xr = new PDResources(d);
|
||||
removeCIDSetFromPDResources(cidSet, xr);
|
||||
}
|
||||
// do stuff with the font
|
||||
}
|
||||
|
||||
// Check for fonts in document-resources:
|
||||
removeCIDSetFromPDResources(cidSet, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCIDSetFromPDResources(COSName cidSet, PDResources res) throws IOException {
|
||||
for (COSName fontName : res.getFontNames()) {
|
||||
try {
|
||||
PDFont pdFont = res.getFont(fontName);
|
||||
if (pdFont instanceof PDType0Font) {
|
||||
PDType0Font typedFont = (PDType0Font) pdFont;
|
||||
|
||||
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
|
||||
@SuppressWarnings("unused")
|
||||
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
|
||||
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
|
||||
|
||||
fontDescriptor.getCOSObject().removeItem(cidSet);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
// do stuff with the font
|
||||
}
|
||||
}
|
||||
|
||||
protected void prepareDocument() throws IOException {
|
||||
|
||||
PDDocumentCatalog cat = doc.getDocumentCatalog();
|
||||
|
||||
@@ -83,15 +83,17 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
}
|
||||
|
||||
protected byte[] filenameToByteArray(String pdfFilename) throws IOException {
|
||||
FileInputStream fileInputStream = new FileInputStream(pdfFilename);
|
||||
return inputstreamToByteArray(fileInputStream);
|
||||
try (FileInputStream fileInputStream = new FileInputStream(pdfFilename)) {
|
||||
return inputstreamToByteArray(fileInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException {
|
||||
byte[] bytes = new byte[fileInputStream.available()];
|
||||
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||
dataInputStream.readFully(bytes);
|
||||
return bytes;
|
||||
try (DataInputStream dataInputStream = new DataInputStream(fileInputStream)) {
|
||||
dataInputStream.readFully(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
@@ -13,92 +13,34 @@ package org.mustangproject.ZUGFeRD;
|
||||
* @version 1.1.0
|
||||
* @author jstaerk
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.mustangproject.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class ZUGFeRDImporter {
|
||||
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
|
||||
|
||||
/**
|
||||
* if metadata has been found
|
||||
*/
|
||||
protected boolean containsMeta = false;
|
||||
/**
|
||||
* map filenames of additional XML files to their contents
|
||||
*/
|
||||
private final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
|
||||
/**
|
||||
* map filenames of all embedded files in the respective PDF
|
||||
*/
|
||||
private final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
|
||||
/**
|
||||
* Raw XML form of the extracted data - may be directly obtained.
|
||||
*/
|
||||
private byte[] rawXML = null;
|
||||
/**
|
||||
* XMP metadata
|
||||
*/
|
||||
private String xmpString = null; // XMP metadata
|
||||
/**
|
||||
* parsed Document
|
||||
*/
|
||||
private Document document;
|
||||
private Integer version;
|
||||
|
||||
|
||||
protected ZUGFeRDImporter() {
|
||||
//constructor for extending classes
|
||||
public ZUGFeRDImporter() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ZUGFeRDImporter(String pdfFilename) {
|
||||
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
|
||||
extractLowLevel(bis);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
public ZUGFeRDImporter(String filename) {
|
||||
super(filename);
|
||||
}
|
||||
|
||||
|
||||
public ZUGFeRDImporter(InputStream pdfStream) {
|
||||
try {
|
||||
extractLowLevel(pdfStream);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
public ZUGFeRDImporter(InputStream stream) {
|
||||
super(stream);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,169 +55,21 @@ public class ZUGFeRDImporter {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
|
||||
*
|
||||
* @param inStream a inputstream of a pdf file
|
||||
*/
|
||||
private void extractLowLevel(InputStream inStream) throws IOException {
|
||||
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
|
||||
byte[] pad = new byte[4];
|
||||
pdfStream.mark(0);
|
||||
pdfStream.read(pad);
|
||||
pdfStream.reset();
|
||||
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
|
||||
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
|
||||
|
||||
|
||||
try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
|
||||
// PDDocumentInformation info = doc.getDocumentInformation();
|
||||
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
||||
//start
|
||||
|
||||
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
|
||||
LOGGER.info("no-xmlpart");
|
||||
return;
|
||||
}
|
||||
|
||||
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
|
||||
xmpString = convertStreamToString(XMP);
|
||||
|
||||
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
|
||||
if (etn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
|
||||
// String filePath = "/tmp/";
|
||||
|
||||
if (efMap != null) {
|
||||
extractFiles(efMap); // see
|
||||
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
|
||||
} else {
|
||||
|
||||
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
|
||||
if (kids == null) {
|
||||
return;
|
||||
}
|
||||
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
|
||||
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
|
||||
extractFiles(namesL);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no PDF probably XML
|
||||
containsMeta = true;
|
||||
setRawXML(XMLTools.getBytesFromStream(pdfStream));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
|
||||
for (final String alias : names.keySet()) {
|
||||
|
||||
final PDComplexFileSpecification fileSpec = names.get(alias);
|
||||
final String filename = fileSpec.getFilename();
|
||||
/**
|
||||
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
|
||||
*/
|
||||
|
||||
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
||||
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
|
||||
containsMeta = true;
|
||||
|
||||
// String embeddedFilename = filePath + filename;
|
||||
// File file = new File(filePath + filename);
|
||||
// System.out.println("Writing " + embeddedFilename);
|
||||
// ByteArrayOutputStream fileBytes=new
|
||||
// ByteArrayOutputStream();
|
||||
// FileOutputStream fos = new FileOutputStream(file);
|
||||
|
||||
setRawXML(embeddedFile.toByteArray());
|
||||
|
||||
// fos.write(embeddedFile.getByteArray());
|
||||
// fos.close();
|
||||
}
|
||||
if (filename.startsWith("additional_data")) {
|
||||
additionalXMLs.put(filename, embeddedFile.toByteArray());
|
||||
}
|
||||
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
|
||||
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
|
||||
xmlFact.setNamespaceAware(true);
|
||||
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
|
||||
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
||||
/// is.skip(guessBOMSize(is));
|
||||
document = builder.parse(is);
|
||||
}
|
||||
|
||||
|
||||
public void setRawXML(byte[] rawXML) throws IOException {
|
||||
this.containsMeta = true;
|
||||
this.rawXML = rawXML;
|
||||
this.version = null;
|
||||
try {
|
||||
setDocument();
|
||||
} catch (ParserConfigurationException | SAXException e) {
|
||||
LOGGER.error("Failed to parse XML", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String extractString(String xpathStr) {
|
||||
if (!containsMeta) {
|
||||
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
|
||||
}
|
||||
final String result;
|
||||
try {
|
||||
final Document document = getDocument();
|
||||
final XPathFactory xpathFact = XPathFactory.newInstance();
|
||||
final XPath xpath = xpathFact.newXPath();
|
||||
result = xpath.evaluate(xpathStr, document);
|
||||
} catch (final XPathExpressionException e) {
|
||||
LOGGER.error("Failed to evaluate XPath", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/***
|
||||
* Wrapper for protected method extractString
|
||||
* @param xpathStr the xpath expression to be evaluated
|
||||
* @return the extracted String for the specific path in the document
|
||||
*/
|
||||
public String wExtractString(String xpathStr) {
|
||||
return extractString(xpathStr);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @return the reference (purpose) the sender specified for this invoice
|
||||
*/
|
||||
public String getForeignReference() {
|
||||
String result = extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']/*[local-name() = 'PaymentReference']");
|
||||
if (result == null || result.isEmpty()) {
|
||||
result = extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'PaymentReference']");
|
||||
}
|
||||
return result;
|
||||
|
||||
return importedInvoice.getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ZUGFeRD Profile
|
||||
*/
|
||||
public String getZUGFeRDProfil() {
|
||||
|
||||
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
|
||||
if (guideline.contains("xrechnung")) {
|
||||
return "XRECHNUNG";
|
||||
@@ -292,6 +86,7 @@ public class ZUGFeRDImporter {
|
||||
case "urn:factur-x.eu:1p0:minimum":
|
||||
return "MINIMUM";
|
||||
case "urn:ferd:CrossIndustryDocument:invoice:1p0:extended":
|
||||
case "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended":
|
||||
case "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended":
|
||||
return "EXTENDED";
|
||||
default:
|
||||
@@ -299,21 +94,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Invoice Currency Code
|
||||
*/
|
||||
public String getInvoiceCurrencyCode() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the IssuerAssigned ID
|
||||
@@ -336,57 +116,6 @@ public class ZUGFeRDImporter {
|
||||
return extractIssuerAssignedID("ContractReferencedDocument");
|
||||
}
|
||||
|
||||
private String extractIssuerAssignedID(String propertyName) {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty ID
|
||||
*/
|
||||
public String getBuyerTradePartyID() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'ID']");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Issue Date()
|
||||
*/
|
||||
public String getIssueDate() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
final String toParse = extractString(
|
||||
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
|
||||
"//*[local-name() = 'BillingSpecifiedPeriod']" +
|
||||
"//*[local-name() = 'StartDateTime']//*[local-name() = 'DateTimeString']");
|
||||
return tryDate(toParse);
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
final String toParse = extractString(
|
||||
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
|
||||
"//*[local-name() = 'BillingSpecifiedPeriod']" +
|
||||
"//*[local-name() = 'EndDateTime']//*[local-name() = 'DateTimeString']");
|
||||
return tryDate(toParse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the TaxBasisTotalAmount
|
||||
@@ -470,7 +199,16 @@ public class ZUGFeRDImporter {
|
||||
* @return the BuyerTradeParty SpecifiedTaxRegistration ID
|
||||
*/
|
||||
public String getBuyertradePartySpecifiedTaxRegistrationID() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'SpecifiedTaxRegistration']//*[local-name() = 'ID']");
|
||||
String id = null;
|
||||
if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) {
|
||||
// this *should* be the official result
|
||||
id = importedInvoice.getRecipient().getLegalOrganisation().getSchemedID().getID();
|
||||
}
|
||||
// but also provide some fallback
|
||||
if (id == null) {
|
||||
id = getBuyerTradePartyID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -494,14 +232,14 @@ public class ZUGFeRDImporter {
|
||||
* @return the BuyerTradeParty Name
|
||||
*/
|
||||
public String getBuyerTradePartyName() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'Name']");
|
||||
return importedInvoice.getRecipient().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty Name
|
||||
*/
|
||||
public String getDeliveryTradePartyName() {
|
||||
return extractString("//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'Name']");
|
||||
return importedInvoice.getDeliveryAddress().getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -525,7 +263,7 @@ public class ZUGFeRDImporter {
|
||||
* @return the Payment Terms
|
||||
*/
|
||||
public String getPaymentTerms() {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']//*[local-name() = 'Description']");
|
||||
return importedInvoice.getPaymentTermDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,16 +286,7 @@ public class ZUGFeRDImporter {
|
||||
* @return the Invoice ID
|
||||
*/
|
||||
public String getInvoiceID() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'ID']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
return importedInvoice.getNumber();
|
||||
}
|
||||
|
||||
|
||||
@@ -582,16 +311,7 @@ public class ZUGFeRDImporter {
|
||||
* @return the referred document
|
||||
*/
|
||||
public String getReference() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeAgreement']/*[local-name() = 'BuyerReference']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ApplicableHeaderTradeAgreement']/*[local-name() = 'BuyerReference']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
return importedInvoice.getReferenceNumber();
|
||||
}
|
||||
|
||||
|
||||
@@ -615,7 +335,18 @@ public class ZUGFeRDImporter {
|
||||
* @return the sender's account IBAN code
|
||||
*/
|
||||
public String getIBAN() {
|
||||
return extractString("//*[local-name() = 'PayeePartyCreditorFinancialAccount']/*[local-name() = 'IBANID']");
|
||||
if ((importedInvoice==null)||(importedInvoice.getTradeSettlement()==null)) {
|
||||
return null;
|
||||
}
|
||||
for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) {
|
||||
if (settlement instanceof IZUGFeRDTradeSettlementDebit) {
|
||||
return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN();
|
||||
}
|
||||
if (settlement instanceof IZUGFeRDTradeSettlementPayment) {
|
||||
return ((IZUGFeRDTradeSettlementPayment) settlement).getOwnIBAN();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -628,14 +359,7 @@ public class ZUGFeRDImporter {
|
||||
* @return the total payable amount
|
||||
*/
|
||||
public String getAmount() {
|
||||
String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']");
|
||||
if (result == null || result.isEmpty()) {
|
||||
|
||||
/* fx/zf would be SpecifiedTradeSettlementMonetarySummation
|
||||
* but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/
|
||||
result = extractString("//*[local-name() = 'GrandTotalAmount']");
|
||||
}
|
||||
return result;
|
||||
return importedInvoice.getGrandTotal().toPlainString();
|
||||
}
|
||||
|
||||
|
||||
@@ -643,7 +367,60 @@ public class ZUGFeRDImporter {
|
||||
* @return when the payment is due
|
||||
*/
|
||||
public String getDueDate() {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']/*[local-name() = 'DueDateDateTime']/*[local-name() = 'DateTimeString']");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
return sdf.format(importedInvoice.getDueDate());
|
||||
}
|
||||
|
||||
|
||||
////////////////////
|
||||
|
||||
/**
|
||||
* @return the Invoice Currency Code
|
||||
*/
|
||||
public String getInvoiceCurrencyCode() {
|
||||
return importedInvoice.getCurrency();
|
||||
}
|
||||
|
||||
|
||||
private String extractIssuerAssignedID(String propertyName) {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty ID
|
||||
*/
|
||||
public String getBuyerTradePartyID() {
|
||||
String id = importedInvoice.getRecipient().getID();
|
||||
if (id == null) {
|
||||
// provide some fallback
|
||||
id = importedInvoice.getRecipient().getVATID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Issue Date()
|
||||
*/
|
||||
public String getIssueDate() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
return sdf.format(importedInvoice.getIssueDate());
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
return importedInvoice.getDetailedDeliveryPeriodFrom();
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return importedInvoice.getDetailedDeliveryPeriodTo();
|
||||
}
|
||||
|
||||
|
||||
@@ -691,28 +468,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
public EStandard getStandard() throws Exception {
|
||||
if (!containsMeta) {
|
||||
throw new Exception("Not yet parsed");
|
||||
}
|
||||
final String head = getUTF8();
|
||||
String rootNode = extractString("local-name(/*)");
|
||||
if (rootNode.equals("CrossIndustryDocument")) {
|
||||
return EStandard.zugferd;
|
||||
} else if (rootNode.equals("Invoice")) {
|
||||
return EStandard.ubl;
|
||||
} else if (rootNode.equals("CrossIndustryInvoice")) {
|
||||
return EStandard.facturx;
|
||||
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
|
||||
return EStandard.despatchadvice;
|
||||
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
|
||||
throw new Exception("ZUGFeRD version could not be determined");
|
||||
|
||||
}
|
||||
|
||||
public int getVersion() throws Exception {
|
||||
if (!containsMeta) {
|
||||
throw new Exception("Not yet parsed");
|
||||
@@ -736,35 +491,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return return UTF8 XML (without BOM) of the invoice
|
||||
*/
|
||||
public String getUTF8() {
|
||||
if (rawXML == null) {
|
||||
return null;
|
||||
}
|
||||
if (rawXML.length < 3) {
|
||||
return new String(rawXML);
|
||||
}
|
||||
|
||||
|
||||
final byte[] bomlessData;
|
||||
|
||||
if ((rawXML[0] == (byte) 0xEF)
|
||||
&& (rawXML[1] == (byte) 0xBB)
|
||||
&& (rawXML[2] == (byte) 0xBF)) {
|
||||
// I don't like BOMs, lets remove it
|
||||
bomlessData = new byte[rawXML.length - 3];
|
||||
System.arraycopy(rawXML, 3, bomlessData, 0,
|
||||
rawXML.length - 3);
|
||||
} else {
|
||||
bomlessData = rawXML;
|
||||
}
|
||||
|
||||
return new String(bomlessData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the raw XML data as extracted from the ZUGFeRD PDF file.
|
||||
*
|
||||
@@ -790,14 +516,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
static String convertStreamToString(java.io.InputStream is) {
|
||||
try {
|
||||
return IOUtils.toString(is, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
||||
*
|
||||
@@ -929,9 +647,11 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* returns a list of LineItems
|
||||
* @deprecated use invoiceimporter getZFItems
|
||||
*
|
||||
* @return a List of LineItem instances
|
||||
*/
|
||||
@Deprecated
|
||||
public List<Item> getLineItemList() {
|
||||
final List<Node> nodeList = getLineItemNodes();
|
||||
final List<Item> lineItemList = new ArrayList<>();
|
||||
@@ -953,7 +673,7 @@ public class ZUGFeRDImporter {
|
||||
if (node != null) {
|
||||
final NodeList tradeAgreementChildren = node.getChildNodes();
|
||||
node = getNodeByName(tradeAgreementChildren, "ChargeAmount");
|
||||
lineItem.setPrice(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setPrice(XMLTools.tryBigDecimal(node));
|
||||
node = getNodeByName(tradeAgreementChildren, "BasisQuantity");
|
||||
if (node != null && node.getAttributes() != null) {
|
||||
final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode");
|
||||
@@ -966,49 +686,65 @@ public class ZUGFeRDImporter {
|
||||
node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "ChargeAmount");
|
||||
lineItem.setGrossPrice(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setGrossPrice(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssociatedDocumentLineDocument":
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "LineID");
|
||||
lineItem.setId(getNodeValue(node));
|
||||
lineItem.setId(XMLTools.getNodeValue(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedTradeProduct":
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "GlobalID");
|
||||
if (node != null) {
|
||||
SchemedID globalId = new SchemedID()
|
||||
.setScheme(node.getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue())
|
||||
.setId(XMLTools.getNodeValue(node));
|
||||
lineItem.getProduct().addGlobalID(globalId);
|
||||
}
|
||||
node = getNodeByName(nn.getChildNodes(), "SellerAssignedID");
|
||||
lineItem.getProduct().setSellerAssignedID(getNodeValue(node));
|
||||
lineItem.getProduct().setSellerAssignedID(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID");
|
||||
lineItem.getProduct().setBuyerAssignedID(getNodeValue(node));
|
||||
lineItem.getProduct().setBuyerAssignedID(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "Name");
|
||||
lineItem.getProduct().setName(getNodeValue(node));
|
||||
lineItem.getProduct().setName(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "Description");
|
||||
lineItem.getProduct().setDescription(getNodeValue(node));
|
||||
lineItem.getProduct().setDescription(XMLTools.getNodeValue(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedLineTradeDelivery":
|
||||
case "SpecifiedSupplyChainTradeDelivery":
|
||||
node = getNodeByName(nn.getChildNodes(), "BilledQuantity");
|
||||
lineItem.setQuantity(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setQuantity(XMLTools.tryBigDecimal(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedLineTradeSettlement":
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "RateApplicablePercent");
|
||||
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
|
||||
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setTax(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "CategoryCode");
|
||||
if(node != null){
|
||||
lineItem.getProduct().setTaxCategoryCode(XMLTools.getNodeValue(node));
|
||||
}
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod");
|
||||
if (node != null) {
|
||||
final Node start = getNodeByName(node.getChildNodes(), "StartDateTime");
|
||||
@@ -1021,13 +757,13 @@ public class ZUGFeRDImporter {
|
||||
if (end != null) {
|
||||
dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString");
|
||||
}
|
||||
lineItem.setDetailedDeliveryPeriod(tryDate(dateTimeStart), tryDate(dateTimeEnd));
|
||||
lineItem.setDetailedDeliveryPeriod(XMLTools.tryDate(dateTimeStart), XMLTools.tryDate(dateTimeEnd));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
|
||||
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
case "SpecifiedSupplyChainTradeSettlement":
|
||||
@@ -1036,19 +772,19 @@ public class ZUGFeRDImporter {
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "ApplicablePercent");
|
||||
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
|
||||
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setTax(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
|
||||
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1123,51 +859,4 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the value of an node
|
||||
*
|
||||
* @param node the Node to get the value from
|
||||
* @return A String or empty String, if no value was found
|
||||
*/
|
||||
private String getNodeValue(Node node) {
|
||||
if (node != null && node.getFirstChild() != null) {
|
||||
return node.getFirstChild().getNodeValue();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to convert an String to BigDecimal.
|
||||
*
|
||||
* @param nodeValue The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
private BigDecimal tryBigDecimal(String nodeValue) {
|
||||
try {
|
||||
return new BigDecimal(nodeValue);
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
return BigDecimal.valueOf(Float.valueOf(nodeValue));
|
||||
} catch (final Exception ex) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Date tryDate(Node node) {
|
||||
final String nodeValue = getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return tryDate(nodeValue);
|
||||
}
|
||||
|
||||
private static Date tryDate(String toParse) {
|
||||
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
|
||||
try {
|
||||
return formatter.parse(toParse);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,56 +20,38 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import com.helger.commons.io.stream.StreamHelper;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Templates;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.apache.fop.apps.FOPException;
|
||||
import org.apache.fop.apps.FOUserAgent;
|
||||
import org.apache.fop.apps.Fop;
|
||||
import org.apache.fop.apps.FopFactory;
|
||||
import org.apache.fop.apps.FopFactoryBuilder;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.fop.apps.*;
|
||||
import org.apache.fop.apps.io.ResourceResolverFactory;
|
||||
import org.apache.fop.configuration.Configuration;
|
||||
import org.apache.fop.configuration.ConfigurationException;
|
||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||
import org.apache.xmlgraphics.util.MimeConstants;
|
||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.helger.commons.io.stream.StreamHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ZUGFeRDVisualizer {
|
||||
|
||||
@@ -97,7 +79,7 @@ public class ZUGFeRDVisualizer {
|
||||
private Templates mXsltXRTemplate = null;
|
||||
private Templates mXsltUBLTemplate = null;
|
||||
private Templates mXsltCIOTemplate = null;
|
||||
private Templates mXsltHTMLTemplate = null;
|
||||
private Map<Language, Templates> mXsltHTMLTemplates = null;
|
||||
private Templates mXsltPDFTemplate = null;
|
||||
private Templates mXsltZF1HTMLTemplate = null;
|
||||
|
||||
@@ -107,123 +89,170 @@ public class ZUGFeRDVisualizer {
|
||||
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
}
|
||||
if (mXsltHTMLTemplate == null) {
|
||||
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
|
||||
}
|
||||
if (mXsltZF1HTMLTemplate == null) {
|
||||
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
|
||||
}
|
||||
} catch (TransformerConfigurationException ex) {
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* *
|
||||
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
|
||||
* http://www.ferd-net.de/upload/Dokumente/FACTUR-X_ZUGFeRD_2p0_Teil1_Profil_EN16931_1p03.pdf
|
||||
* http://countwordsfree.com/xmlviewer
|
||||
*/
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
String fileContent = "";
|
||||
try {
|
||||
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
|
||||
} catch (IOException e2) {
|
||||
LOGGER.error("Failed to read file content", e2);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
/***
|
||||
* returns which standard is used, CII or UBL
|
||||
* @param fis inputstream (will be consumed)
|
||||
* @return (facturx = cii)
|
||||
*/
|
||||
private EStandard findOutStandardFromRootNode(InputStream fis)
|
||||
throws ParserConfigurationException {
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
String ublSignature = "Invoice";
|
||||
String ublCreditNoteSignature = "CreditNote";
|
||||
String cioSignature = "SCRDMCCBDACIOMessageStructure" +
|
||||
"";
|
||||
boolean doPostProcessing = false;
|
||||
String cioSignature = "SCRDMCCBDACIOMessageStructure";
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
//REDHAT
|
||||
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
|
||||
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
||||
|
||||
//OWASP
|
||||
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
// Disable external DTDs as well
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
dbf.setNamespaceAware(true);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(new InputSource(fis));
|
||||
Element root = doc.getDocumentElement();
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
if (root.getLocalName().equals(zf1Signature)) {
|
||||
applyZF1XSLT(fis, baos);
|
||||
} else if (root.getLocalName().equals(zf2Signature)) {
|
||||
try {
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(new InputSource(fis));
|
||||
Element root = doc.getDocumentElement();
|
||||
if (root.getLocalName().equals(zf1Signature)) {
|
||||
return EStandard.zugferd;
|
||||
} else if (root.getLocalName().equals(zf2Signature)) {
|
||||
return EStandard.facturx;
|
||||
} else if (root.getLocalName().equals(ublSignature)) {
|
||||
return EStandard.ubl;
|
||||
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
|
||||
return EStandard.ubl_creditnote;
|
||||
} else if (root.getLocalName().equals(cioSignature)) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to recognize standard", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws IOException, TransformerException, ParserConfigurationException {
|
||||
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
||||
return visualize(fis, lang);
|
||||
}
|
||||
}
|
||||
|
||||
public String visualize(InputStream inputXml, Language lang)
|
||||
throws IOException, TransformerException, ParserConfigurationException {
|
||||
initTemplates(lang);
|
||||
|
||||
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
|
||||
EStandard thestandard = findOutStandardFromRootNode(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream htmlOutput = new ByteArrayOutputStream();
|
||||
|
||||
|
||||
ByteArrayInputStream xmlContentStream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
if (thestandard == EStandard.zugferd) {
|
||||
applyZF1XSLT(xmlContentStream, htmlOutput);
|
||||
return htmlOutput.toString(StandardCharsets.UTF_8);
|
||||
} else if (thestandard == EStandard.facturx) {
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (root.getLocalName().equals(ublSignature)) {
|
||||
applyZF2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl) {
|
||||
//zf2 or fx
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
|
||||
applyUBL2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl_creditnote) {
|
||||
//zf2 or fx
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (root.getLocalName().equals(cioSignature)) {
|
||||
applyUBLCreditNote2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.orderx) {
|
||||
//zf2 or fx
|
||||
applyCIO2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyCIO2XSLT(xmlContentStream, htmlOutput);
|
||||
} else {
|
||||
throw new IllegalArgumentException("File does not look like CII or UBL");
|
||||
}
|
||||
if (doPostProcessing) {
|
||||
// take the copy of the stream and re-write it to an InputStream
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
try {
|
||||
out = new PipedOutputStream(in);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToHTML(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create HTML", e1);
|
||||
}
|
||||
|
||||
Optional<InputStream> in = copyStream(htmlOutput);
|
||||
ByteArrayOutputStream htmlOutStream = new ByteArrayOutputStream();
|
||||
if (in.isPresent()) {
|
||||
applyXSLTToHTML(in.get(), htmlOutStream, lang);
|
||||
}
|
||||
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
return htmlOutStream.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: jstaerk: why not copy with that simple call: new ByteArrayInputStream(byteArrayOutputStream.toByteArray()) ?
|
||||
*/
|
||||
private Optional<InputStream> copyStream(ByteArrayOutputStream byteArrayOutputStream) {
|
||||
// take the copy of the stream and re-write it to an InputStream
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
try {
|
||||
PipedOutputStream out = new PipedOutputStream(in);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
byteArrayOutputStream.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}).start();
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create HTML", e1);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(in);
|
||||
}
|
||||
|
||||
|
||||
private void initTemplates(Language lang) throws TransformerConfigurationException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
|
||||
if (mXsltHTMLTemplates == null) {
|
||||
mXsltHTMLTemplates = new HashMap<>();
|
||||
if (mXsltHTMLTemplates.get(lang) == null) {
|
||||
Templates mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
|
||||
mXsltHTMLTemplates.put(lang, mXsltHTMLTemplate);
|
||||
}
|
||||
}
|
||||
if (mXsltZF1HTMLTemplate == null) {
|
||||
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
|
||||
}
|
||||
}
|
||||
|
||||
protected String toFOP(String xmlFilename)
|
||||
throws FileNotFoundException, TransformerException {
|
||||
throws IOException, TransformerException, ParserConfigurationException {
|
||||
EStandard theStandard;
|
||||
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
||||
theStandard = findOutStandardFromRootNode(fis);
|
||||
}
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
||||
return toFOP(fis, theStandard);
|
||||
}
|
||||
}
|
||||
|
||||
protected String toFOP(InputStream is, EStandard theStandard)
|
||||
throws TransformerException, IOException {
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
@@ -232,58 +261,86 @@ public class ZUGFeRDVisualizer {
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
try {
|
||||
out = new PipedOutputStream(in);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToPDF(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create PDF", e1);
|
||||
if (theStandard == EStandard.facturx) {
|
||||
applyZF2XSLT(is, iaos);
|
||||
} else if (theStandard == EStandard.ubl) {
|
||||
applyUBL2XSLT(is, iaos);
|
||||
} else if (theStandard == EStandard.ubl_creditnote) {
|
||||
applyUBLCreditNote2XSLT(is, iaos);
|
||||
}
|
||||
|
||||
|
||||
Optional<InputStream> in = copyStream(iaos);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (in.isPresent()) {
|
||||
applyXSLTToPDF(in.get(), baos);
|
||||
}
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void toPDF(String xmlFilename, String pdfFilename) {
|
||||
|
||||
// the writing part
|
||||
File CIIinputFile = new File(xmlFilename);
|
||||
File XMLinputFile = new File(xmlFilename);
|
||||
|
||||
String result = null;
|
||||
String fopInput = null;
|
||||
|
||||
/* remove file endings so that tests can also pass after checking
|
||||
out from git with arbitrary options (which may include CSRF changes)
|
||||
*/
|
||||
try {
|
||||
result = this.toFOP(CIIinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException | TransformerException e) {
|
||||
fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
|
||||
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
|
||||
toPDFfromFOP(fopInput, () -> {
|
||||
try {
|
||||
return new FileOutputStream(pdfFilename);
|
||||
} catch (FileNotFoundException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
return null;
|
||||
}, (OutputStream out) -> {});
|
||||
}
|
||||
|
||||
public byte[] toPDF(String xmlContent) {
|
||||
|
||||
String fopInput = null;
|
||||
|
||||
/* remove file endings so that tests can also pass after checking
|
||||
out from git with arbitrary options (which may include CSRF changes)
|
||||
*/
|
||||
try {
|
||||
ByteArrayInputStream fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));
|
||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
|
||||
|
||||
fopInput = toFOP(fis, theStandard);
|
||||
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
|
||||
AtomicReference<byte[]> byteHolder = new AtomicReference<>();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
toPDFfromFOP(fopInput, () -> new BufferedOutputStream(os), (OutputStream out) -> {
|
||||
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
byteHolder.set(os.toByteArray());
|
||||
});
|
||||
|
||||
return byteHolder.get();
|
||||
}
|
||||
|
||||
private void toPDFfromFOP(String fopInput, Supplier<OutputStream> outputStreamDelegate, Consumer<OutputStream> consumerDelegate) {
|
||||
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
|
||||
Configuration cfg = null;
|
||||
@@ -311,40 +368,46 @@ public class ZUGFeRDVisualizer {
|
||||
// Step 2: Set up output stream.
|
||||
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
|
||||
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
|
||||
try (OutputStream out = new BufferedOutputStream(outputStreamDelegate.get())) {
|
||||
|
||||
// Step 3: Construct fop with desired output format
|
||||
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
|
||||
|
||||
// Step 4: Setup JAXP using identity transformer
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
Transformer transformer = factory.newTransformer(); // identity transformer
|
||||
|
||||
// Step 5: Setup input and output for XSLT transformation
|
||||
// Setup input stream
|
||||
Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
|
||||
Source src = new StreamSource(new ByteArrayInputStream(fopInput.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
// Resulting SAX events (the generated FO) must be piped through to FOP
|
||||
Result res = new SAXResult(fop.getDefaultHandler());
|
||||
|
||||
// Step 6: Start XSLT transformation and FOP processing
|
||||
transformer.transform(src, res);
|
||||
|
||||
//Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
consumerDelegate.accept(out);
|
||||
|
||||
} catch (FOPException | IOException | TransformerException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
|
||||
}
|
||||
Transformer transformer = mXsltXRTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream htmlOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltCIOTemplate == null) {
|
||||
mXsltCIOTemplate = mFactory.newTemplates(
|
||||
@@ -352,10 +415,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltCIOTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutstream));
|
||||
}
|
||||
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
mXsltUBLTemplate = mFactory.newTemplates(
|
||||
@@ -363,10 +426,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltUBLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
mXsltUBLTemplate = mFactory.newTemplates(
|
||||
@@ -374,28 +437,30 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltUBLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
Transformer transformer = mXsltZF1HTMLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
Transformer transformer = mXsltHTMLTemplate.newTransformer();
|
||||
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream htmlOutStream, Language lang)
|
||||
throws TransformerException, IOException {
|
||||
Transformer transformer = mXsltHTMLTemplates.get(lang).newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
xmlFile.close();
|
||||
}
|
||||
|
||||
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
|
||||
throws TransformerException {
|
||||
throws TransformerException, IOException {
|
||||
Transformer transformer = mXsltPDFTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
|
||||
xmlFile.close();
|
||||
}
|
||||
|
||||
private static class ClasspathResourceURIResolver implements URIResolver {
|
||||
@@ -404,7 +469,7 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Source resolve(String href, String base) throws TransformerException {
|
||||
public Source resolve(String href, String base) {
|
||||
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/** **********************************************************************
|
||||
*
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright 2018 Jochen Staerk
|
||||
*
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
*
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*********************************************************************** */
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD.model;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -29,6 +31,7 @@ public class TaxCategoryCodeTypeConstants {
|
||||
public static final String ZEROTAXPRODUCTS = "Z";
|
||||
public static final String UNTAXEDSERVICE = "O";
|
||||
public static final String INTRACOMMUNITY = "K";
|
||||
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE).collect(Collectors.toSet());
|
||||
public static final String FREEEXPORT = "G";
|
||||
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT, FREEEXPORT, UNTAXEDSERVICE).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.mustangproject.util;
|
||||
|
||||
public final class ByteArraySearcher {
|
||||
|
||||
private ByteArraySearcher() {
|
||||
}
|
||||
|
||||
public static int indexOf(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= haystack.length - needle.length; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[i + j] != needle[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean contains(byte[] haystack, byte[] needle) {
|
||||
return indexOf(haystack, needle) >= 0;
|
||||
}
|
||||
|
||||
public static boolean startsWith(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[j] != needle[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
151
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
151
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.util;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The {@link NodeMap} contains a {@link Map} representation of DOM object
|
||||
* It can be constructed either from the children of a single {@link Node} or a {@link NodeList}.
|
||||
*/
|
||||
public class NodeMap {
|
||||
private final Map<String, List<Node>> map = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link NodeMap}
|
||||
* The {@link Node} that is passed to the constructor must not be null.
|
||||
* The {@link NodeMap} will be empty when no child nodes are present.
|
||||
*
|
||||
* @param node the node that shall be represented by this {@link NodeMap}
|
||||
* @throws IllegalArgumentException when argument is null
|
||||
*/
|
||||
public NodeMap(Node node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node cannot be null");
|
||||
}
|
||||
if (node.hasChildNodes()) {
|
||||
mapNodeList(node.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
public NodeMap(NodeList nodeList) {
|
||||
if (nodeList == null) {
|
||||
throw new IllegalArgumentException("nodeList cannot be null");
|
||||
}
|
||||
mapNodeList(nodeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the matching node
|
||||
*/
|
||||
public Optional<Node> getNode(String... localNames) {
|
||||
return getAllNodes(localNames).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link NodeMap} of the child of a matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the {@link NodeMap} representation of the matching node
|
||||
*/
|
||||
public Optional<NodeMap> getAsNodeMap(String... localNames) {
|
||||
return getNode(localNames).filter(Node::hasChildNodes).map(NodeMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node
|
||||
*/
|
||||
public Optional<String> getAsString(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node, converted to BigDecimal
|
||||
*/
|
||||
public Optional<BigDecimal> getAsBigDecimal(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent).map(s->{
|
||||
try {
|
||||
return new BigDecimal(s.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node or {@code null} when no matching node could be found
|
||||
*/
|
||||
public String getAsStringOrNull(String... localNames) {
|
||||
return getAsString(localNames).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all matching nodes
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return a {@code Stream} that contains all matching nodes (can be empty if nothing matches)
|
||||
*/
|
||||
public Stream<Node> getAllNodes(String... localNames) {
|
||||
List<String> localNamesList = Arrays.asList(localNames);
|
||||
return map.entrySet().stream().filter(e -> localNamesList.contains(e.getKey())).flatMap(e -> e.getValue().stream());
|
||||
}
|
||||
|
||||
private void mapNodeList(NodeList nodeList) {
|
||||
IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item)
|
||||
.filter(node -> node != null && node.getLocalName() != null)
|
||||
.forEach(node -> map.computeIfAbsent(node.getLocalName(), k -> new ArrayList<>()).add(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
}
|
||||
BIN
library/src/main/resources/fonts/SourceSansPro-Bold.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSansPro-Bold.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSansPro-BoldIt.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSansPro-BoldIt.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSansPro-It.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSansPro-It.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSansPro-Regular.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSansPro-Regular.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSerifPro-Bold.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSerifPro-Bold.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSerifPro-BoldIt.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSerifPro-BoldIt.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSerifPro-It.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSerifPro-It.ttf
Normal file
Binary file not shown.
BIN
library/src/main/resources/fonts/SourceSerifPro-Regular.ttf
Normal file
BIN
library/src/main/resources/fonts/SourceSerifPro-Regular.ttf
Normal file
Binary file not shown.
@@ -1,37 +1,57 @@
|
||||
<fop version="1.0">
|
||||
|
||||
<!-- Strict user configuration -->
|
||||
<strict-configuration>true</strict-configuration>
|
||||
|
||||
<!-- Strict FO validation -->
|
||||
<strict-validation>true</strict-validation>
|
||||
|
||||
<!-- Base URL for resolving relative URLs -->
|
||||
|
||||
<!-- Font Base URL for resolving relative font URLs -->
|
||||
|
||||
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
|
||||
<source-resolution>96</source-resolution>
|
||||
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
|
||||
<target-resolution>96</target-resolution>
|
||||
|
||||
<!-- default page-height and page-width, in case
|
||||
value is specified as auto -->
|
||||
<default-page-settings height="297mm" width="210mm"/><!-- DIN A/4 -->
|
||||
|
||||
<!-- etc. etc..... -->
|
||||
|
||||
<renderers>
|
||||
<renderer mime="application/pdf">
|
||||
<fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
|
||||
<auto-detect/>
|
||||
<font kerning="no" embed-url="classpath:FreeSans.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="normal" weight="normal" />
|
||||
<font-triplet name="Times-Bold" style="normal" weight="normal" />
|
||||
</font>
|
||||
</fonts>
|
||||
<output-profile>classpath:AdobeCompat-v2.icc</output-profile>
|
||||
</renderer>
|
||||
</renderers>
|
||||
|
||||
</fop>
|
||||
<fop version="1.0">
|
||||
|
||||
<!-- Strict user configuration -->
|
||||
<strict-configuration>true</strict-configuration>
|
||||
|
||||
<!-- Strict FO validation -->
|
||||
<strict-validation>true</strict-validation>
|
||||
|
||||
<!-- Base URL for resolving relative URLs -->
|
||||
|
||||
<!-- Font Base URL for resolving relative font URLs -->
|
||||
|
||||
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
|
||||
<source-resolution>96</source-resolution>
|
||||
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
|
||||
<target-resolution>96</target-resolution>
|
||||
|
||||
<!-- default page-height and page-width, in case
|
||||
value is specified as auto -->
|
||||
<default-page-settings height="297mm" width="210mm"/><!-- DIN A/4 -->
|
||||
|
||||
<!-- etc. etc..... -->
|
||||
|
||||
<renderers>
|
||||
<renderer mime="application/pdf">
|
||||
<fonts>
|
||||
<!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
|
||||
<auto-detect/>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Regular.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSansPro" style="normal" weight="400"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-It.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSansPro" style="italic" weight="400"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-Bold.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSansPro" style="normal" weight="700"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSansPro-BoldIt.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSansPro" style="italic" weight="700"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Regular.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="normal" weight="400"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-It.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="italic" weight="400"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-Bold.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="normal" weight="700"/>
|
||||
</font>
|
||||
<font kerning="yes" embed-url="classpath:fonts/SourceSerifPro-BoldIt.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="italic" weight="700"/>
|
||||
</font>
|
||||
</fonts>
|
||||
<output-profile>classpath:AdobeCompat-v2.icc</output-profile>
|
||||
</renderer>
|
||||
</renderers>
|
||||
</fop>
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- Daily rollover -->
|
||||
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
|
||||
|
||||
<!-- Keep 5 years worth of history -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
</then>
|
||||
</if>
|
||||
|
||||
<!-- Configure so that it outputs to both console and log file -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="STDERR"/>
|
||||
</root>
|
||||
</configuration>
|
||||
<configuration>
|
||||
<appender name="STDERR"
|
||||
class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<appender name="FILE"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- Daily rollover -->
|
||||
<fileNamePattern>log/ZUV-%d{yyyy-MM}.log</fileNamePattern>
|
||||
<!-- Keep 5 years worth of history -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
</then>
|
||||
</if>
|
||||
<!-- Configure so that it outputs to both console and log file -->
|
||||
<if condition='property("FILE_APPENDER_ENABLED").contains("true")'>
|
||||
<then>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</then>
|
||||
</if>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDERR"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@
|
||||
<entry key="xr:Third_party_payment_type" id="BT-DEX-001">Art der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_amount" id="BT-DEX-002">Betrag der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_description" id="BT-DEX-003">Beschreibung der Fremdforderung</entry>
|
||||
<entry key="uebersicht">Übersicht</entry>
|
||||
<entry key="uebersicht">Daten der E-Rechnung</entry>
|
||||
<entry key="uebersichtKaeufer" id="BG-7">Informationen zum Käufer</entry>
|
||||
<entry key="uebersichtVerkaeufer" id="BG-4">Informationen zum Verkäufer</entry>
|
||||
<entry key="uebersichtRechnungsInfo" id="invoice-data">Rechnungsdaten</entry>
|
||||
@@ -214,7 +214,7 @@
|
||||
<entry key="uebersichtZahlungLastschrift" id="BG-19">Lastschrift</entry>
|
||||
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Überweisung</entry>
|
||||
<entry key="uebersichtBemerkungen" id="BG-1">Bemerkungen zur Rechnung</entry>
|
||||
<entry key="details">Details</entry>
|
||||
<entry key="details">Rechnungspositionen</entry>
|
||||
<entry key="detailsPositionAbrechnungszeitraum" id="BG-26">Abrechnungszeitraum</entry>
|
||||
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Preiseinzelheiten</entry>
|
||||
<entry key="detailsPositionNachlaesse" id="BG-27">Nachlässe auf Ebene der Rechnungsposition</entry>
|
||||
|
||||
377
library/src/main/resources/stylesheets/result-pdf.xsl
Normal file
377
library/src/main/resources/stylesheets/result-pdf.xsl
Normal file
@@ -0,0 +1,377 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
|
||||
xmlns:fo="http://www.w3.org/1999/XSL/Format"
|
||||
xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
version="2.0">
|
||||
<!-- Imports -->
|
||||
<xsl:import href="common-xr.xsl"/>
|
||||
<xsl:import href="xr-pdf/lib/konstanten.xsl"/>
|
||||
<!--
|
||||
FO engine used can be specified.
|
||||
Specific extensions will be then enabled.
|
||||
Supported values are:
|
||||
axf - Antenna House XSL Formatter
|
||||
fop - Apache FOP
|
||||
-->
|
||||
<xsl:param name="foengine"/>
|
||||
<xsl:param name="axf.extensions"
|
||||
select="if ($foengine eq 'axf') then true() else false()"/>
|
||||
<xsl:param name="fop.extensions"
|
||||
select="if ($foengine eq 'fop') then true() else false()"/>
|
||||
<xsl:variable name="xml_result_color">
|
||||
<xsl:if test="/validation/xml/summary/@status = 'valid'">
|
||||
<xsl:text>green</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
|
||||
<xsl:text>red</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="xml_result_text">
|
||||
<xsl:if test="/validation/xml/summary/@status = 'valid'">
|
||||
<xsl:text>Das XML ist valide.</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/xml/summary/@status = 'invalid'">
|
||||
<xsl:text>Das XML ist nicht valide.</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pdf_result_color">
|
||||
<xsl:if test="/validation/pdf/summary/@status = 'valid'">
|
||||
<xsl:text>green</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/pdf/summary/@status = 'invalid'">
|
||||
<xsl:text>red</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pdf_result_text">
|
||||
<xsl:if test="/validation/pdf/summary/@status = 'valid'">
|
||||
<xsl:text>Das ZUGFeRD-PDF ist valide.</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/pdf/summary/@status = 'invalid'">
|
||||
<xsl:text>Das ZUGFeRD-PDF ist nicht valide.</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="result_color">
|
||||
<xsl:if test="/validation/summary/@status = 'valid'">
|
||||
<xsl:text>green</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/summary/@status = 'invalid'">
|
||||
<xsl:text>red</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="result_text">
|
||||
<xsl:if test="/validation/summary/@status = 'valid'">
|
||||
<xsl:text>Es wird empfohlen, das Dokument anzunehmen und es weiterzuverarbeiten.</xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:if test="/validation/summary/@status = 'invalid'">
|
||||
<xsl:text>Es wird empfohlen, das Dokument zurückzuweisen.</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:template match="validation">
|
||||
<fo:root language="{$lang}"
|
||||
font-family="{$fontSans}">
|
||||
<fo:layout-master-set>
|
||||
<fo:simple-page-master master-name="DIN-A4"
|
||||
page-height="297mm"
|
||||
page-width="210mm">
|
||||
<fo:region-body region-name="body"
|
||||
margin="20mm 10mm 20mm 20mm"/>
|
||||
</fo:simple-page-master>
|
||||
</fo:layout-master-set>
|
||||
<fo:page-sequence master-reference="DIN-A4">
|
||||
<fo:flow flow-name="body">
|
||||
<fo:block font-size="24px"
|
||||
font-weight="bold">
|
||||
<xsl:text>Prüfbericht</xsl:text>
|
||||
</fo:block>
|
||||
<xsl:call-template name="SubHeader">
|
||||
<xsl:with-param name="text"
|
||||
select="'Angaben zum geprüften Dokument'"/>
|
||||
<xsl:with-param name="color"
|
||||
select="'black'"/>
|
||||
</xsl:call-template>
|
||||
<fo:table>
|
||||
<fo:table-column border-style="none"/>
|
||||
<fo:table-column column-width="70%"
|
||||
border-style="none"/>
|
||||
<fo:table-body>
|
||||
<fo:table-row>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="left">Referenz:</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="right">
|
||||
<xsl:value-of select="./@filename"/>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
<fo:table-row>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="left">Zeitpunkt der Prüfung:</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="right">
|
||||
<xsl:value-of select="./@datetime"/>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
<fo:table-row>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="left">Erkannter Dokumenttyp:</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block text-align="right">
|
||||
<xsl:call-template name="EnableLineBreaks">
|
||||
<xsl:with-param name="text"
|
||||
select="./xml/info/profile"/>
|
||||
<xsl:with-param name="separator"
|
||||
select="'#'"/>
|
||||
</xsl:call-template>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
</fo:table-body>
|
||||
</fo:table>
|
||||
<xsl:apply-templates select="./pdf"/>
|
||||
<xsl:apply-templates select="./xml"/>
|
||||
<!--
|
||||
<xsl:call-template name="SubHeader">
|
||||
<xsl:with-param name="text"
|
||||
select=="'Konformitätsprüfung:'"/>
|
||||
<xsl:with-param name="color"
|
||||
select=="'black'"/>
|
||||
</xsl:call-template>
|
||||
-->
|
||||
<xsl:call-template name="SubHeader">
|
||||
<xsl:with-param name="text"
|
||||
select="concat('Bewertung: ', $result_text)"/>
|
||||
<xsl:with-param name="color"
|
||||
select="$result_color"/>
|
||||
</xsl:call-template>
|
||||
<fo:block>Validierungsergebnisse im Detail:</fo:block>
|
||||
<fo:table>
|
||||
<fo:table-column border-style="solid"/>
|
||||
<fo:table-column border-style="solid"/>
|
||||
<fo:table-column border-style="solid"/>
|
||||
<fo:table-column border-style="solid"
|
||||
column-width="68%"/>
|
||||
<fo:table-header>
|
||||
<fo:table-row background-color="#E0E0E0"
|
||||
font-weight="bold"
|
||||
border-style="solid">
|
||||
<fo:table-cell>
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">Type</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">Code</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">Schwere</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">Text</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
</fo:table-header>
|
||||
<fo:table-body page-break-after="auto"
|
||||
page-break-before="auto"
|
||||
page-break-inside="avoid">
|
||||
<xsl:choose>
|
||||
<xsl:when test="./pdf/messages|./xml/messages|./messages">
|
||||
<xsl:apply-templates select="./pdf/messages|./xml/messages|./messages"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<fo:table-row border-style="solid"
|
||||
font-size="12px">
|
||||
<fo:table-cell number-columns-spanned="4">
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm"
|
||||
text-align="center">Es gibt keine Hinweise, Warnungen oder Fehler.</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</fo:table-body>
|
||||
</fo:table>
|
||||
</fo:flow>
|
||||
</fo:page-sequence>
|
||||
</fo:root>
|
||||
</xsl:template>
|
||||
<xsl:template match="notice|warning|error|exception|fatal">
|
||||
<xsl:variable name="msg_color">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name() = 'error' or name() = 'exception' or name() = 'fatal'">
|
||||
<xsl:text>red</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:when test="name() = 'warning'">
|
||||
<xsl:text>orange</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:text>black</xsl:text>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="msg_label">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name() = 'error' or name() = 'exception' or name() = 'fatal'">
|
||||
<xsl:text>Fehler</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:when test="name() = 'warning'">
|
||||
<xsl:text>Warnung</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:text>Hinweis</xsl:text>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="msg_text">
|
||||
<xsl:choose>
|
||||
<xsl:when test="contains(., ' [ID ')">
|
||||
<xsl:value-of select="substring-before(., ' [ID')"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:value-of select="."/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="rows-spanned">
|
||||
<xsl:choose>
|
||||
<xsl:when test="./@location">
|
||||
<xsl:value-of select="'2'"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:value-of select="'1'"/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<fo:table-row border-style="solid"
|
||||
font-size="12px"
|
||||
keep-with-previous="auto"
|
||||
keep-with-next="always">
|
||||
<fo:table-cell number-rows-spanned="{$rows-spanned}">
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">
|
||||
<xsl:value-of select="./@type"/>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block margin="0mm"
|
||||
padding="1mm">
|
||||
<xsl:value-of select="substring-before(substring-after(., ' [ID '), ']')"/>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block color="{$msg_color}"
|
||||
margin="0mm"
|
||||
padding="1mm">
|
||||
<xsl:value-of select="$msg_label"/>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
<fo:table-cell>
|
||||
<fo:block color="{$msg_color}"
|
||||
margin="0mm"
|
||||
padding="1mm">
|
||||
<xsl:call-template name="IntersperseWithZeroSpaces">
|
||||
<xsl:with-param name="str" select="$msg_text"/>
|
||||
</xsl:call-template>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
<xsl:if test="./@location">
|
||||
<fo:table-row border-style="solid"
|
||||
font-size="8px"
|
||||
keep-with-previous="always"
|
||||
keep-with-next="auto">
|
||||
<fo:table-cell number-columns-spanned="3">
|
||||
<fo:block color="{$msg_color}"
|
||||
margin="0mm"
|
||||
padding="1mm">
|
||||
<fo:block font-size="12px">
|
||||
<xsl:value-of select="'Pfad: '"/>
|
||||
</fo:block>
|
||||
<xsl:call-template name="EnableLineBreaks">
|
||||
<xsl:with-param name="text"
|
||||
select="./@location"/>
|
||||
<xsl:with-param name="separator"
|
||||
select="'/'"/>
|
||||
</xsl:call-template>
|
||||
</fo:block>
|
||||
</fo:table-cell>
|
||||
</fo:table-row>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
<xsl:template match="pdf">
|
||||
<xsl:call-template name="SubHeader">
|
||||
<xsl:with-param name="text"
|
||||
select="concat('ZUGFeRD-PDF: ', $pdf_result_text)"/>
|
||||
<xsl:with-param name="color"
|
||||
select="$pdf_result_color"/>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
<xsl:template match="xml">
|
||||
<xsl:call-template name="SubHeader">
|
||||
<xsl:with-param name="text"
|
||||
select="concat('E-Rechnung XML: ', $xml_result_text)"/>
|
||||
<xsl:with-param name="color"
|
||||
select="$xml_result_color"/>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
<xsl:template name="SubHeader">
|
||||
<xsl:param name="text"/>
|
||||
<xsl:param name="color"/>
|
||||
<fo:block color="{$color}"
|
||||
background-color="#E0E0E0"
|
||||
margin-bottom="10px"
|
||||
padding="3px"
|
||||
font-weight="bold"
|
||||
margin-top="10px">
|
||||
<xsl:value-of select="$text"/>
|
||||
</fo:block>
|
||||
</xsl:template>
|
||||
<xsl:template name="EnableLineBreaks">
|
||||
<xsl:param name="text"/>
|
||||
<xsl:param name="separator"/>
|
||||
<xsl:for-each select="tokenize($text, $separator)">
|
||||
<xsl:choose>
|
||||
<xsl:when test="position() eq 1">
|
||||
<fo:block>
|
||||
<xsl:value-of select="."/>
|
||||
</fo:block>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<fo:block>
|
||||
<xsl:value-of select="concat($separator, .)"/>
|
||||
</fo:block>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:for-each>
|
||||
</xsl:template>
|
||||
<xsl:template name="IntersperseWithZeroSpaces">
|
||||
<xsl:param name="str"/>
|
||||
<xsl:variable name="spacechars">
|
||||
	

|
||||
      
|
||||
     ​
|
||||
</xsl:variable>
|
||||
<xsl:if test="string-length($str) > 0">
|
||||
<xsl:variable name="c1" select="substring($str, 1, 1)"/>
|
||||
<xsl:variable name="c2" select="substring($str, 2, 1)"/>
|
||||
|
||||
<xsl:value-of select="$c1"/>
|
||||
<xsl:if test="$c2 != '' and
|
||||
not(contains($spacechars, $c1) or
|
||||
contains($spacechars, $c2))">
|
||||
<xsl:text>​</xsl:text>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:call-template name="IntersperseWithZeroSpaces">
|
||||
<xsl:with-param name="str" select="substring($str, 2)"/>
|
||||
</xsl:call-template>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
@@ -789,7 +789,7 @@
|
||||
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Deliver_to_location_identifier/@scheme_identifier">
|
||||
<xsl:with-param name="field-mapping-identifier" select="'xr:Deliver_to_location_identifier/@scheme_identifier'"/>
|
||||
</xsl:apply-templates>
|
||||
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Actual_delivery_date">
|
||||
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Actual_delivery_date">
|
||||
<xsl:with-param name="value" select="format-date(xr:DELIVERY_INFORMATION/xr:Actual_delivery_date, xrf:_('date-format'))"/>
|
||||
</xsl:apply-templates>
|
||||
<xsl:apply-templates mode="list-entry" select="xr:DELIVERY_INFORMATION/xr:Deliver_to_party_name"/>
|
||||
@@ -807,10 +807,11 @@
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="zusaetzeVertrag">
|
||||
<xsl:call-template name="box">
|
||||
<xsl:call-template name="spanned-box">
|
||||
<xsl:with-param name="identifier" select="'zusaetzeVertrag'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:call-template name="list">
|
||||
<xsl:with-param name="layout" select="'einspaltig'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Tender_or_lot_reference"/>
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Receiving_advice_reference"/>
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
<nummer>BT-64</nummer>
|
||||
</xsl:when>
|
||||
<xsl:when test="$identifier = 'xr:Tax_representative_address_line_2'">
|
||||
<label>Postfach</label>
|
||||
<label>Adresszusatz</label>
|
||||
<nummer>BT-65</nummer>
|
||||
</xsl:when>
|
||||
<xsl:when test="$identifier = 'xr:Tax_representative_address_line_3'">
|
||||
@@ -486,7 +486,7 @@
|
||||
<nummer>BT-75</nummer>
|
||||
</xsl:when>
|
||||
<xsl:when test="$identifier = 'xr:Deliver_to_address_line_2'">
|
||||
<label>Postfach</label>
|
||||
<label>Adresszusatz</label>
|
||||
<nummer>BT-76</nummer>
|
||||
</xsl:when>
|
||||
<xsl:when test="$identifier = 'xr:Deliver_to_address_line_3'">
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<dc:title><xsl:value-of select="xr:Invoice_number"/></dc:title>
|
||||
<dc:title><rdf:Alt><rdf:li xml:lang="x-default"><xsl:value-of select="xr:Invoice_number"/></rdf:li></rdf:Alt></dc:title>
|
||||
<!--
|
||||
<dc:creator></dc:creator>
|
||||
<dc:description></dc:description>
|
||||
|
||||
@@ -1,235 +1,235 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
|
||||
xmlns:xr="urn:ce.eu:en16931:2017:xoev-de:kosit:standard:xrechnung-1"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xrv="http://www.example.org/XRechnung-Viewer"
|
||||
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions"
|
||||
version="2.0">
|
||||
|
||||
<!-- ==========================================================================
|
||||
== Schriften
|
||||
=========================================================================== -->
|
||||
|
||||
<xsl:variable name="fontSans">SourceSerifPro</xsl:variable>
|
||||
<xsl:variable name="fontSerif">SourceSerifPro</xsl:variable>
|
||||
|
||||
<xsl:variable name="amount-picture" select="xrf:_('amount-format')"/>
|
||||
<xsl:variable name="percentage-picture" select="xrf:_('percentage-format')"/>
|
||||
|
||||
|
||||
<!-- ==========================================================================
|
||||
== Attribute-Sets
|
||||
=========================================================================== -->
|
||||
|
||||
<!-- Fliesstext -->
|
||||
<xsl:attribute-set name="fliesstext">
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">12pt</xsl:attribute>
|
||||
<xsl:attribute name="hyphenation-ladder-count">3</xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">true</xsl:attribute>
|
||||
<xsl:attribute name="language">de</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H1 Marker -->
|
||||
<xsl:attribute-set name="marker">
|
||||
<xsl:attribute name="font-size">18pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">false</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H1 -->
|
||||
<xsl:attribute-set name="h1">
|
||||
<xsl:attribute name="font-size">18pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">false</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H2-Container -->
|
||||
<xsl:attribute-set name="h2-container">
|
||||
<xsl:attribute name="border-top">0.5pt solid</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">2.5pt</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H2 -->
|
||||
<xsl:attribute-set name="h2">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="border">0.5pt solid</xsl:attribute>
|
||||
<xsl:attribute name="padding">4pt 6pt</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
<xsl:attribute name="margin">0</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H3 -->
|
||||
<xsl:attribute-set name="h3">
|
||||
<xsl:attribute name="font-size">10pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-top">2mm</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Separator -->
|
||||
<xsl:attribute-set name="separator">
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">3mm</xsl:attribute>
|
||||
<xsl:attribute name="border-bottom">0.5pt dotted</xsl:attribute>
|
||||
<xsl:attribute name="color">#999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Wert-Felder -->
|
||||
<xsl:variable name="wertHG">#eeeeee</xsl:variable>
|
||||
<xsl:variable name="wert-legende-breite">28</xsl:variable>
|
||||
|
||||
<xsl:attribute-set name="wert-legende">
|
||||
<xsl:attribute name="font-size">7pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">10pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="wert-ausgabe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">10pt</xsl:attribute>
|
||||
<xsl:attribute name="background-color"><xsl:value-of select="$wertHG"/></xsl:attribute>
|
||||
<xsl:attribute name="padding-top">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-together.within-column">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Rechnung-Felder -->
|
||||
<xsl:attribute-set name="rechnung-legende">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-steuer">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-wert">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">right</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">0mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-legende-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-steuer-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-wert-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="text-align">right</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">0mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Box-Container -->
|
||||
<xsl:attribute-set name="box-container-kapitel">
|
||||
<xsl:attribute name="margin-bottom">10mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="box-container-bereich">
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-together.within-page">always</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="box-container-inner">
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Tabular invoice lines -->
|
||||
<xsl:attribute-set name="invoicelines-table">
|
||||
<xsl:attribute name="padding-left">2pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2pt</xsl:attribute>
|
||||
<xsl:attribute name="width">100%</xsl:attribute>
|
||||
<xsl:attribute name="table-layout">fixed</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-table-header">
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-table-row">
|
||||
<!-- Nested sub-invoice lines will recursively decrease font-size -->
|
||||
<xsl:attribute name="font-size" select="if (self::xr:SUB_INVOICE_LINE) then '90%' else '100%'"/>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-nested-info">
|
||||
<xsl:attribute name="font-size">80%</xsl:attribute>
|
||||
<xsl:attribute name="font-style">italic</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-allowances-table">
|
||||
<xsl:attribute name="padding-left">2pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2pt</xsl:attribute>
|
||||
<xsl:attribute name="width">100%</xsl:attribute>
|
||||
<xsl:attribute name="table-layout">fixed</xsl:attribute>
|
||||
<xsl:attribute name="font-size">80%</xsl:attribute>
|
||||
<xsl:attribute name="font-style">italic</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
</xsl:stylesheet>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet xmlns:fo="http://www.w3.org/1999/XSL/Format"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
|
||||
xmlns:xr="urn:ce.eu:en16931:2017:xoev-de:kosit:standard:xrechnung-1"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xrv="http://www.example.org/XRechnung-Viewer"
|
||||
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions"
|
||||
version="2.0">
|
||||
|
||||
<!-- ==========================================================================
|
||||
== Schriften
|
||||
=========================================================================== -->
|
||||
|
||||
<xsl:variable name="fontSans">SourceSansPro</xsl:variable>
|
||||
<xsl:variable name="fontSerif">SourceSerifPro</xsl:variable>
|
||||
|
||||
<xsl:variable name="amount-picture" select="xrf:_('amount-format')"/>
|
||||
<xsl:variable name="percentage-picture" select="xrf:_('percentage-format')"/>
|
||||
|
||||
|
||||
<!-- ==========================================================================
|
||||
== Attribute-Sets
|
||||
=========================================================================== -->
|
||||
|
||||
<!-- Fliesstext -->
|
||||
<xsl:attribute-set name="fliesstext">
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">12pt</xsl:attribute>
|
||||
<xsl:attribute name="hyphenation-ladder-count">3</xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">true</xsl:attribute>
|
||||
<xsl:attribute name="language">de</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H1 Marker -->
|
||||
<xsl:attribute-set name="marker">
|
||||
<xsl:attribute name="font-size">18pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">false</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H1 -->
|
||||
<xsl:attribute-set name="h1">
|
||||
<xsl:attribute name="font-size">18pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSerif"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
|
||||
<xsl:attribute name="hyphenate">false</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H2-Container -->
|
||||
<xsl:attribute-set name="h2-container">
|
||||
<xsl:attribute name="border-top">0.5pt solid</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">2.5pt</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">4mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H2 -->
|
||||
<xsl:attribute-set name="h2">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="border">0.5pt solid</xsl:attribute>
|
||||
<xsl:attribute name="padding">4pt 6pt</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
<xsl:attribute name="margin">0</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- H3 -->
|
||||
<xsl:attribute-set name="h3">
|
||||
<xsl:attribute name="font-size">10pt</xsl:attribute>
|
||||
<xsl:attribute name="font-family"><xsl:value-of select="$fontSans"/></xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-top">2mm</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Separator -->
|
||||
<xsl:attribute-set name="separator">
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">3mm</xsl:attribute>
|
||||
<xsl:attribute name="border-bottom">0.5pt dotted</xsl:attribute>
|
||||
<xsl:attribute name="color">#999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Wert-Felder -->
|
||||
<xsl:variable name="wertHG">#eeeeee</xsl:variable>
|
||||
<xsl:variable name="wert-legende-breite">28</xsl:variable>
|
||||
|
||||
<xsl:attribute-set name="wert-legende">
|
||||
<xsl:attribute name="font-size">7pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">10pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="wert-ausgabe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="line-height">10pt</xsl:attribute>
|
||||
<xsl:attribute name="background-color"><xsl:value-of select="$wertHG"/></xsl:attribute>
|
||||
<xsl:attribute name="padding-top">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">1mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-together.within-column">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Rechnung-Felder -->
|
||||
<xsl:attribute-set name="rechnung-legende">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-steuer">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-wert">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">right</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">0mm</xsl:attribute>
|
||||
<xsl:attribute name="margin-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-legende-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-steuer-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="text-align">left</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">1mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="rechnung-wert-summe">
|
||||
<xsl:attribute name="font-size">9pt</xsl:attribute>
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
<xsl:attribute name="text-align">right</xsl:attribute>
|
||||
<xsl:attribute name="padding-top">0.4mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-bottom">0.2mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-left">0mm</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">0mm</xsl:attribute>
|
||||
<xsl:attribute name="border-top">0.1pt solid #999999</xsl:attribute>
|
||||
<xsl:attribute name="keep-with-next.within-page">always</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Box-Container -->
|
||||
<xsl:attribute-set name="box-container-kapitel">
|
||||
<xsl:attribute name="margin-bottom">10mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="box-container-bereich">
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
<xsl:attribute name="keep-together.within-page">always</xsl:attribute>
|
||||
<xsl:attribute name="span">all</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="box-container-inner">
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<!-- Tabular invoice lines -->
|
||||
<xsl:attribute-set name="invoicelines-table">
|
||||
<xsl:attribute name="padding-left">2pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2pt</xsl:attribute>
|
||||
<xsl:attribute name="width">100%</xsl:attribute>
|
||||
<xsl:attribute name="table-layout">fixed</xsl:attribute>
|
||||
<xsl:attribute name="margin-bottom">2mm</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-table-header">
|
||||
<xsl:attribute name="font-weight">bold</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-table-row">
|
||||
<!-- Nested sub-invoice lines will recursively decrease font-size -->
|
||||
<xsl:attribute name="font-size" select="if (self::xr:SUB_INVOICE_LINE) then '90%' else '100%'"/>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-nested-info">
|
||||
<xsl:attribute name="font-size">80%</xsl:attribute>
|
||||
<xsl:attribute name="font-style">italic</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
<xsl:attribute-set name="invoicelines-allowances-table">
|
||||
<xsl:attribute name="padding-left">2pt</xsl:attribute>
|
||||
<xsl:attribute name="padding-right">2pt</xsl:attribute>
|
||||
<xsl:attribute name="width">100%</xsl:attribute>
|
||||
<xsl:attribute name="table-layout">fixed</xsl:attribute>
|
||||
<xsl:attribute name="font-size">80%</xsl:attribute>
|
||||
<xsl:attribute name="font-style">italic</xsl:attribute>
|
||||
</xsl:attribute-set>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
||||
@@ -1193,7 +1193,7 @@ function downloadData (element_id) {
|
||||
<div id="BT-50" title="BT-50" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach (BT-51):</div>
|
||||
<div class="boxdaten legende">Adresszusatz (BT-51):</div>
|
||||
<div id="BT-51" title="BT-51" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -1258,7 +1258,7 @@ function downloadData (element_id) {
|
||||
<div id="BT-35" title="BT-35" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach (BT-36):</div>
|
||||
<div class="boxdaten legende">Adresszusatz (BT-36):</div>
|
||||
<div id="BT-36" title="BT-36" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -1998,7 +1998,7 @@ function downloadData (element_id) {
|
||||
<div id="BT-64" title="BT-64" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach (BT-65):</div>
|
||||
<div class="boxdaten legende">Adresszusatz (BT-65):</div>
|
||||
<div id="BT-65" title="BT-65" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -2111,7 +2111,7 @@ function downloadData (element_id) {
|
||||
<div id="BT-75" title="BT-75" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach (BT-76):</div>
|
||||
<div class="boxdaten legende">Adresszusatz (BT-76):</div>
|
||||
<div id="BT-76" title="BT-76" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2355
library/src/main/resources/stylesheets/xrechnung-html.univ.xsl
Normal file
2355
library/src/main/resources/stylesheets/xrechnung-html.univ.xsl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -122,7 +122,7 @@
|
||||
<div title="BT-50" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach:</div>
|
||||
<div class="boxdaten legende">Adresszusatz:</div>
|
||||
<div title="BT-51" class="boxdaten wert"><xsl:value-of select="xr:BUYER_POSTAL_ADDRESS/xr:Buyer_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -179,7 +179,7 @@
|
||||
<div title="BT-35" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach:</div>
|
||||
<div class="boxdaten legende">Adresszusatz:</div>
|
||||
<div title="BT-36" class="boxdaten wert"><xsl:value-of select="xr:SELLER_POSTAL_ADDRESS/xr:Seller_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -892,7 +892,7 @@
|
||||
<div title="BT-64" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach:</div>
|
||||
<div class="boxdaten legende">Adresszusatz:</div>
|
||||
<div title="BT-65" class="boxdaten wert"><xsl:value-of select="xr:SELLER_TAX_REPRESENTATIVE_POSTAL_ADDRESS/xr:Tax_representative_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
@@ -998,7 +998,7 @@
|
||||
<div title="BT-75" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_1"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
<div class="boxdaten legende">Postfach:</div>
|
||||
<div class="boxdaten legende">Adresszusatz:</div>
|
||||
<div title="BT-76" class="boxdaten wert"><xsl:value-of select="xr:DELIVER_TO_ADDRESS/xr:Deliver_to_address_line_2"/></div>
|
||||
</div>
|
||||
<div class="boxzeile">
|
||||
|
||||
@@ -52,6 +52,15 @@ public class BaseTest extends TestCase {
|
||||
assertEquals("12.00", XMLTools.nDigitFormat(new BigDecimal("12"),2));
|
||||
assertEquals("12", XMLTools.nDigitFormat(new BigDecimal("12"),0));
|
||||
assertEquals("20000123.342", XMLTools.nDigitFormat(new BigDecimal("20000123.3419"),3));
|
||||
|
||||
assertEquals("0.00", XMLTools.nDigitFormatDecimalRange(BigDecimal.ZERO,2, 2));
|
||||
assertEquals("-1.10", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.100000"), 4,2));
|
||||
assertEquals("-1.101", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.101000"),10, 3));
|
||||
assertEquals("-1.10", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.103"), 2,2));
|
||||
assertEquals("4", XMLTools.nDigitFormatDecimalRange(new BigDecimal("4"),2, 0));
|
||||
assertEquals("3.14", XMLTools.nDigitFormatDecimalRange(new BigDecimal("3.141526"),2, 0));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.mustangproject.validator;
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mustangproject.util.ByteArraySearcher;
|
||||
|
||||
public class ByteArraySearcherTest
|
||||
{
|
||||
@@ -3,28 +3,35 @@ package org.mustangproject.ZUGFeRD;
|
||||
import static java.math.BigDecimal.TEN;
|
||||
import static java.math.BigDecimal.valueOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mustangproject.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/***
|
||||
* tests the linecalculator and transactioncalculator classes
|
||||
*
|
||||
*/
|
||||
public class CalculationTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (CalculationTest.class);
|
||||
public class CalculationTest extends ResourceCase {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CalculationTest.class);
|
||||
|
||||
@Test
|
||||
public void testLineCalculator_simpleAmounts_resultInValidVATAmount() {
|
||||
final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16));
|
||||
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(100))
|
||||
.setQuantity(TEN)
|
||||
.setProduct(product);
|
||||
.setQuantity(TEN)
|
||||
.setProduct(product);
|
||||
|
||||
final LineCalculator calculator = new LineCalculator(currentItem);
|
||||
|
||||
@@ -41,15 +48,15 @@ public class CalculationTest {
|
||||
final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.8730));
|
||||
|
||||
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73))
|
||||
.setQuantity(valueOf(12))
|
||||
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
|
||||
.setProduct(product);
|
||||
.setQuantity(valueOf(12))
|
||||
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
|
||||
.setProduct(product);
|
||||
|
||||
final LineCalculator calculator = new LineCalculator(currentItem);
|
||||
|
||||
assertEquals(valueOf(133.857).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
|
||||
assertEquals(valueOf(1606.28).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(257.0048).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
|
||||
assertEquals(valueOf(1769.89).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(283.1824).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,16 +67,49 @@ public class CalculationTest {
|
||||
// 20 % charge
|
||||
final IZUGFeRDAllowanceCharge charge = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(29.746));
|
||||
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73))
|
||||
.setQuantity(valueOf(12))
|
||||
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
|
||||
.setItemCharges(new IZUGFeRDAllowanceCharge[]{charge})
|
||||
.setProduct(product);
|
||||
.setQuantity(valueOf(12))
|
||||
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
|
||||
.setItemCharges(new IZUGFeRDAllowanceCharge[]{charge})
|
||||
.setProduct(product);
|
||||
|
||||
final LineCalculator calculator = new LineCalculator(currentItem);
|
||||
|
||||
assertEquals(valueOf(163.603).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
|
||||
assertEquals(valueOf(1963.24).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(314.1184).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(148.73).stripTrailingZeros(), calculator.getPrice().stripTrailingZeros());
|
||||
assertEquals(valueOf(1799.63).stripTrailingZeros(), calculator.getItemTotalNetAmount().stripTrailingZeros());
|
||||
assertEquals(valueOf(287.9408).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLineCalculatorForeignCurrencyExample() {
|
||||
/*** xml of official fx sample with allowances and charges
|
||||
* 10x100 with 10% and 50€ item discount =850€
|
||||
* +8,75 charges on document level=858,75, +19%VAT=1021,91
|
||||
* prepaid 500->due payable=521,91
|
||||
*/
|
||||
File inputCII = getResourceAsFile("Extended_fremdwaehrung.xml");
|
||||
|
||||
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter();
|
||||
Invoice invoice=null;
|
||||
zii.doIgnoreCalculationErrors();
|
||||
boolean hasExceptions=false;
|
||||
try {
|
||||
zii.setInputStream(new FileInputStream(inputCII));
|
||||
|
||||
invoice=zii.extractInvoice();
|
||||
} catch (XPathExpressionException | ParseException e) {
|
||||
// handle Exceptions
|
||||
hasExceptions=true;
|
||||
} catch (FileNotFoundException e) {
|
||||
hasExceptions=true;
|
||||
}
|
||||
assertFalse(hasExceptions);
|
||||
// Reading ZUGFeRD
|
||||
|
||||
final TransactionCalculator calculator = new TransactionCalculator(invoice);
|
||||
|
||||
assertEquals(valueOf(521.91).stripTrailingZeros(), calculator.getDuePayable().stripTrailingZeros());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -184,8 +224,106 @@ public class CalculationTest {
|
||||
invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte"));
|
||||
}
|
||||
TransactionCalculator calculator = new TransactionCalculator(invoice);
|
||||
assertEquals(valueOf(101.86).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
|
||||
assertEquals(valueOf(307.18).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
|
||||
}
|
||||
|
||||
public void testSimpleItemPercentAllowance() {
|
||||
/***
|
||||
* a product with net 1.10 and qty 5 and relative item allowance of 10% should return 5 as line and grand total
|
||||
*/
|
||||
SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
Invoice invoice = new Invoice();
|
||||
invoice.setDocumentName("Rechnung");
|
||||
invoice.setNumber("777777");
|
||||
try {
|
||||
invoice.setIssueDate(sqlDate.parse("2020-12-31"));
|
||||
invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1]));
|
||||
invoice.setDeliveryDate(sqlDate.parse("2020-12-31"));
|
||||
invoice.setDueDate(sqlDate.parse("2021-01-15"));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to set dates", e);
|
||||
|
||||
}
|
||||
TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE");
|
||||
sender.addVATID("DE2222222222");
|
||||
invoice.setSender(sender);
|
||||
|
||||
/* trade party (recipient) */
|
||||
TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE");
|
||||
recipient.setID("111111");
|
||||
recipient.addVATID("DE111111111");
|
||||
invoice.setRecipient(recipient);
|
||||
|
||||
/* item */
|
||||
Product product;
|
||||
Item item;
|
||||
|
||||
product = new Product("AAA", "", "H84", BigDecimal.ZERO);
|
||||
item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00));
|
||||
|
||||
item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO));
|
||||
invoice.addItem(item);
|
||||
|
||||
TransactionCalculator calculator = new TransactionCalculator(invoice);
|
||||
assertEquals(new BigDecimal(5), calculator.getGrandTotal().stripTrailingZeros());
|
||||
}
|
||||
|
||||
public void testSimpleItemTotalAllowance() {
|
||||
/***
|
||||
* a product with net 1 and qty 5 and absolute _item_ allowance of 1 should return 4 as line total, and grand total
|
||||
*/
|
||||
SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
Invoice invoice = new Invoice();
|
||||
invoice.setDocumentName("Rechnung");
|
||||
invoice.setNumber("777777");
|
||||
try {
|
||||
invoice.setIssueDate(sqlDate.parse("2020-12-31"));
|
||||
invoice.setDetailedDeliveryPeriod(sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[0]), sqlDate.parse("2020-12-01 - 2020-12-31".split(" - ")[1]));
|
||||
invoice.setDeliveryDate(sqlDate.parse("2020-12-31"));
|
||||
invoice.setDueDate(sqlDate.parse("2021-01-15"));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to set dates", e);
|
||||
|
||||
}
|
||||
TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE");
|
||||
sender.addVATID("DE2222222222");
|
||||
invoice.setSender(sender);
|
||||
|
||||
/* trade party (recipient) */
|
||||
TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE");
|
||||
recipient.setID("111111");
|
||||
recipient.addVATID("DE111111111");
|
||||
invoice.setRecipient(recipient);
|
||||
|
||||
/* item */
|
||||
Product product;
|
||||
Item item;
|
||||
|
||||
product = new Product("AAA", "", "H84", BigDecimal.ZERO);
|
||||
item = new Item(product, new BigDecimal("1.00"), new BigDecimal(5.00));
|
||||
|
||||
item.addAllowance(new Allowance(new BigDecimal(1)).setTaxPercent(BigDecimal.ZERO));
|
||||
invoice.addItem(item);
|
||||
|
||||
TransactionCalculator calculator = new TransactionCalculator(invoice);
|
||||
assertEquals(new BigDecimal(4), calculator.getGrandTotal().stripTrailingZeros());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LineCalculator should not throw an exception when calculating a non-terminating decimal expansion
|
||||
* */
|
||||
@Test
|
||||
public void testNonTerminatingDecimalExpansion() {
|
||||
final Product product = new Product();
|
||||
final IZUGFeRDExportableItem currentItem = new Item().setPrice(valueOf(386.52))
|
||||
.setQuantity(BigDecimal.valueOf(31))
|
||||
.setBasisQuantity(BigDecimal.valueOf(366))
|
||||
.setProduct(product);
|
||||
final LineCalculator calculator = new LineCalculator(currentItem);
|
||||
assertEquals(BigDecimal.valueOf(32.74), calculator.getItemTotalNetAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -109,6 +109,16 @@ public class DXTest extends MustangReaderTestCase {
|
||||
return "DE99XX12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansCode() {
|
||||
return "54";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansInformation() {
|
||||
return "Credit Card";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,22 +21,43 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.experimental.theories.FromDataPoints;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.Allowance;
|
||||
import org.mustangproject.BankDetails;
|
||||
import org.mustangproject.CalculatedInvoice;
|
||||
import org.mustangproject.CashDiscount;
|
||||
import org.mustangproject.Charge;
|
||||
import org.mustangproject.Contact;
|
||||
import org.mustangproject.Invoice;
|
||||
import org.mustangproject.Item;
|
||||
import org.mustangproject.Product;
|
||||
import org.mustangproject.SchemedID;
|
||||
import org.mustangproject.TradeParty;
|
||||
import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class DeSerializationTest extends TestCase {
|
||||
public class DeSerializationTest extends ResourceCase {
|
||||
public void testJackson() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty("some org", "teststr", "55232", "teststadt", "DE").addTaxID("taxID").addBankDetails(new BankDetails("DE3600000123456", "ABCDEFG1001").setAccountName("Donald Duck"))).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber("0185").addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal("1"), new BigDecimal(1.0)));
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty("some org", "teststr", "55232", "teststadt", "DE").addTaxID("taxID").addBankDetails(new BankDetails("DE3600000123456", "ABCDEFG1001").setAccountName("Donald Duck")).setEmail("info@company.com")).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber("0185").addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal("1"), new BigDecimal(1.0)));
|
||||
String jsonArray = mapper.writeValueAsString(i);
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
@@ -45,6 +66,827 @@ public class DeSerializationTest extends TestCase {
|
||||
Invoice fromJSON = mapper.readValue(jsonArray, Invoice.class);
|
||||
assertEquals(fromJSON.getNumber(), i.getNumber());
|
||||
assertEquals(fromJSON.getZFItems().length, i.getZFItems().length);
|
||||
assertEquals("info@company.com", fromJSON.getSender().getEmail());
|
||||
assertEquals("info@company.com", fromJSON.getSender().getUriUniversalCommunicationID());
|
||||
|
||||
}
|
||||
|
||||
public void testInvoiceLine() throws JsonProcessingException {
|
||||
File inputCII = getResourceAsFile("factur-x.xml");
|
||||
boolean hasExceptions = false;
|
||||
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
|
||||
try {
|
||||
zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8));
|
||||
|
||||
} catch (IOException e) {
|
||||
hasExceptions = true;
|
||||
}
|
||||
|
||||
CalculatedInvoice ci = new CalculatedInvoice();
|
||||
try {
|
||||
zii.extractInto(ci);
|
||||
} catch (XPathExpressionException e) {
|
||||
hasExceptions = true;
|
||||
} catch (ParseException e) {
|
||||
hasExceptions = true;
|
||||
}
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String jsonArray = mapper.writeValueAsString(ci);
|
||||
assertFalse(hasExceptions);
|
||||
assertTrue(jsonArray.contains("lineTotalAmount"));
|
||||
|
||||
}
|
||||
|
||||
public void testAllowanceRead() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
|
||||
|
||||
Invoice fromJSON = mapper.readValue("{\n" +
|
||||
" \"documentCode\": \"380\",\n" +
|
||||
" \"number\": \"471102\",\n" +
|
||||
" \"ownOrganisationName\": \"Lieferant GmbH\",\n" +
|
||||
" \"currency\": \"EUR\",\n" +
|
||||
" \"issueDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
|
||||
" \"deliveryDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
|
||||
" \"detailedDeliveryPeriodFrom\": \"2017-03-03T13:00:00.000+00:00\",\n" +
|
||||
" \"detailedDeliveryPeriodTo\": \"2017-03-04T13:00:00.000+00: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" +
|
||||
" \"vatid\": \"DE123456789\"\n" +
|
||||
" },\n" +
|
||||
" \"recipient\": {\n" +
|
||||
" \"name\": \"Kunden AG Mitte\",\n" +
|
||||
" \"zip\": \"69876\",\n" +
|
||||
" \"street\": \"Kundenstraße 15\",\n" +
|
||||
" \"location\": \"Frankfurt\",\n" +
|
||||
" \"country\": \"DE\"\n" +
|
||||
" },\n" +
|
||||
" \"grandTotal\": 234.43,\n" +
|
||||
" \"zfitems\": [\n" +
|
||||
" {\n" +
|
||||
" \"price\": 9.9,\n" +
|
||||
" \"quantity\": 20,\n" +
|
||||
" \"tax\": null,\n" +
|
||||
" \"grossPrice\": null,\n" +
|
||||
" \"lineTotalAmount\": null,\n" +
|
||||
" \"basisQuantity\": 1,\n" +
|
||||
" \"detailedDeliveryPeriodFrom\": null,\n" +
|
||||
" \"detailedDeliveryPeriodTo\": null,\n" +
|
||||
" \"id\": null,\n" +
|
||||
" \"product\": {\n" +
|
||||
" \"unit\": \"H87\",\n" +
|
||||
" \"name\": \"Trennblätter A4\",\n" +
|
||||
" \"taxCategoryCode\": \"S\",\n" +
|
||||
" \"attributes\": null,\n" +
|
||||
" \"vatpercent\": 19\n" +
|
||||
" },\n" +
|
||||
" \"value\": 9.9\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"tradeSettlement\": null,\n" +
|
||||
" \"ownTaxID\": \"201/113/40209\",\n" +
|
||||
" \"ownVATID\": \"DE123456789\",\n" +
|
||||
" \"ownStreet\": \"Lieferantenstraße 20\",\n" +
|
||||
" \"ownZIP\": \"80333\",\n" +
|
||||
" \"ownLocation\": \"München\",\n" +
|
||||
" \"ownCountry\": \"DE\",\n" +
|
||||
" \"zfallowances\": [\n" +
|
||||
" {\n" +
|
||||
" \"totalAmount\": 1,\n" +
|
||||
" \"taxPercent\": 19,\n" +
|
||||
" \"reason\": \"Sondernachlass\",\n" +
|
||||
" \"reasonCode\": null,\n" +
|
||||
" \"categoryCode\": \"S\",\n" +
|
||||
" \"charge\": false\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}", Invoice.class);
|
||||
TransactionCalculator tc = new TransactionCalculator(fromJSON);
|
||||
assertEquals(tc.getGrandTotal(), new BigDecimal("234.43"));
|
||||
assertEquals(fromJSON.getNumber(), fromJSON.getNumber());
|
||||
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(fromJSON);
|
||||
String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8);
|
||||
|
||||
assertEquals(fromJSON.getZFItems().length, fromJSON.getZFItems().length);
|
||||
assertTrue(theXML.contains("20170303"));
|
||||
assertTrue(theXML.contains("20170304"));
|
||||
|
||||
}
|
||||
|
||||
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\",\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");
|
||||
|
||||
Invoice i = new Invoice();
|
||||
String exText = null;
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
|
||||
zii.doIgnoreCalculationErrors();
|
||||
try {
|
||||
zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8));
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
zii.extractInto(i);
|
||||
String json = mapper.writeValueAsString(i);
|
||||
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
|
||||
|
||||
assertEquals("Test_EeISI_100", i.getNumber());
|
||||
assertEquals(newInvoiceFromJSON.getNumber(), i.getNumber());
|
||||
|
||||
} catch (IOException e) {
|
||||
exText = e.getMessage();
|
||||
} catch (XPathExpressionException e) {
|
||||
exText = e.getMessage();
|
||||
} catch (ParseException e) {
|
||||
exText = e.getMessage();
|
||||
}
|
||||
assertNull(exText);
|
||||
|
||||
|
||||
}
|
||||
public void testNulledAttachments() {
|
||||
|
||||
String json="{\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" +
|
||||
"\"additionalReferencedDocuments\":null,"+
|
||||
" \"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();
|
||||
boolean exceptions=false;
|
||||
try {
|
||||
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
exceptions=true;
|
||||
}
|
||||
assertFalse(exceptions);
|
||||
}
|
||||
|
||||
public void testItemAllowances() {
|
||||
|
||||
String json="{\"number\":\"123\",\"currency\":\"EUR\",\"issueDate\":1738935176399,\"dueDate\":1738935176399,\"sender\":{\"name\":\"Test company\",\"zip\":\"55232\",\"street\":\"teststr\",\"location\":\"teststadt\",\"country\":\"DE\",\"taxID\":\"4711\",\"vatID\":\"DE0815\",\"vatid\":\"DE0815\"},\"recipient\":{\"name\":\"Franz Müller\",\"zip\":\"55232\",\"street\":\"teststr.12\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"contact\":{\"name\":\"contact testname\",\"phone\":\"123456\",\"email\":\"contact.testemail@example.org\",\"fax\":\"0911623562\"}},\"zfitems\":[{\"price\":3.00,\"quantity\":1,\"basisQuantity\":1,\"product\":{\"unit\":\"C62\",\"name\":\"Testprodukt\",\"taxCategoryCode\":\"S\",\"vatpercent\":19,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"itemAllowances\":[{\"totalAmount\":0.1,\"categoryCode\":\"S\"}],\"value\":3.00},{\"price\":3.00,\"quantity\":1,\"basisQuantity\":1,\"product\":{\"unit\":\"C62\",\"name\":\"Testprodukt\",\"taxCategoryCode\":\"S\",\"vatpercent\":19,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"itemAllowances\":[{\"percent\":50,\"taxPercent\":0,\"categoryCode\":\"S\"}],\"value\":3.00},{\"price\":3.00,\"quantity\":2,\"basisQuantity\":1,\"product\":{\"unit\":\"C62\",\"name\":\"Testprodukt\",\"taxCategoryCode\":\"S\",\"vatpercent\":19,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"itemCharges\":[{\"totalAmount\":1,\"reason\":\"AnotherReason\",\"reasonCode\":\"ABK\",\"categoryCode\":\"S\"}],\"value\":3.00},{\"price\":3.00,\"quantity\":1,\"basisQuantity\":1,\"product\":{\"unit\":\"C62\",\"name\":\"Testprodukt\",\"taxCategoryCode\":\"S\",\"vatpercent\":19,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"itemAllowances\":[{\"totalAmount\":1,\"categoryCode\":\"S\"}],\"itemCharges\":[{\"totalAmount\":1,\"categoryCode\":\"S\"}],\"value\":3.00}],\"ownStreet\":\"teststr\",\"ownCountry\":\"DE\",\"zfcharges\":[{\"totalAmount\":1,\"taxPercent\":19,\"reason\":\"AReason\",\"reasonCode\":\"ABK\",\"categoryCode\":\"S\"}],\"ownLocation\":\"teststadt\",\"ownTaxID\":\"4711\",\"ownZIP\":\"55232\",\"ownVATID\":\"DE0815\",\"valid\":true}";
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
|
||||
TransactionCalculator tc=new TransactionCalculator(newInvoiceFromJSON);
|
||||
assertEquals(new BigDecimal("18.92"),tc.getGrandTotal());
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void testIssuerAssignedIDRoundtrip() {
|
||||
String occurrenceFrom = "20201001";
|
||||
String occurrenceTo = "20201005";
|
||||
String contractID = "376zreurzu0983";
|
||||
|
||||
String orgID = "0009845";
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
String priceStr = "1.00";
|
||||
String taxID = "9990815";
|
||||
BigDecimal price = new BigDecimal(priceStr);
|
||||
Invoice newInvoiceFromJSON = null;
|
||||
boolean hasExceptions = false;
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
try {
|
||||
SchemedID gtin = new SchemedID("0160", "2001015001325");
|
||||
SchemedID gln = new SchemedID("0088", "4304171000002");
|
||||
Invoice i = new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.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"))
|
||||
.setDeliveryAddress(new TradeParty("just the other side of the street", "teststr.12a", "55232", "Entenhausen", "DE").addVATID("DE47110"))
|
||||
.setContractReferencedDocument(contractID)
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711")
|
||||
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3"))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).setId("a123").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
|
||||
.addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16)))
|
||||
.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);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = mapper.writeValueAsString(i);
|
||||
newInvoiceFromJSON = mapper.readValue(json, Invoice.class);
|
||||
} catch (ParseException e) {
|
||||
hasExceptions = true;
|
||||
} catch (JsonProcessingException e) {
|
||||
hasExceptions = true;
|
||||
}
|
||||
assertEquals(newInvoiceFromJSON.getBuyerOrderReferencedDocumentID(), "28934");
|
||||
assertFalse(hasExceptions);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void testDueDateRoundtrip() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
|
||||
|
||||
Invoice fromJSON = mapper.readValue("{\n" +
|
||||
" \"number\": \"RE - 228\",\n" +
|
||||
" \"currency\": \"EUR\",\n" +
|
||||
" \"issueDate\": \"2024-10-24\",\n" +
|
||||
" \"dueDate\": \"2024-10-26\",\n" +
|
||||
" \"deliveryDate\": \"2024-10-25\",\n" +
|
||||
" \"sender\": {\n" +
|
||||
" \"name\": \"Amazing Company\",\n" +
|
||||
" \"zip\": \"10000\",\n" +
|
||||
" \"street\": \"Straße der Kosmonauten 20\",\n" +
|
||||
" \"location\": \"Berlin\",\n" +
|
||||
" \"country\": \"DE\",\n" +
|
||||
" \"taxID\": \"201/113/40209\",\n" +
|
||||
" \"vatID\": \"DE123456789\",\n" +
|
||||
" \"globalID\": \"4000001123452\",\n" +
|
||||
" \"globalIDScheme\": \"0088\"\n" +
|
||||
" },\n" +
|
||||
" \"recipient\": {\n" +
|
||||
" \"name\": \"Amazing Company\",\n" +
|
||||
" \"zip\": \"1000\",\n" +
|
||||
" \"street\": \"Straße der Kosmonauten 10\",\n" +
|
||||
" \"location\": \"Berlin\",\n" +
|
||||
" \"taxID\": \"201/113/40209\",\n" +
|
||||
" \"vatID\": \"DE123456789\",\n" +
|
||||
" \"country\": \"DE\"\n" +
|
||||
" },\n" +
|
||||
" \"zfitems\": [\n" +
|
||||
" {\n" +
|
||||
" \"price\": 99.9,\n" +
|
||||
" \"quantity\": 10,\n" +
|
||||
" \"product\": {\n" +
|
||||
" \"unit\": \"H87\",\n" +
|
||||
" \"name\": \"Amazing Archives\",\n" +
|
||||
" \"description\": \"123\",\n" +
|
||||
" \"vatpercent\": \"19\",\n" +
|
||||
" \"taxCategoryCode\": \"3\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n", Invoice.class);
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(fromJSON);
|
||||
String theXML = new String(zf2p.getXML());
|
||||
assertTrue(theXML.contains("<udt:DateTimeString format=\"102\">20241026</udt:DateTimeString>"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testDirectDebit() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
|
||||
|
||||
boolean exceptions = false;
|
||||
String theXML = "";
|
||||
try {
|
||||
Invoice fromJSON = mapper.readValue("\n" +
|
||||
"\t{\n" +
|
||||
"\t\t\"documentCode\": \"380\",\n" +
|
||||
"\t\t\"number\": \"F20220031\",\n" +
|
||||
"\t\t\"referenceNumber\": \"SERVEXEC\",\n" +
|
||||
"\t\t\"buyerOrderReferencedDocumentID\": \"PO201925478\",\n" +
|
||||
"\t\t\"ownOrganisationName\": \"LE FOURNISSEUR\",\n" +
|
||||
"\t\t\"currency\": \"EUR\",\n" +
|
||||
"\t\t\"issueDate\": \"2022-01-30T23:00:00.000+00:00\",\n" +
|
||||
"\t\t\"dueDate\": \"2022-03-01T23:00:00.000+00:00\",\n" +
|
||||
"\t\t\"deliveryDate\": \"2022-01-27T23:00:00.000+00:00\",\n" +
|
||||
"\t\t\"sender\": {\n" +
|
||||
"\t\t\"name\": \"LE FOURNISSEUR\",\n" +
|
||||
"\t\t\t\"zip\": \"75018\",\n" +
|
||||
"\t\t\t\"street\": \"35 rue d'ici\",\n" +
|
||||
"\t\t\t\"location\": \"PARIS\",\n" +
|
||||
"\t\t\t\"country\": \"FR\",\n" +
|
||||
"\t\t\t\"vatID\": \"FR11123456782\",\n" +
|
||||
"\t\t\t\"additionalAddress\": \"Seller line 2\",\n" +
|
||||
"\t\t\t\"additionalAddressExtension\": \"Seller line 3\",\n" +
|
||||
"\t\t\t\"bankDetails\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"accountName\": null,\n" +
|
||||
"\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\",\n" +
|
||||
"\t\t\t\"bic\": \"BIC_MONCOMPTE\"\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"debitDetails\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"mandate\": \"MANDATE PT\",\n" +
|
||||
"\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\"\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"contact\": {\n" +
|
||||
"\t\t\t\"name\": \"M. CONTACT\",\n" +
|
||||
"\t\t\t\t\"phone\": \"01 02 03 54 87\",\n" +
|
||||
"\t\t\t\t\"email\": \"seller@seller.com\",\n" +
|
||||
"\t\t\t\t\"zip\": null,\n" +
|
||||
"\t\t\t\t\"street\": null,\n" +
|
||||
"\t\t\t\t\"location\": null,\n" +
|
||||
"\t\t\t\t\"country\": null,\n" +
|
||||
"\t\t\t\t\"fax\": null,\n" +
|
||||
"\t\t\t\t\"vatid\": null,\n" +
|
||||
"\t\t\t\t\"id\": null,\n" +
|
||||
"\t\t\t\t\"additionalAddress\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\"email\": \"moi@seller.com\",\n" +
|
||||
"\t\t\t\"vatid\": \"FR11123456782\",\n" +
|
||||
"\t\t\t\"id\": \"123\",\n" +
|
||||
"\t\t\t\"legalOrganisation\": {\n" +
|
||||
"\t\t\t\"schemedID\": null,\n" +
|
||||
"\t\t\t\t\"tradingBusinessName\": \"SELLER TRADE NAME\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\"globalID\": \"587451236587\",\n" +
|
||||
"\t\t\t\"globalIDScheme\": \"0088\"\n" +
|
||||
"\t},\n" +
|
||||
"\t\t\"recipient\": {\n" +
|
||||
"\t\t\"name\": \"LE CLIENT\",\n" +
|
||||
"\t\t\t\"zip\": \"06000\",\n" +
|
||||
"\t\t\t\"street\": \"MON ADRESSE LIGNE 1\",\n" +
|
||||
"\t\t\t\"location\": \"MA VILLE\",\n" +
|
||||
"\t\t\t\"country\": \"FR\",\n" +
|
||||
"\t\t\t\"vatID\": \"FR 05 987 654 321\",\n" +
|
||||
"\t\t\t\"additionalAddress\": \"Buyer line 2\",\n" +
|
||||
"\t\t\t\"additionalAddressExtension\": \"Buyer line 3\",\n" +
|
||||
"\t\t\t\"contact\": {\n" +
|
||||
"\t\t\t\"name\": \"Buyer contact name\",\n" +
|
||||
"\t\t\t\t\"phone\": \"01 01 25 45 87\",\n" +
|
||||
"\t\t\t\t\"email\": \"buyer@buyer.com\",\n" +
|
||||
"\t\t\t\t\"zip\": null,\n" +
|
||||
"\t\t\t\t\"street\": null,\n" +
|
||||
"\t\t\t\t\"location\": null,\n" +
|
||||
"\t\t\t\t\"country\": null,\n" +
|
||||
"\t\t\t\t\"fax\": null,\n" +
|
||||
"\t\t\t\t\"vatid\": null,\n" +
|
||||
"\t\t\t\t\"id\": null,\n" +
|
||||
"\t\t\t\t\"additionalAddress\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\"email\": \"me@buyer.com\",\n" +
|
||||
"\t\t\t\"vatid\": \"FR 05 987 654 321\",\n" +
|
||||
"\t\t\t\"legalOrganisation\": {\n" +
|
||||
"\t\t\t\"schemedID\": null,\n" +
|
||||
"\t\t\t\t\"tradingBusinessName\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\"globalID\": \"3654789851\",\n" +
|
||||
"\t\t\t\"globalIDScheme\": \"0088\"\n" +
|
||||
"\t},\n" +
|
||||
"\t\t\"deliveryAddress\": {\n" +
|
||||
"\t\t\"name\": \"DEL Name\",\n" +
|
||||
"\t\t\t\"zip\": \"06000\",\n" +
|
||||
"\t\t\t\"street\": \"DEL ADRESSE LIGNE 1\",\n" +
|
||||
"\t\t\t\"location\": \"NICE\",\n" +
|
||||
"\t\t\t\"country\": \"FR\",\n" +
|
||||
"\t\t\t\"additionalAddress\": \"DEL line 2\",\n" +
|
||||
"\t\t\t\"id\": \"PRIVATE_ID_DEL\"\n" +
|
||||
"\t},\n" +
|
||||
"\t\t\"payee\": {\n" +
|
||||
"\t\t\"name\": \"PAYEE NAME\",\n" +
|
||||
"\t\t\t\"legalOrganisation\": {\n" +
|
||||
"\t\t\t\"schemedID\": null,\n" +
|
||||
"\t\t\t\t\"tradingBusinessName\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\"globalID\": \"587451236586\",\n" +
|
||||
"\t\t\t\"globalIDScheme\": \"0088\"\n" +
|
||||
"\t},\n" +
|
||||
"\t\t\"sellerOrderReferencedDocumentID\": \"SALES REF 2547\",\n" +
|
||||
"\t\t\"despatchAdviceReferencedDocumentID\": \"DESPADV002\",\n" +
|
||||
"\t\t\"grandTotal\": 107.82,\n" +
|
||||
"\t\t\"detailedDeliveryPeriodFrom\": \"2021-12-31T23:00:00.000+00:00\",\n" +
|
||||
"\t\t\"detailedDeliveryPeriodTo\": \"2022-12-30T23:00:00.000+00:00\",\n" +
|
||||
"\t\t\"notesWithSubjectCode\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"FOURNISSEUR F SARL au capital de 50 000 EUR\",\n" +
|
||||
"\t\t\t\"subjectCode\": \"REG\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"RCS MAVILLE 123 456 782\",\n" +
|
||||
"\t\t\t\"subjectCode\": \"ABL\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"35 ma rue a moi, code postal Ville Pays – contact@masociete.fr - www.masociete.fr – N° TVA : FR32 123 456 789\",\n" +
|
||||
"\t\t\t\"subjectCode\": \"AAI\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"Tout retard de paiement engendre une pénalité exigible à compter de la date d'échéance, calculée sur la base de trois fois le taux d'intérêt légal.\",\n" +
|
||||
"\t\t\t\"subjectCode\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"Indemnité forfaitaire pour frais de recouvrement en cas de retard de paiement : 40 €.\",\n" +
|
||||
"\t\t\t\"subjectCode\": null\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"content\": \"Les réglements reçus avant la date d'échéance ne donneront pas lieu à escompte.\",\n" +
|
||||
"\t\t\t\"subjectCode\": null\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"zfallowances\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": null,\n" +
|
||||
"\t\t\t\"reasonCode\": \"100\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 2,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE VOLUME\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"71\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": null,\n" +
|
||||
"\t\t\t\"reasonCode\": \"100\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1.4,\n" +
|
||||
"\t\t\t\"taxPercent\": 20,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE COMMERCIALE\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"100\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1.2,\n" +
|
||||
"\t\t\t\"taxPercent\": 10,\n" +
|
||||
"\t\t\t\"reason\": \"REMISE COMMERCIALE\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"100\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"zfcharges\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": null,\n" +
|
||||
"\t\t\t\"reasonCode\": \"ADL\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 1,\n" +
|
||||
"\t\t\t\"taxPercent\": null,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS PALETTE\",\n" +
|
||||
"\t\t\t\"reasonCode\": null,\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 2.8,\n" +
|
||||
"\t\t\t\"taxPercent\": 20,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS DEPLACEMENT\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"FC\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"percent\": null,\n" +
|
||||
"\t\t\t\"totalAmount\": 0.6,\n" +
|
||||
"\t\t\t\"taxPercent\": 10,\n" +
|
||||
"\t\t\t\"reason\": \"FRAIS DEPLACEMENT\",\n" +
|
||||
"\t\t\t\"reasonCode\": \"ADR\",\n" +
|
||||
"\t\t\t\"categoryCode\": \"S\"\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"tradeSettlement\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"accountName\": null,\n" +
|
||||
"\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\",\n" +
|
||||
"\t\t\t\"bic\": \"BIC_MONCOMPTE\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"mandate\": \"MANDATE PT\",\n" +
|
||||
"\t\t\t\"iban\": \"FR20 1254 2547 2569 8542 5874 698\"\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"zfitems\": [\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"price\": 60,\n" +
|
||||
"\t\t\t\"quantity\": 1,\n" +
|
||||
"\t\t\t\"basisQuantity\": 1,\n" +
|
||||
"\t\t\t\"product\": {\n" +
|
||||
"\t\t\t\"unit\": \"C62\",\n" +
|
||||
"\t\t\t\t\"name\": \"REMBOURSEMENT AFFRANCHISSEMENT\",\n" +
|
||||
"\t\t\t\t\"description\": \"Description\",\n" +
|
||||
"\t\t\t\t\"taxCategoryCode\": \"Z\",\n" +
|
||||
"\t\t\t\t\"vatpercent\": 0,\n" +
|
||||
"\t\t\t\t\"reverseCharge\": false,\n" +
|
||||
"\t\t\t\t\"intraCommunitySupply\": false,\n" +
|
||||
"\t\t\t\t\"globalID\": \"598785412598745\",\n" +
|
||||
"\t\t\t\t\"globalIDScheme\": \"0160\"\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\t\"buyerOrderReferencedDocumentLineID\": \"1\",\n" +
|
||||
"\t\t\t\"value\": 60\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"price\": 30,\n" +
|
||||
"\t\t\t\"quantity\": 3,\n" +
|
||||
"\t\t\t\"basisQuantity\": 3,\n" +
|
||||
"\t\t\t\"product\": {\n" +
|
||||
"\t\t\t\"unit\": \"C62\",\n" +
|
||||
"\t\t\t\t\"name\": \"FOURNITURES DIVERSES\",\n" +
|
||||
"\t\t\t\t\"description\": \"Description\",\n" +
|
||||
"\t\t\t\t\"taxCategoryCode\": \"S\",\n" +
|
||||
"\t\t\t\t\"vatpercent\": 20,\n" +
|
||||
"\t\t\t\t\"reverseCharge\": false,\n" +
|
||||
"\t\t\t\t\"intraCommunitySupply\": false\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\t\"buyerOrderReferencedDocumentLineID\": \"3\",\n" +
|
||||
"\t\t\t\"value\": 30\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t{\n" +
|
||||
"\t\t\t\"price\": 12,\n" +
|
||||
"\t\t\t\"quantity\": 1,\n" +
|
||||
"\t\t\t\"basisQuantity\": 1,\n" +
|
||||
"\t\t\t\"product\": {\n" +
|
||||
"\t\t\t\"unit\": \"C62\",\n" +
|
||||
"\t\t\t\t\"name\": \"APPEL\",\n" +
|
||||
"\t\t\t\t\"description\": \"Description\",\n" +
|
||||
"\t\t\t\t\"taxCategoryCode\": \"S\",\n" +
|
||||
"\t\t\t\t\"vatpercent\": 10,\n" +
|
||||
"\t\t\t\t\"reverseCharge\": false,\n" +
|
||||
"\t\t\t\t\"intraCommunitySupply\": false\n" +
|
||||
"\t\t},\n" +
|
||||
"\t\t\t\"buyerOrderReferencedDocumentLineID\": \"2\",\n" +
|
||||
"\t\t\t\"value\": 12\n" +
|
||||
"\t\t}\n" +
|
||||
" ],\n" +
|
||||
"\t\t\"ownStreet\": \"35 rue d'ici\",\n" +
|
||||
"\t\t\"ownCountry\": \"FR\",\n" +
|
||||
"\t\t\"ownZIP\": \"75018\",\n" +
|
||||
"\t\t\"ownLocation\": \"PARIS\",\n" +
|
||||
"\t\t\"ownVATID\": \"FR11123456782\",\n" +
|
||||
"\t\t\"valid\": false\n" +
|
||||
"\t}\n", Invoice.class);
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(fromJSON);
|
||||
theXML = new String(zf2p.getXML());
|
||||
} catch (Exception e) {
|
||||
exceptions = true;
|
||||
}
|
||||
assertTrue(theXML.contains("pour frais de recouvrement en cas de retard de paiement"));
|
||||
assertFalse(exceptions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -214,8 +214,10 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
|
||||
public void testForeignImport() {
|
||||
InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf");
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream);
|
||||
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter();
|
||||
zi.doRecalculateItemPricesFromLineTotals();
|
||||
zi.doIgnoreCalculationErrors();
|
||||
zi.setInputStream(inputStream);
|
||||
// Reading ZUGFeRD
|
||||
String amount = zi.getAmount();
|
||||
|
||||
|
||||
@@ -106,6 +106,16 @@ public class OXTest extends MustangReaderTestCase {
|
||||
return "DE99XX12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansCode() {
|
||||
return "54";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansInformation() {
|
||||
return "Credit Card";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
Invoice i = new Invoice()
|
||||
.setIssueDate(new Date())
|
||||
.setSender(
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR")
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
|
||||
.addBankDetails(new BankDetails(ownIBAN, ownBIC)))
|
||||
.setRecipient(recipient)
|
||||
.setNumber(ownNumber)
|
||||
@@ -116,7 +116,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
.setIssueDate(new Date())
|
||||
.setDueDate(new Date())
|
||||
.setSender(
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR")
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
|
||||
.addBankDetails(new BankDetails(ownIBAN)))
|
||||
.setRecipient(recipient)
|
||||
.setNumber(ownNumber).setTotalPrepaidAmount(new BigDecimal("1"))
|
||||
@@ -137,7 +137,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM_INV);
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("145.37",zi.getAmount());
|
||||
assertEquals("146.37",zi.getAmount());
|
||||
// assertEquals(zi.getBIC(), ownBIC);
|
||||
// assertEquals(zi.getIBAN(), ownIBAN);
|
||||
assertEquals(ownOrgName, zi.getHolder());
|
||||
|
||||
@@ -123,35 +123,8 @@ public class UBLTest extends ResourceCase {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* public void testInvoiceImportOtherUBL() {
|
||||
|
||||
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_1_Teilrechnung.ubl.xml");
|
||||
// File UBLinputFile = getResourceAsFile("ubl/EN16931_2_Teilrechnung.ubl.xml");
|
||||
/* File UBLinputFile = getResourceAsFile("ubl/EN16931_AbweichenderZahlungsempf.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Betriebskostenabrechnung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Einfach.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Einfach_DueDate.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Einfach_negativePaymentDue.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Elektron.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_ElektronischeAdresse.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Gutschrift.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Haftpflichtversicherung_Versicherungssteuer.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Innergemeinschaftliche_Lieferungen.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Kraftfahrversicherung_Bruttopreise.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Miete.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_OEPNV.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Physiotherapeut.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Rabatte.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Rechnungskorrektur.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_RechnungsUebertragung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Reisekostenabrechnung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_Sachversicherung_berechneter_Steuersatz.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_SEPA_Prenotification.ubl.xml");
|
||||
|
||||
|
||||
|
||||
public void testEdgeInvoiceImportUBL2() {
|
||||
File UBLinputFile = getResourceAsFile("ubl/04.01a-INVOICE_ubl.xml");
|
||||
boolean hasExceptions = false;
|
||||
|
||||
ZUGFeRDInvoiceImporter zii = null;
|
||||
@@ -160,15 +133,12 @@ public class UBLTest extends ResourceCase {
|
||||
zii = new ZUGFeRDInvoiceImporter(new FileInputStream(UBLinputFile));
|
||||
invoice = zii.extractInvoice();
|
||||
} catch (XPathExpressionException | ParseException | FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
hasExceptions = true;
|
||||
}
|
||||
assertFalse(hasExceptions);
|
||||
// Reading ZUGFeRD
|
||||
assertEquals(new BigDecimal("288.79"), invoice.getZFItems()[0].getPrice());
|
||||
assertEquals("04011000-12345-03", invoice.getReferenceNumber());
|
||||
assertEquals("seller@email.de", invoice.getSender().getContact().getEMail());
|
||||
assertEquals("12345", invoice.getRecipient().getZIP());
|
||||
assertEquals("DE75512108001245126199", invoice.getSender().getBankDetails().get(0).getIBAN());
|
||||
|
||||
}*/
|
||||
assertEquals(new BigDecimal("10000.0"), invoice.getTotalPrepaidAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,174 +1,176 @@
|
||||
|
||||
/** **********************************************************************
|
||||
*
|
||||
* Copyright 2019 Jochen Staerk
|
||||
*
|
||||
* Use is subject to license terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.*;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class VisualizationTest extends ResourceCase {
|
||||
|
||||
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","");
|
||||
|
||||
File expectedResult=getResourceAsFile("factur-x-vis.fr.html");
|
||||
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r","").replace("\n","");
|
||||
// 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);
|
||||
}
|
||||
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(" ","").replace("\t","");
|
||||
|
||||
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(" ","").replace("\t","");
|
||||
// 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);
|
||||
}
|
||||
public void testUBLVisualizationBasic() {
|
||||
|
||||
// the writing part
|
||||
File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.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","");
|
||||
|
||||
File expectedResult=getResourceAsFile("factur-x-vis-ubl.en.html");
|
||||
expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r","").replace("\n","");
|
||||
// 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);
|
||||
}
|
||||
|
||||
/* public void testPDFVisualization() {
|
||||
|
||||
// the writing part
|
||||
CIIToUBL c2u = new CIIToUBL();
|
||||
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)
|
||||
*
|
||||
zvi.toPDF(CIIinputFile.getAbsolutePath(), "c:\\users\\jstaerk\\temp\\fopy2.pdf");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
fail("UnsupportedOperationException should not happen: "+e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
fail("IllegalArgumentException should not happen: "+e.getMessage());
|
||||
}
|
||||
|
||||
// assertNotNull(result);
|
||||
// Reading ZUGFeRD
|
||||
// assertEquals(expected, result);
|
||||
}*/
|
||||
|
||||
}
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import junit.framework.TestCase;
|
||||
import org.mustangproject.*;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
|
||||
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
@@ -52,12 +53,17 @@ public class XRTest extends TestCase {
|
||||
TradeParty recipient = new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE");
|
||||
recipient.setEmail("quack@ducktown.org");
|
||||
Invoice i = createInvoice(recipient);
|
||||
|
||||
String legalOrgID = "aCustomSellerLegalOrgId";
|
||||
String sellerID = "aSellerTradePartyID";
|
||||
i.getSender().setLegalOrganisation(new LegalOrganisation(legalOrgID));
|
||||
i.getSender().setID(sellerID);
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(i);
|
||||
String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8);
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
assertTrue(theXML.contains("<ram:ID>" + sellerID + "</ram:ID>"));// must be possible without scheme #
|
||||
assertTrue(theXML.contains("<ram:ID>" + legalOrgID + "</ram:ID>"));// must be possible without scheme #
|
||||
assertThat(theXML).valueByXPath("count(//*[local-name()='IncludedSupplyChainTradeLineItem'])")
|
||||
.asInt()
|
||||
.isEqualTo(1); //2 errors are OK because there is a known bug
|
||||
@@ -76,6 +82,7 @@ public class XRTest extends TestCase {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testXREdgeExport() {
|
||||
|
||||
// the writing part
|
||||
@@ -88,13 +95,14 @@ public class XRTest extends TestCase {
|
||||
|
||||
FileAttachment fe1 = new FileAttachment("one.pdf", "application/pdf", "Alternative", b);
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX")))
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX").setAccountName("kontoInhaber")))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setEmail("recipient@sample.org"))
|
||||
.addCashDiscount(new CashDiscount(new BigDecimal(2), 7))
|
||||
.addCashDiscount(new CashDiscount(new BigDecimal(3), 14))
|
||||
.setReferenceNumber("991-01484-64")//leitweg-id
|
||||
// not using any VAT, this is also a test of zero-rated goods:
|
||||
.setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO), amount, new BigDecimal(1.0)))
|
||||
.setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO).setTaxExemptionReason("Kleinunternehmer"), amount, new BigDecimal(1.0)))
|
||||
.setPayee(new TradeParty().setName("VR Factoring GmbH").setID("DE813838785").setLegalOrganisation(new LegalOrganisation("391200LDDFJDMIPPMZ54", "0199")))
|
||||
.embedFileInXML(fe1);
|
||||
|
||||
|
||||
@@ -109,6 +117,9 @@ public class XRTest extends TestCase {
|
||||
.asInt()
|
||||
.isEqualTo(1); //2 errors are OK because there is a known bug
|
||||
|
||||
assertThat(theXML).valueByXPath("count(//*[local-name()='PayeeTradeParty'])")
|
||||
.asInt()
|
||||
.isEqualTo(1);
|
||||
|
||||
assertThat(theXML).valueByXPath("//*[local-name()='DuePayableAmount']")
|
||||
.asDouble()
|
||||
@@ -125,19 +136,18 @@ public class XRTest extends TestCase {
|
||||
Invoice readInvoice = new Invoice();
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
|
||||
try {
|
||||
|
||||
zii.setRawXML(zf2p.getXML());
|
||||
zii.setRawXML(zf2p.getXML(), false);
|
||||
zii.extractInto(readInvoice);
|
||||
} catch (ParseException | XPathExpressionException xp) {
|
||||
fail("Exception not expected");
|
||||
fail("ParseException not expected");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
List<FileAttachment> attachedFiles=zii.getFileAttachmentsXML();
|
||||
assertNotNull(attachedFiles);
|
||||
assertEquals(attachedFiles.size(), 1);
|
||||
fail("IOException not expected");
|
||||
}
|
||||
FileAttachment[] attachedFiles = readInvoice.getAdditionalReferencedDocuments();
|
||||
assertNotNull(attachedFiles);
|
||||
assertEquals(attachedFiles.length, 1);
|
||||
|
||||
assertTrue(Arrays.equals(attachedFiles.get(0).getData(), b));
|
||||
assertTrue(Arrays.equals(attachedFiles[0].getData(), b));
|
||||
|
||||
|
||||
}
|
||||
@@ -170,6 +180,66 @@ public class XRTest extends TestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testTaxExemptionReasonIssue() {
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
String amountStr = "1.00";
|
||||
BigDecimal amount = new BigDecimal(amountStr);
|
||||
byte[] b = {12, 13};
|
||||
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").setEmail("sender@example.com").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX").setAccountName("kontoInhaber")))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setEmail("recipient@sample.org"))
|
||||
.setReferenceNumber("991-01484-64")//leitweg-id
|
||||
// not using any VAT, this is also a test of zero-rated goods:
|
||||
.setNumber(number)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO).setTaxCategoryCode("E").setTaxExemptionReason("Kleinunternehmer"), amount, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt2", "", "C62", BigDecimal.ZERO).setTaxCategoryCode("S"), amount, new BigDecimal(1.0)))
|
||||
.setPayee( new TradeParty().setName("VR Factoring GmbH").setID("DE813838785").setLegalOrganisation(new LegalOrganisation("391200LDDFJDMIPPMZ54", "0199")));
|
||||
|
||||
|
||||
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(i);
|
||||
String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8);
|
||||
assertThat(theXML).valueByXPath("count(//*[local-name()='ExemptionReason'])")
|
||||
.asInt()
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
|
||||
public void testApplicablePercentInUntaxedService() {
|
||||
|
||||
// the writing part
|
||||
TradeParty recipient = new TradeParty("Franz Müller", null, "55232", "Entenhausen", "DE");
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
String amountStr = "1.00";
|
||||
BigDecimal amount = new BigDecimal(amountStr);
|
||||
var i = new Invoice().setDueDate(new java.util.Date()).setIssueDate(new java.util.Date()).setDeliveryDate(new java.util.Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("DE4711").addVATID("DE0815").setEmail("info@example.org").setContact(new org.mustangproject.Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new org.mustangproject.BankDetails("DE12500105170648489890", "COBADEFXXX")))
|
||||
.setRecipient(recipient)
|
||||
.setReferenceNumber("991-01484-64")//leitweg-id
|
||||
// not using any VAT, this is also a test of zero-rated goods:
|
||||
.setNumber(number).addItem(new org.mustangproject.Item(new org.mustangproject.Product("Testprodukt", "", "C62", java.math.BigDecimal.ZERO).setTaxCategoryCode(TaxCategoryCodeTypeConstants.UNTAXEDSERVICE).setTaxExemptionReason("Expemtion reason"), amount, new java.math.BigDecimal(1.0)));
|
||||
|
||||
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
|
||||
zf2p.setProfile(Profiles.getByName("XRechnung"));
|
||||
zf2p.generateXML(i);
|
||||
String theXML = new String(zf2p.getXML(), StandardCharsets.UTF_8);
|
||||
// Untaxed services don't have the field RateApplicablePercent, since it would be always 0. An error is thrown on validation, if 0 is set.
|
||||
assertThat(theXML).valueByXPath("count(//*[local-name()='RateApplicablePercent'])")
|
||||
.asInt()
|
||||
.isEqualTo(0);
|
||||
|
||||
//Exemption reason needs to be set if TaxCategoryCode == "O", reason should be at the product and in the ApplicableTradeTax
|
||||
assertThat(theXML).valueByXPath("count(//*[local-name()='ExemptionReason'])")
|
||||
.asInt()
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
private org.mustangproject.Invoice createInvoice(TradeParty recipient) {
|
||||
String orgname = "Test company";
|
||||
|
||||
@@ -100,6 +100,16 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
|
||||
return "DE99XX12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansCode() {
|
||||
return "54";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentMeansInformation() {
|
||||
return "Credit Card";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,13 +240,11 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
return
|
||||
new PaymentTerms(
|
||||
"14 Tage 2% Skonto, 30 Tage rein netto",
|
||||
due,// fälligkeitsdatum
|
||||
paymentDiscountTerms //PaymentDiscountTerms
|
||||
);
|
||||
|
||||
return new PaymentTerms(
|
||||
"14 Tage 2% Skonto, 30 Tage rein netto",
|
||||
due,// fälligkeitsdatum
|
||||
paymentDiscountTerms //PaymentDiscountTerms
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -297,7 +305,8 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
|
||||
InputStream SOURCE_PDF = this.getClass()
|
||||
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf");
|
||||
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1();ze.ignorePDFAErrors();
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1();
|
||||
ze.ignorePDFAErrors();
|
||||
ze.load(SOURCE_PDF);
|
||||
ze.setProducer("My Application")
|
||||
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("Extended"));
|
||||
@@ -313,7 +322,8 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF);
|
||||
String resultXML=zi.getUTF8();
|
||||
assertTrue(resultXML.contains("<ram:TypeCode>59</ram:TypeCode>"));
|
||||
assertTrue(resultXML.contains("<ram:TypeCode>54</ram:TypeCode>"));
|
||||
assertTrue(resultXML.contains("<ram:Information>Credit Card</ram:Information>"));
|
||||
assertTrue(resultXML.contains("<ram:ShipToTradeParty>"));
|
||||
assertTrue(resultXML.contains("<ram:IBANID>DE540815</ram:IBANID>"));
|
||||
assertTrue(resultXML.contains("<ram:ApplicableTradePaymentDiscountTerms"));
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
@@ -29,7 +30,9 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.mustangproject.*;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
@@ -56,13 +59,14 @@ public class ZF2PushTest extends TestCase {
|
||||
final String TARGET_ATTACHMENTSPDF = "./target/testout-ZF2PushAttachments.pdf";
|
||||
final String TARGET_BANKPDF = "./target/testout-ZF2PushBank.pdf";
|
||||
final String TARGET_PUSHEDGE = "./target/testout-ZF2PushEdge.pdf";
|
||||
final String TARGET_INTRACOMMUNITYSUPPLYMANUALPDF = "./target/testout-ZF2PushIntraCommunitySupplyManual.pdf";
|
||||
final String TARGET_INTRACOMMUNITYSUPPLYPDF = "./target/testout-ZF2PushIntraCommunitySupply.pdf";
|
||||
final String TARGET_REVERSECHARGEPDF = "./target/testout-ZF2PushReverseCharge.pdf";
|
||||
|
||||
public void testPushExport() {
|
||||
/***
|
||||
* This writes to a filename like an official sample, please consider when changing (probably better not?)
|
||||
*/
|
||||
/***
|
||||
* This writes to a filename like an official sample, please consider when changing (probably better not?)
|
||||
*/
|
||||
// the writing part
|
||||
String orgname = "Bei Spiel GmbH";
|
||||
String number = "RE-20201121/508";
|
||||
@@ -87,6 +91,7 @@ public class ZF2PushTest extends TestCase {
|
||||
.addItem(new Item(new Product("Design (hours)", "Of a sample invoice", "HUR", new BigDecimal(7)), price, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Ballons", "various colors, ~2000ml", "H87", new BigDecimal(19)), new BigDecimal("0.79"), new BigDecimal(400.0)))
|
||||
.addItem(new Item(new Product("Hot air „heiße Luft“ (litres)", "", "LTR", new BigDecimal(19)), new BigDecimal("0.025"), new BigDecimal(800.0)))
|
||||
.setRoundingAmount(new BigDecimal("1"))
|
||||
);
|
||||
|
||||
ze.export(TARGET_PDF);
|
||||
@@ -94,6 +99,15 @@ public class ZF2PushTest extends TestCase {
|
||||
fail("Exception should not be raised");
|
||||
}
|
||||
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
Invoice i = new Invoice();
|
||||
try {
|
||||
zii.extractInto(i);
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
@@ -125,8 +139,9 @@ public class ZF2PushTest extends TestCase {
|
||||
String number = "123";
|
||||
String priceStr = "1.00";
|
||||
|
||||
String senderDescription = "Kein Kleinunternehmer";
|
||||
String senderDescription = "Kleinunternehmer";
|
||||
String taxID = "9990815";
|
||||
String theNote = "oh lala";
|
||||
BigDecimal price = new BigDecimal(priceStr);
|
||||
try {
|
||||
InputStream SOURCE_PDF = this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf");
|
||||
@@ -144,7 +159,7 @@ public class ZF2PushTest extends TestCase {
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711")
|
||||
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE")))
|
||||
.setNumber(number)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E"), price, new BigDecimal(1.0)).addNote(theNote))
|
||||
);
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
@@ -152,8 +167,8 @@ public class ZF2PushTest extends TestCase {
|
||||
} catch (IOException e) {
|
||||
fail("IOException should not be raised");
|
||||
}
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PDF);
|
||||
Invoice i= null;
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_ATTACHMENTSPDF);
|
||||
Invoice i = null;
|
||||
try {
|
||||
i = zii.extractInvoice();
|
||||
} catch (XPathExpressionException e) {
|
||||
@@ -161,7 +176,9 @@ public class ZF2PushTest extends TestCase {
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
assertEquals(senderDescription,i.getSender().getDescription());
|
||||
|
||||
assertEquals(i.getZFItems()[0].getNotesWithSubjectCode().get(0).getContent(), theNote);
|
||||
assertEquals(senderDescription, i.getSender().getDescription());
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ATTACHMENTSPDF);
|
||||
@@ -170,7 +187,7 @@ public class ZF2PushTest extends TestCase {
|
||||
assertTrue(zi.getUTF8().contains(taxID));
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("1.19", zi.getAmount());
|
||||
assertEquals("1.00", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
try {
|
||||
@@ -232,20 +249,24 @@ public class ZF2PushTest extends TestCase {
|
||||
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1();
|
||||
ze.ignorePDFAErrors().load(SOURCE_PDF);
|
||||
ze.setProfile(Profiles.getByName("Extended"));
|
||||
|
||||
ze.setProducer("My Application").setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("extended");
|
||||
// ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname,"teststr", "55232","teststadt","DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number)
|
||||
// .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50)))));
|
||||
|
||||
ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date())
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815"))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")
|
||||
.setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562")))
|
||||
.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))))
|
||||
.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"))))
|
||||
);
|
||||
.addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("AReason").setTaxPercent(new BigDecimal(19)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance(new BigDecimal("0.1")).setReasonCode("95")))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50)).setReason("In love with salesperson")))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(2.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("AnotherReason")))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addCharge(new Charge(new BigDecimal(1)).setReasonCode("ABK").setReason("Yet another reason")).addAllowance(new Allowance(new BigDecimal("1")).setReason("Something completely strange")));
|
||||
ze.setTransaction(i);
|
||||
|
||||
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
@@ -254,25 +275,28 @@ public class ZF2PushTest extends TestCase {
|
||||
fail("IOException should not be raised");
|
||||
}
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ITEMCHARGESALLOWANCESPDF);
|
||||
|
||||
assertEquals("EUR", zi.getInvoiceCurrencyCode());
|
||||
assertTrue(zi.getUTF8().contains("0911623562")); // fax number
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("18.33", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
try {
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_ITEMCHARGESALLOWANCESPDF);
|
||||
assertEquals("EUR", zi.getInvoiceCurrencyCode());
|
||||
assertTrue(zi.getUTF8().contains("0911623562")); // fax number
|
||||
assertTrue(zi.getUTF8().contains("ABK"));
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("18.92", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
assertEquals(zi.getVersion(), 2);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail("Exception should not be raised");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testIntraCommunitySupplyExport() {
|
||||
/***
|
||||
* you can activate intra community suppliy on item level
|
||||
*/
|
||||
public void testIntraCommunitySupplyItemExport() {
|
||||
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
@@ -288,7 +312,7 @@ public class ZF2PushTest extends TestCase {
|
||||
// .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)).addAllowance(new Allowance().setPercent(new BigDecimal(50)))));
|
||||
|
||||
ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addVATID("DE0815").addTaxID ("4711"))
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addVATID("DE0815").addTaxID("4711"))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816")
|
||||
.setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562")))
|
||||
.setDeliveryAddress(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816"))
|
||||
@@ -323,6 +347,68 @@ public class ZF2PushTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* or manually, in which case the transaction needs a delivery address outside DE, and the items a 0 tax with reason K and reason code
|
||||
*/
|
||||
|
||||
public void testIntraCommunitySupplyManualExport() {
|
||||
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
String priceStr = "1.00";
|
||||
|
||||
String taxID = "9990815";
|
||||
BigDecimal price = new BigDecimal(priceStr);
|
||||
try {
|
||||
InputStream SOURCE_PDF = this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf");
|
||||
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1();
|
||||
ze.ignorePDFAErrors();
|
||||
ze.load(SOURCE_PDF);
|
||||
ze.setProducer("My Application").setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2);
|
||||
|
||||
ze.setTransaction(new Invoice().setDeliveryAddress(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "FR")).setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).addVATID("DE0815"))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "FR").addVATID("DE4711")
|
||||
|
||||
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE")))
|
||||
.setNumber(number)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)).setTaxExemptionReason("Kein Ausweis der Umsatzsteuer bei innergemeinschaftlichen Lieferungen").setTaxCategoryCode("K"), price, new BigDecimal(1.0)))
|
||||
);
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
ze.export(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF);
|
||||
} catch (IOException e) {
|
||||
fail("IOException should not be raised");
|
||||
}
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF);
|
||||
Invoice i = null;
|
||||
try {
|
||||
i = zii.extractInvoice();
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_INTRACOMMUNITYSUPPLYMANUALPDF);
|
||||
|
||||
assertEquals("EUR", zi.getInvoiceCurrencyCode());
|
||||
assertTrue(zi.getUTF8().contains(taxID));
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("1.00", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
try {
|
||||
assertEquals(zi.getVersion(), 2);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testReverseChargeExport() {
|
||||
|
||||
String orgname = "Test company";
|
||||
@@ -394,8 +480,8 @@ public class ZF2PushTest extends TestCase {
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)))
|
||||
.addCharge(new Charge(new BigDecimal(0.5)).setTaxPercent(new BigDecimal(19)))
|
||||
.addAllowance(new Allowance(new BigDecimal(0.2)).setTaxPercent(new BigDecimal(19)))
|
||||
.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"))
|
||||
);
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
@@ -446,7 +532,7 @@ public class ZF2PushTest extends TestCase {
|
||||
try {
|
||||
SchemedID gtin = new SchemedID("0160", "2001015001325");
|
||||
SchemedID gln = new SchemedID("0088", "4304171000002");
|
||||
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())
|
||||
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")
|
||||
.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"))
|
||||
@@ -454,11 +540,12 @@ public class ZF2PushTest extends TestCase {
|
||||
.setContractReferencedDocument(contractID)
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addGlobalID(gln).setEmail("recipient@test.org").addVATID("DE4711")
|
||||
.setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE").setFax("++49555123456")).setAdditionalAddress("Hinterhaus 3"))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)).addGlobalID(gtin).setSellerAssignedID("4711"), price, new BigDecimal(1.0)).addBuyerOrderReferencedDocumentID("orderId").addBuyerOrderReferencedDocumentLineID("xxx").addReferencedLineID("xxx").addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))).setDetailedDeliveryPeriod(sdf.parse("2020-01-13"), sdf.parse("2020-01-15")))
|
||||
.addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16)))
|
||||
.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").addInvoiceReferencedDocument(new ReferencedDocument("abcd1234"))
|
||||
);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
@@ -487,7 +574,7 @@ public class ZF2PushTest extends TestCase {
|
||||
assertTrue(zi.getUTF8().contains(occurrenceFrom));
|
||||
assertTrue(zi.getUTF8().contains(occurrenceTo));
|
||||
assertTrue(zi.getUTF8().contains(contractID));
|
||||
|
||||
assertEquals(zi.importedInvoice.getZFItems()[0].getId(), "a123");
|
||||
assertTrue(zi.getUTF8().contains("20200113")); // to contain item delivery periods
|
||||
assertTrue(zi.getUTF8().contains("20200115")); // to contain item delivery periods
|
||||
|
||||
@@ -498,14 +585,23 @@ public class ZF2PushTest extends TestCase {
|
||||
assertTrue(zi.getUTF8().contains("++49555123456"));
|
||||
assertTrue(zi.getUTF8().contains("Cash Discount")); // default description for cash discounts
|
||||
assertThat(zi.getUTF8()).valueByXPath("//*[local-name()='ApplicableTradeTax']/*[local-name()='DueDateTypeCode']").asString().isEqualTo(EventTimeCodeTypeConstants.PAYMENT_DATE);
|
||||
assertTrue(zi.getUTF8().contains("<ram:Name>Rechnung</ram:Name>"));
|
||||
|
||||
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(TARGET_PUSHEDGE);
|
||||
try {
|
||||
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());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
assertEquals(occurrenceFrom, sdf.format(i.getDetailedDeliveryPeriodFrom()));
|
||||
assertEquals(occurrenceTo, sdf.format(i.getDetailedDeliveryPeriodTo()));
|
||||
assertEquals("2001015001325", i.getZFItems()[0].getProduct().getGlobalID());
|
||||
assertEquals(orgID, i.getSender().getID());
|
||||
assertEquals("Verwendungszweck", i.getPaymentReference());
|
||||
assertEquals("Rechnung", i.getDocumentName());
|
||||
|
||||
} catch (XPathExpressionException e) {
|
||||
fail("XPathExpressionException should not be raised");
|
||||
@@ -533,7 +629,7 @@ public class ZF2PushTest extends TestCase {
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815"))
|
||||
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE"))
|
||||
.setNumber(number)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19))))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)).addAllowance(new Allowance(BigDecimal.ONE)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19))))
|
||||
.addAllowance(new Allowance(new BigDecimal(600)).setTaxPercent(new BigDecimal(19)))
|
||||
);
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
@@ -549,7 +645,7 @@ public class ZF2PushTest extends TestCase {
|
||||
assertEquals("EUR", zi.getInvoiceCurrencyCode());
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("4046.00", zi.getAmount());
|
||||
assertEquals("10805.20", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
try {
|
||||
@@ -579,8 +675,8 @@ public class ZF2PushTest extends TestCase {
|
||||
.setNumber(number)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19))))
|
||||
.addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", 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"))
|
||||
);
|
||||
String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
@@ -593,7 +689,7 @@ public class ZF2PushTest extends TestCase {
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_RELATIVECHARGESALLOWANCESPDF);
|
||||
|
||||
assertEquals("CHF", zi.getInvoiceCurrencyCode());
|
||||
assertEquals("6.25", zi.getAmount());
|
||||
assertEquals("11.10", zi.getAmount());
|
||||
assertEquals(orgname, zi.getHolder());
|
||||
assertEquals(number, zi.getForeignReference());
|
||||
try {
|
||||
@@ -719,4 +815,5 @@ public class ZF2PushTest extends TestCase {
|
||||
match the read grand total */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
|
||||
/** **********************************************************************
|
||||
*
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <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 java.io.IOException;
|
||||
@@ -99,9 +101,9 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
public IZUGFeRDExportableItem[] getZFItems() {
|
||||
final Item[] allItems = new Item[3];
|
||||
final Product designProduct = new Product("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung", "HUR",
|
||||
new BigDecimal("7.000000"));
|
||||
new BigDecimal("7.000000"));
|
||||
final Product balloonProduct = new Product("", "Bestellerweiterung für E&F Umbau", "C62",
|
||||
new BigDecimal("19.000000"));// test for issue 103
|
||||
new BigDecimal("19.000000"));// test for issue 103
|
||||
final Product airProduct = new Product("", "Heiße Luft pro Liter", "LTR", new BigDecimal("19.000000"));
|
||||
|
||||
allItems[0] = new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct);
|
||||
@@ -166,12 +168,12 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
// the writing part
|
||||
|
||||
try (InputStream SOURCE_PDF = this.getClass()
|
||||
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
|
||||
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
|
||||
|
||||
ZUGFeRDExporterFromA3 ze = new ZUGFeRDExporterFromA3().setProducer("My Application")
|
||||
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("EN16931")
|
||||
.load(SOURCE_PDF)) {
|
||||
|
||||
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile("EN16931")
|
||||
.load(SOURCE_PDF)) {
|
||||
|
||||
ze.setTransaction(this);
|
||||
final String theXML = new String(ze.getProvider().getXML());
|
||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||
@@ -190,13 +192,13 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
assertEquals(zi.getInvoiceID(), "RE-20170509/505");
|
||||
assertEquals(zi.getZUGFeRDProfil(), "COMFORT");
|
||||
assertEquals(zi.getInvoiceCurrencyCode(), "EUR");
|
||||
assertEquals(zi.getIssuerAssignedID(),"");
|
||||
assertEquals(zi.getIssuerAssignedID(), "");
|
||||
assertEquals(zi.getIssueDate(), "20170509");
|
||||
assertEquals(zi.getTaxPointDate(), "20170507");
|
||||
assertEquals(zi.getPaymentTerms(), "Zahlbar ohne Abzug bis zum 30.05.2017");
|
||||
assertEquals(zi.getLineTotalAmount(), "496.00");
|
||||
assertEquals(zi.getTaxBasisTotalAmount(), "496.00");
|
||||
assertEquals(zi.getTaxTotalAmount(),"75.04");
|
||||
assertEquals(zi.getTaxTotalAmount(), "75.04");
|
||||
assertEquals(zi.getRoundingAmount(), "");
|
||||
assertEquals(zi.getPaidAmount(), "0.00");
|
||||
assertEquals(zi.getBuyerTradePartyName(), "Theodor Est");
|
||||
@@ -206,8 +208,8 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
assertEquals(zi.getBuyertradePartySpecifiedTaxRegistrationID(), "DE999999999");
|
||||
assertEquals(zi.getIncludedNote(), "");
|
||||
assertEquals(zi.getHolder(), getOwnOrganisationName());
|
||||
assertEquals(zi.getDocumentCode(),"380");
|
||||
assertEquals(zi.getReference(),"AB321");
|
||||
assertEquals(zi.getDocumentCode(), "380");
|
||||
assertEquals(zi.getReference(), "AB321");
|
||||
assertEquals(zi.getAmount(), "571.04");
|
||||
assertEquals(zi.getBIC(), "COBADEFFXXX");
|
||||
assertEquals(zi.getIBAN(), "DE88 2008 0000 0970 3757 00");
|
||||
@@ -244,7 +246,9 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
} /**
|
||||
}
|
||||
|
||||
/**
|
||||
* The exporter test bases on @{code
|
||||
* ./src/test/MustangBeispiel20221026.pdf}, adds
|
||||
* metadata, writes to @{code ./target/testout-*} and then imports to check the
|
||||
@@ -264,7 +268,7 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
assertEquals("Innerhalb von 30 Tagen 2% Skonto, 60 Tage ohne Abzug", zi.getPaymentTerms());
|
||||
assertEquals("804.35", zi.getLineTotalAmount());
|
||||
assertEquals("809.34", zi.getTaxBasisTotalAmount());
|
||||
assertEquals("153.77",zi.getTaxTotalAmount());
|
||||
assertEquals("153.77", zi.getTaxTotalAmount());
|
||||
assertEquals("", zi.getRoundingAmount());
|
||||
assertEquals("0.00", zi.getPaidAmount());
|
||||
assertEquals("Beispiel AG", zi.getBuyerTradePartyName());
|
||||
@@ -273,8 +277,8 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
assertEquals("10000", zi.getBuyerTradePartyID());
|
||||
assertEquals("\n weclapp.com\nSomestreet 42\n08155 Some city\nDE\n ", zi.getIncludedNote());
|
||||
assertEquals("weclapp.com", zi.getHolder());
|
||||
assertEquals("380",zi.getDocumentCode());
|
||||
assertEquals("01-95",zi.getReference());
|
||||
assertEquals("380", zi.getDocumentCode());
|
||||
assertEquals("01-95", zi.getReference());
|
||||
assertEquals("RE1001", zi.getForeignReference());
|
||||
assertEquals("54321", zi.getBuyerTradePartyAddress().getPostcodeCode());
|
||||
assertEquals("Feldstraße 34", zi.getBuyerTradePartyAddress().getLineOne());
|
||||
@@ -284,13 +288,13 @@ public class ZF2Test extends MustangReaderTestCase {
|
||||
assertEquals("DE", zi.getBuyerTradePartyAddress().getCountryID());
|
||||
assertEquals("Hithausen", zi.getBuyerTradePartyAddress().getCityName());
|
||||
assertEquals("Beispiel Lager AG", zi.getDeliveryTradePartyName());
|
||||
assertEquals("54321", zi.getDeliveryTradePartyAddress().getPostcodeCode());
|
||||
assertEquals("Feldstraße 39", zi.getDeliveryTradePartyAddress().getLineOne());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getLineTwo());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getLineThree());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getCountrySubDivisionName());
|
||||
assertEquals("DE", zi.getDeliveryTradePartyAddress().getCountryID());
|
||||
assertEquals("Hithausen", zi.getDeliveryTradePartyAddress().getCityName());
|
||||
assertEquals("54321", zi.getDeliveryTradePartyAddress().getPostcodeCode());
|
||||
assertEquals("Feldstraße 39", zi.getDeliveryTradePartyAddress().getLineOne());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getLineTwo());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getLineThree());
|
||||
assertEquals(null, zi.getDeliveryTradePartyAddress().getCountrySubDivisionName());
|
||||
assertEquals("DE", zi.getDeliveryTradePartyAddress().getCountryID());
|
||||
assertEquals("Hithausen", zi.getDeliveryTradePartyAddress().getCityName());
|
||||
assertEquals("08155", zi.getSellerTradePartyAddress().getPostcodeCode());
|
||||
assertEquals("Somestreet 42", zi.getSellerTradePartyAddress().getLineOne());
|
||||
assertEquals(null, zi.getSellerTradePartyAddress().getLineTwo());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user