Merge remote-tracking branch 'remotes/origin/master' into master

This commit is contained in:
Sebastian Sieber
2020-11-17 15:14:38 +01:00
36 changed files with 1048 additions and 415 deletions

View File

@@ -1,5 +1,18 @@
## 2.0
### done
### 2.0.1 todo
- confirm that VAT category code switches from S to Z on 0%VAT
- 2.1 support kleinunternehmer, reverse charge?
- dont show empty tax number field
- fail when no bankverbindung?
- fail when xr attrs missing?
- build xr skonto???
- *validator not to XR error on ZF files (only notices)
- xmp errors may not show correctly in log
- XR test now includes guideline ID #172
- support zero-rated goods
2.0.0
=====
2020-11-12
- support for ZF 2.1.1, i.e. "Reference profile" Xrechnung
- ZF 2.1.1 now default (up to 1.7.8 ZF2 could be set but ZF1 was default)
@@ -51,16 +64,8 @@ switch
- contacts also for recipients
- absolute and relative allowances and charges on item and document level #135,
- support contact fax numbers
- closes #190 BOM not treated correctly on XML input file
### 2.0 still todo
- dont show empty tax number field
- new sample invoice
- *visualizer to work with Extended profile
- *visualizer tests
- *validator not to XR error on ZF files (only notices)
- *from A3 does not seem tow ork see mustangreaderwriteredgetest:testedgeexport
- xmp errors may not show correctly in log
- finalize invoiceimporter
Alpha3 2020-10-24
Alpha2 2020-09-15
Alpha1 2020-08-06

View File

@@ -47,21 +47,22 @@ public class Main {
private static String getUsage() {
return "Usage: --action metrics|combine|extract|a3only|validate|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] | [-h,--help] \r\n"
+ "* merics\n" + " -d, --directory count ZUGFeRD files in directory to be scanned\n"
+ " --action=metrics\n"
+ " -d, --directory count ZUGFeRD files in directory to be scanned\n"
+ " If it is a directory, it will recurse.\n"
+ " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n"
+ " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n"
+ " It will start once a blank line has been entered.\n" + "\n"
+ " Additional parameter for both count operations\n"
+ " [-i, --ignorefileextension] Check for all files (*.*) instead of PDF files only (*.pdf) in metrics, ignore PDF/A input file errors in combine\n"
+ "\n" + "* Merge actions\n" + " extract extract ZUGFeRD PDF to XML file\n"
+ " --action=extract extract ZUGFeRD PDF to XML file\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source=<filename>]: set input PDF file\n"
+ " [--out=<filename>]: set output XML file\n"
+ " a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n"
+ " --action=a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source=<filename>]: set input PDF file\n"
+ " [--out=<filename>]: set output PDF file\n"
+ " combine combine XML and PDF file to ZUGFeRD PDF file\n"
+ " --action=combine combine XML and PDF file to ZUGFeRD PDF file\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source=<filename>]: set input PDF file\n"
+ " [--source-xml=<filename>]: set input XML file\n"
@@ -70,25 +71,25 @@ public class Main {
+ " [--version <1|2>]: set ZUGFeRD version\n"
+ " [--profile <...>]: set ZUGFeRD profile\n"
+ " For ZUGFeRD v1: <B>ASIC, <C>OMFORT or EX<T>ENDED\n"
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED "
+ " upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED \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"
+ " validate validate XML or PDF file \n"
+ " --action=validate validate XML or PDF file \n"
+ " [--no-notices]: refrain from reporting notices\n"
+ " [--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"
+ " validateExpectValid validate directory expecting positive results \n"
+ " --action=validateExpectValid validate directory expecting positive results \n"
+ " [--no-notices]: refrain from reporting notices\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " -d, --directory to check recursively \n"
+ " validateExpectInvalid validate directory expecting negative results \n"
+ " --action=validateExpectInvalid validate directory expecting negative results \n"
+ " [--no-notices]: refrain from reporting notices\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " -d, --directory to check recursively\n"
+ " visualize convert XML to HTML \n"
+ " --action=visualize convert XML to HTML \n"
;
}

View File

@@ -19,24 +19,13 @@ More information in [the mustang documentation](https://github.com/ZUGFeRD/musta
Usage
-----
If you setup a Maven project, you can grab the artifacts from this maven repository.
```xml
<repositories>
<repository>
<id>mustang-mvn-repo</id>
<url>https://raw.github.com/ZUGFeRD/mustangproject/mvn-repo/</url>
</repository>
</repositories>
```
As dependency use this
If you setup a Maven project, you can grab the artifacts using
```xml
<dependency>
<groupId>org.mustangproject.ZUGFeRD</groupId>
<artifactId>mustang</artifactId>
<groupId>org.mustangproject</groupId>
<artifactId>library</artifactId>
<version>2.0.0</version>
</dependency>
```

View File

@@ -1,7 +1,7 @@
## Changes
On it's official website you can [download](https://www.mustangproject.org/files/Mustang-CLI-2.0.0-alpha3.jar) a alpha release of Mustang 2.
On it's official website you can [download](https://www.mustangproject.org/deploy/Mustang-CLI-2.0.0.jar) Mustang 2.
* Factur-X/ZUGFeRD 2 is now the default. In Mustangproject 1.x one had to select ZUGFeRD version 2 if that was desired, in Mustangproject 2 one now has to select ZUGFeRD version 1 if version 2 is not appropriate.
* Mustang is now available via Maven Central, which makes it even easier to use the Mustang library. Apart from making it easier in Maven because there is no longer the need to mention the custom repository, it also makes it possible to use Mustang in Gradle projects
@@ -31,38 +31,8 @@ to for non-interactive (i.e., batch) processing.
The source file parameter for validation changed
from `-f` (ZUV) to the usual `--source`. The following
result codes apply:
from `-f` (ZUV) to the usual `--source`.
| section | meaning |
|---|---|
| 1 | file not found |
| 2 | additional data schema validation fails |
| 3 | xml data not found |
| 4 | schematron rule failed |
| 5 | file too small |
| 6 | VeraPDFException |
| 7 | IOException PDF |
| 8 | File does not look like PDF nor XML (contains neither %PDF nor <?xml) |
| 9 | IOException XML |
| 11 | XMP Metadata: ConformanceLevel not found |
| 12 | XMP Metadata: ConformanceLevel contains invalid value |
| 13 | XMP Metadata: DocumentType not found |
| 14 | XMP Metadata: DocumentType invalid |
| 15 | XMP Metadata: Version not found |
| 16 | XMP Metadata: Version contains invalid value |
| 18 | schema validation failed |
| 19 | XMP Metadata: DocumentFileName contains invalid value |
| 20 | not a pdf |
| 21 | XMP Metadata: DocumentFileName not found") |
| 22 | generic XML validation exception |
| 23 | Not a PDF/A-3 |
| 24 | Issues in CEN EN16931 Schematron Check |
| 25 | Unsupported profile type |
| 26 | No rules matched, XML to minimal? |
| 27 | XRechnung Schematron Check |
### Use as Library
We're now on maven central, please remove the old github repository. Additionally, the following changed
@@ -145,7 +115,7 @@ but there is also the new invoiceImporter
assertEquals("Spielkreis", invoice.getRecipient().getLocation());
TransactionCalculator tc=new TransactionCalculator(invoice);
assertEquals(new BigDecimal("571.040000"),tc.getTotalGross());
assertEquals(new BigDecimal("571.04"),tc.getTotalGross());
```
@@ -230,7 +200,7 @@ read/write ZUGFeRD-invoices like the (smaller) library module.
<dependency>
<groupId>org.mustangproject</groupId>
<artifactId>validator</artifactId>
<version>2.0.0-alpha3</version>
<version>2.0.0</version>
</dependency>
</dependencies>

View File

@@ -131,6 +131,7 @@
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.mustangproject.toecount.Toecount</mainClass>

View File

@@ -2,6 +2,9 @@ package org.mustangproject;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
/**
* provides e.g. the IBAN to transfer money to :-)
*/
public class BankDetails implements IZUGFeRDTradeSettlementPayment {
protected String IBAN, BIC;
@@ -14,6 +17,14 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
return IBAN;
}
/**
* Sets the IBAN "ID", which means that it only needs to be a way to uniquely
* 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
*/
public BankDetails setIBAN(String IBAN) {
this.IBAN = IBAN;
return this;
@@ -28,12 +39,17 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
return this;
}
/***
* getOwn... methods will be removed in the future in favor of Tradeparty (e.g. Sender) class
* */
@Override
@Deprecated
public String getOwnBIC() {
return getBIC();
}
@Override
@Deprecated
public String getOwnIBAN() {
return getIBAN();
}

View File

@@ -2,17 +2,39 @@ package org.mustangproject;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
/***
* a named contact person in an organisation
* for the organisation/company itsel please
* @see TradeParty
*/
public class Contact implements IZUGFeRDExportableContact {
protected String name,phone,email,zip,street,location,country;
protected String fax=null;
/***
* default constructor.
* Name, phone and email of sender contact person are e.g. required by XRechnung
* @param name full name of the contact
* @param phone full phone number
* @param email email address of the contact
*/
public Contact(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
/***
* complete specification of a named contact with a different address
* @param name full name
* @param phone full phone number
* @param email full email
* @param street street+number
* @param zip postcode
* @param location city
* @param country two-letter iso code
*/
public Contact(String name, String phone, String email, String street, String zip, String location, String country) {
this.name = name;
this.phone = phone;
@@ -29,6 +51,11 @@ public class Contact implements IZUGFeRDExportableContact {
return name;
}
/**
* the first and last name of the contact
* @param name first and last name
* @return fluent setter
*/
public Contact setName(String name) {
this.name = name;
return this;
@@ -39,6 +66,11 @@ public class Contact implements IZUGFeRDExportableContact {
return phone;
}
/***
* complete phone number of the contact
* @param phone the complete phone number
* @return fluent setter
*/
public Contact setPhone(String phone) {
this.phone = phone;
return this;
@@ -49,6 +81,11 @@ public class Contact implements IZUGFeRDExportableContact {
return fax;
}
/***
* (optional) complete fax number
* @param fax complete fax number of the contact
* @return fluent setter
*/
public Contact setFax(String fax) {
this.fax = fax;
return this;
@@ -58,6 +95,11 @@ public class Contact implements IZUGFeRDExportableContact {
return email;
}
/***
* personal email address of the contact person
* @param email the email address of the contact
* @return fluent setter
*/
public Contact setEMail(String email) {
this.email = email;
return this;
@@ -67,6 +109,11 @@ public class Contact implements IZUGFeRDExportableContact {
return zip;
}
/***
* the postcode, if the address is different to the organisation
* @param zip the postcode of the contact
* @return fluent setter
*/
public Contact setZIP(String zip) {
this.zip = zip;
return this;
@@ -77,6 +124,11 @@ public class Contact implements IZUGFeRDExportableContact {
return street;
}
/**
* street and number, if the address is different to the organisation
* @param street street and number of the contact
* @return fluent setter
*/
public Contact setStreet(String street) {
this.street = street;
return this;
@@ -87,6 +139,11 @@ public class Contact implements IZUGFeRDExportableContact {
return location;
}
/***
* city of the contact person, if different from organisation
* @param location city
* @return fluent setter
*/
public Contact setLocation(String location) {
this.location = location;
return this;
@@ -97,6 +154,11 @@ public class Contact implements IZUGFeRDExportableContact {
return country;
}
/***
* two-letter ISO country code of the contact, if different from organisation
* @param country two-letter iso code
* @return fluent setter
*/
public Contact setCountry(String country) {
this.country = country;
return this;

View File

@@ -36,13 +36,13 @@ 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, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
protected Date issueDate = null, dueDate = null, deliveryDate = null;
protected BigDecimal totalPrepaidAmount = null;
protected TradeParty sender=null, recipient = null, deliveryAddress = null;
protected TradeParty sender = null, recipient = null, deliveryAddress = null;
protected ArrayList<IZUGFeRDExportableItem> ZFItems = null;
protected ArrayList<String> notes = null;
protected String contractReferencedDocument = null;
protected Date occurrenceDateFrom = null;
protected Date occurrenceDateTo = null;
protected Date detailedDeliveryDateStart = null;
protected Date detailedDeliveryPeriodEnd = null;
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<IZUGFeRDAllowanceCharge>(),
Charges = new ArrayList<IZUGFeRDAllowanceCharge>(), LogisticsServiceCharges = new ArrayList<IZUGFeRDAllowanceCharge>();
@@ -59,7 +59,9 @@ public class Invoice implements IExportableTransaction {
return documentName;
}
public String getContractReferencedDocument() { return contractReferencedDocument; }
public String getContractReferencedDocument() {
return contractReferencedDocument;
}
public Invoice setDocumentName(String documentName) {
this.documentName = documentName;
@@ -88,13 +90,16 @@ public class Invoice implements IExportableTransaction {
}
/***
* switch type to invoice correction and refer to document number
* switch type to invoice correction and refer to document number.
* Please note that the quantities need to be negative, if you
* e.g. delivered 100 and take 50 back the quantity should be -50 in the
* corrected invoice, which will result in negative VAT and a negative payment amount
* @param number the invoice number to be corrected
* @return this object (fluent setter)
*/
public Invoice setCorrection(String number) {
setBuyerOrderReferencedDocumentID(number);
documentCode= DocumentCodeTypeConstants.CORRECTEDINVOICE;
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
return this;
}
@@ -144,7 +149,6 @@ public class Invoice implements IExportableTransaction {
}
public Invoice setShipToStreet(String shipToStreet) {
this.shipToStreet = shipToStreet;
return this;
@@ -185,6 +189,11 @@ public class Invoice implements IExportableTransaction {
return buyerOrderReferencedDocumentID;
}
/***
* usually the order number or in case of a correction the original invoice number
* @param buyerOrderReferencedDocumentID string with number
* @return fluent setter
*/
public Invoice setBuyerOrderReferencedDocumentID(String buyerOrderReferencedDocumentID) {
this.buyerOrderReferencedDocumentID = buyerOrderReferencedDocumentID;
return this;
@@ -195,6 +204,11 @@ public class Invoice implements IExportableTransaction {
return buyerOrderReferencedDocumentIssueDateTime;
}
/***
* when the order (or whatever reference in BuyerOrderReferencedDocumentID) was issued (@todo switch to date?)
* @param buyerOrderReferencedDocumentIssueDateTime IssueDateTime in format CCYY-MM-DDTHH:MM:SS
* @return fluent setter
*/
public Invoice setBuyerOrderReferencedDocumentIssueDateTime(String buyerOrderReferencedDocumentIssueDateTime) {
this.buyerOrderReferencedDocumentIssueDateTime = buyerOrderReferencedDocumentIssueDateTime;
return this;
@@ -209,6 +223,7 @@ public class Invoice implements IExportableTransaction {
@Deprecated
/***
* @deprecated use TradeParty::addTaxID instead
* @see TradeParty
*/
public Invoice setOwnTaxID(String ownTaxID) {
sender.addTaxID(ownTaxID);
@@ -223,6 +238,7 @@ public class Invoice implements IExportableTransaction {
@Deprecated
/***
* @deprecated use TradeParty::addVATID instead
* @see TradeParty
*/
public Invoice setOwnVATID(String ownVATID) {
sender.addVATID(ownVATID);
@@ -234,6 +250,12 @@ public class Invoice implements IExportableTransaction {
return ownForeignOrganisationID;
}
@Deprecated
/***
* @deprecated use TradeParty instead
* @see TradeParty
*/
public Invoice setOwnForeignOrganisationID(String ownForeignOrganisationID) {
this.ownForeignOrganisationID = ownForeignOrganisationID;
return this;
@@ -244,6 +266,12 @@ public class Invoice implements IExportableTransaction {
return ownOrganisationName;
}
@Deprecated
/***
* @deprecated use senders' TradeParty's name instead
* @see TradeParty
*/
public Invoice setOwnOrganisationName(String ownOrganisationName) {
this.ownOrganisationName = ownOrganisationName;
return this;
@@ -273,7 +301,7 @@ public class Invoice implements IExportableTransaction {
@Override
public String[] getNotes() {
if (notes==null) {
if (notes == null) {
return null;
}
return notes.toArray(new String[0]);
@@ -344,6 +372,13 @@ public class Invoice implements IExportableTransaction {
return sender;
}
/***
* sets a named sender contact
* @deprecated use setSender
* @see Contact
* @param ownContact the sender contact
* @return fluent setter
*/
public Invoice setOwnContact(Contact ownContact) {
this.sender.setContact(ownContact);
return this;
@@ -353,14 +388,26 @@ public class Invoice implements IExportableTransaction {
return recipient;
}
/**
* required.
* sets the invoice receiving institution = invoicee
* @param recipient the invoicee organisation
* @return fluent setter
*/
public Invoice setRecipient(TradeParty recipient) {
this.recipient = recipient;
return this;
}
/**
* required.
* sets the invoicing institution = invoicer
* @param sender the invoicer
* @return fluent setter
*/
public Invoice setSender(TradeParty sender) {
this.sender = sender;
if ((sender.getBankDetails()!=null)&&(sender.getBankDetails().size()>0)) {
if ((sender.getBankDetails() != null) && (sender.getBankDetails().size() > 0)) {
// convert bankdetails
}
@@ -372,7 +419,7 @@ public class Invoice implements IExportableTransaction {
if (Allowances.isEmpty()) {
return null;
} else
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
}
@@ -397,11 +444,11 @@ public class Invoice implements IExportableTransaction {
@Override
public IZUGFeRDTradeSettlement[] getTradeSettlement() {
if (getSender()==null) {
if (getSender() == null) {
return null;
}
return ((TradeParty)getSender()).getAsTradeSettlement();
return ((TradeParty) getSender()).getAsTradeSettlement();
}
@@ -421,6 +468,11 @@ public class Invoice implements IExportableTransaction {
return deliveryAddress;
}
/***
* if the delivery address is not the recipient address, it can be specified here
* @param deliveryAddress the goods receiving organisation
* @return fluent setter
*/
public Invoice setDeliveryAddress(TradeParty deliveryAddress) {
this.deliveryAddress = deliveryAddress;
return this;
@@ -431,14 +483,19 @@ public class Invoice implements IExportableTransaction {
return ZFItems.toArray(new IZUGFeRDExportableItem[0]);
}
/**
* required
* adds invoice "lines" :-)
* @see Item
* @param item the invoice line
* @return fluent setter
*/
public Invoice addItem(IZUGFeRDExportableItem item) {
ZFItems.add(item);
return this;
}
/***
* checks if all required items are set in order to be able to export it
* @return true if all required items are set
@@ -454,61 +511,74 @@ public class Invoice implements IExportableTransaction {
// this.country = country;
}
/***
* adds a document level addition to the price
* @see Charge
* @param izac the charge to be applied
* @return fluent setter
*/
public Invoice addCharge(IZUGFeRDAllowanceCharge izac) {
Charges.add(izac);
return this;
}
/***
* adds a document level rebate
* @see Allowance
* @param izac the allowance to be applied
* @return fluent setter
*/
public Invoice addAllowance(IZUGFeRDAllowanceCharge izac) {
Allowances.add(izac);
return this;
}
/***
* adds the ID of a contract referenced in the invoice
* @param s the contract number
* @return fluent setter
*/
public Invoice setContractReferencedDocument(String s) {
contractReferencedDocument=s;
contractReferencedDocument = s;
return this;
}
public Invoice setOccurrenceDate(Date occur) {
occurrenceDateFrom=occur;
occurrenceDateTo=null;
/***
* sets a document level delivery period,
* which is optional additional to the mandatory deliverydate
* and which will become a BillingSpecifiedPeriod-Element
* @param start the date of first delivery
* @param end the date of last delivery
* @return fluent setter
*/
public Invoice setDetailedDeliveryPeriod(Date start, Date end) {
detailedDeliveryDateStart = start;
detailedDeliveryPeriodEnd = end;
return this;
}
public Invoice setOccurrencePeriod(Date start, Date end) {
occurrenceDateFrom=start;
occurrenceDateTo=end;
return this;
@Override
public Date getDetailedDeliveryPeriodFrom() {
return detailedDeliveryDateStart;
}
@Override
public Date getOccurrenceDate() {
return occurrenceDateFrom;
}
@Override
public Date getOccurrencePeriodFrom() {
if (occurrenceDateTo!=null) {
return occurrenceDateFrom;
} else {
return null;
}
}
@Override
public Date getOccurrencePeriodTo() {
if (occurrenceDateTo!=null) {
return occurrenceDateTo;
} else {
return null;
}
public Date getDetailedDeliveryPeriodTo() {
return detailedDeliveryPeriodEnd;
}
/***
* adds a free text paragraph, which will become a includedNote element
* @param text freeform UTF8 plain text
* @return fluent setter
*/
public Invoice addNote(String text) {
if (notes==null) {
notes=new ArrayList<String>();
if (notes == null) {
notes = new ArrayList<String>();
}
notes.add(text);
return this;

View File

@@ -6,6 +6,9 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import java.math.BigDecimal;
import java.util.ArrayList;
/***
* describes any invoice line
*/
public class Item implements IZUGFeRDExportableItem {
protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount;
protected String id;
@@ -14,6 +17,12 @@ public class Item implements IZUGFeRDExportableItem {
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<IZUGFeRDAllowanceCharge>(),
Charges = new ArrayList<IZUGFeRDAllowanceCharge>();
/***
* default constructor
* @param product contains the products name, tax rate, and unit
* @param price the base price of one item the product
* @param quantity the number, dimensions or the weight of the delivered product or good in this context
*/
public Item(Product product, BigDecimal price, BigDecimal quantity) {
this.price = price;
this.quantity = quantity;
@@ -24,6 +33,11 @@ public class Item implements IZUGFeRDExportableItem {
return lineTotalAmount;
}
/**
* should only be set by calculator classes or maybe when reading from XML
* @param lineTotalAmount price*quantity of this line
* @return fluent setter
*/
public Item setLineTotalAmount(BigDecimal lineTotalAmount) {
this.lineTotalAmount = lineTotalAmount;
return this;
@@ -33,6 +47,12 @@ public class Item implements IZUGFeRDExportableItem {
return grossPrice;
}
/***
* the list price without VAT (sic!), refer to EN16931-1 for definition
* @param grossPrice the list price without VAT
* @return fluent setter
*/
public Item setGrossPrice(BigDecimal grossPrice) {
this.grossPrice = grossPrice;
return this;
@@ -125,6 +145,11 @@ public class Item implements IZUGFeRDExportableItem {
return this;
}
/***
* adds item level freetext fields (includednote)
* @param text UTF8 plain text
* @return fluent setter
*/
public Item addNote(String text) {
if (notes==null) {
notes=new ArrayList<String>();

View File

@@ -4,10 +4,20 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
import java.math.BigDecimal;
/***
* describes a product, good or service used in an invoice item line
*/
public class Product implements IZUGFeRDExportableProduct {
protected String unit, name, description, sellerAssignedID, buyerAssignedID;
protected BigDecimal VATPercent;
/***
* default constructor
* @param name product short name
* @param description product long name
* @param unit a two/three letter UN/ECE rec 20 unit code, e.g. "C62" for piece
* @param VATPercent product vat rate
*/
public Product(String name, String description, String unit, BigDecimal VATPercent) {
this.unit = unit;
this.name = name;
@@ -20,6 +30,11 @@ public class Product implements IZUGFeRDExportableProduct {
return sellerAssignedID;
}
/***
* how the seller identifies this type of product
* @param sellerAssignedID a unique String
* @return fluent setter
*/
public Product setSellerAssignedID(String sellerAssignedID) {
this.sellerAssignedID = sellerAssignedID;
return this;
@@ -29,6 +44,11 @@ public class Product implements IZUGFeRDExportableProduct {
return buyerAssignedID;
}
/***
* if the buyer provided an ID how he refers to this product
* @param buyerAssignedID a string the buyer provided
* @return fluent setter
*/
public Product setBuyerAssignedID(String buyerAssignedID) {
this.buyerAssignedID = buyerAssignedID;
return this;
@@ -39,6 +59,11 @@ public class Product implements IZUGFeRDExportableProduct {
return unit;
}
/***
* sets a UN/ECE rec 20 or 21 code which unit the product ships in, e.g. C62=piece
* @param unit 2-3 letter UN/ECE rec 20 or 21
* @return fluent setter
*/
public Product setUnit(String unit) {
this.unit = unit;
return this;
@@ -49,6 +74,11 @@ public class Product implements IZUGFeRDExportableProduct {
return name;
}
/**
* name of the product
* @param name short name
* @return fluent setter
*/
public Product setName(String name) {
this.name = name;
return this;
@@ -59,6 +89,11 @@ public class Product implements IZUGFeRDExportableProduct {
return description;
}
/**
* description of the product (required)
* @param description long name
* @return fluent setter
*/
public Product setDescription(String description) {
this.description = description;
return this;
@@ -69,6 +104,11 @@ public class Product implements IZUGFeRDExportableProduct {
return VATPercent;
}
/****
* VAT rate of the product
* @param VATPercent vat rate of the product
* @return fluent setter
*/
public Product setVATPercent(BigDecimal VATPercent) {
this.VATPercent = VATPercent;
return this;

View File

@@ -1,21 +1,31 @@
package org.mustangproject;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
import org.mustangproject.ZUGFeRD.*;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
/***
* A organisation, i.e. usually a company
*/
public class TradeParty implements IZUGFeRDExportableTradeParty {
protected String name, zip, street, location, country;
protected String taxID = null, vatID = null;
protected String ID = null;
protected String additionalAddress = null;
protected ArrayList<BankDetails> bankDetails = new ArrayList<BankDetails>();
protected Contact contact = null;
/***
*
* @param name of the company
* @param street street and number (use setAdditionalAddress for more parts)
* @param zip postcode of the company
* @param location city of the company
* @param country two letter ISO code
*/
public TradeParty(String name, String street, String zip, String location, String country) {
this.name = name;
this.street = street;
@@ -25,6 +35,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
/***
* XML parsing constructor
* @param nodes the nodelist returned e.g. from xpath
*/
public TradeParty(NodeList nodes) {
/**
* <ram:SellerTradeParty>
@@ -88,11 +102,37 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
}
@Override
public String getID() {
return ID;
}
/**
* if it's a customer, this can e.g. be the customer ID
* @param ID customer/seller number
* @return fluent setter
*/
public TradeParty setID(String ID) {
this.ID = ID;
return this;
}
/***
* (optional) a named contact person
* @see Contact
* @param c the named contact person
* @return fluent setter
*/
public TradeParty setContact(Contact c) {
this.contact = c;
return this;
}
/***
* required (for senders, if payment is not debit): the BIC and IBAN
* @param s bank credentials
* @return fluent setter
*/
public TradeParty addBankDetails(BankDetails s) {
bankDetails.add(s);
return this;
@@ -102,11 +142,21 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return bankDetails;
}
/***
* a general tax ID
* @param taxID tax number of the organisation
* @return fluent setter
*/
public TradeParty addTaxID(String taxID) {
this.taxID = taxID;
return this;
}
/***
* the USt-ID
* @param vatID Ust-ID
* @return fluent setter
*/
public TradeParty addVATID(String vatID) {
this.vatID = vatID;
return this;
@@ -126,6 +176,12 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return name;
}
/***
* required, usually done in the constructor: the complete name of the organisation
* @param name complete legal name
* @return fluent setter
*/
public TradeParty setName(String name) {
this.name = name;
return this;
@@ -136,6 +192,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return zip;
}
/***
* usually set in the constructor, required for recipients in german invoices: postcode
* @param zip postcode
* @return fluent setter
*/
public TradeParty setZIP(String zip) {
this.zip = zip;
return this;
@@ -146,6 +207,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return street;
}
/***
* usually set in constructor, required in germany, street and house number
* @param street street name and number
* @return fluent setter
*/
public TradeParty setStreet(String street) {
this.street = street;
return this;
@@ -156,6 +222,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return location;
}
/***
* usually set in constructor, usually required in germany, the city of the organisation
* @param location city
* @return fluent setter
*/
public TradeParty setLocation(String location) {
this.location = location;
return this;
@@ -166,6 +237,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return country;
}
/***
* two-letter ISO code of the country
* @param country two-letter-code
* @return fluent setter
*/
public TradeParty setCountry(String country) {
this.country = country;
return this;
@@ -197,9 +273,17 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return additionalAddress;
}
/***
* additional parts of the address, e.g. which floor.
* Street address will become "lineOne", this will become "lineTwo"
* @param additionalAddress additional address description
* @return fluent setter
*/
public TradeParty setAdditionalAddress(String additionalAddress) {
this.additionalAddress = additionalAddress;
return this;
}
}

View File

@@ -1,9 +1,25 @@
package org.mustangproject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.logging.Level;
import java.util.logging.Logger;
public class XMLTools {
import org.dom4j.io.XMLWriter;
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider;
public class XMLTools extends XMLWriter {
public String escapeAttributeEntities(String s) {
return super.escapeAttributeEntities(s);
}
public String escapeElementEntities(String s) {
return super.escapeElementEntities(s);
}
public static String nDigitFormat(BigDecimal value, int scale) {
@@ -28,42 +44,98 @@ public class XMLTools {
public static String encodeXML(CharSequence s) {
StringBuilder sb = new StringBuilder();
int len = s.length();
for (int i=0;i<len;i++) {
int c = s.charAt(i);
if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
c = ((c-0xd7c0)<<10) | (s.charAt(++i)&0x3ff); // UTF16 decode
}
if (c < 0x80) { // ASCII range: test most common case first
if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) {
// Illegal XML character, even encoded. Skip or substitute
sb.append("&#xfffd;"); // Unicode replacement character
} else {
switch(c) {
case '&': sb.append("&amp;"); break;
case '>': sb.append("&gt;"); break;
case '<': sb.append("&lt;"); break;
// Uncomment next two if encoding for an XML attribute
StringBuilder sb = new StringBuilder();
int len = s.length();
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
c = ((c - 0xd7c0) << 10) | (s.charAt(++i) & 0x3ff); // UTF16 decode
}
if (c < 0x80) { // ASCII range: test most common case first
if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) {
// Illegal XML character, even encoded. Skip or substitute
sb.append("&#xfffd;"); // Unicode replacement character
} else {
switch (c) {
case '&':
sb.append("&amp;");
break;
case '>':
sb.append("&gt;");
break;
case '<':
sb.append("&lt;");
break;
// Uncomment next two if encoding for an XML attribute
// case '\'' sb.append("&apos;"); break;
// case '\"' sb.append("&quot;"); break;
// Uncomment next three if you prefer, but not required
// Uncomment next three if you prefer, but not required
// case '\n' sb.append("&#10;"); break;
// case '\r' sb.append("&#13;"); break;
// case '\t' sb.append("&#9;"); break;
default: sb.append((char)c);
}
}
} else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) {
// Illegal XML character, even encoded. Skip or substitute
sb.append("&#xfffd;"); // Unicode replacement character
} else {
sb.append("&#x");
sb.append(Integer.toHexString(c));
sb.append(';');
}
}
return sb.toString();
default:
sb.append((char) c);
}
}
} else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) {
// Illegal XML character, even encoded. Skip or substitute
sb.append("&#xfffd;"); // Unicode replacement character
} else {
sb.append("&#x");
sb.append(Integer.toHexString(c));
sb.append(';');
}
}
return sb.toString();
}
/**
* Returns the Byte Order Mark size and thus allows to skips over a BOM
* at the beginning of the given ByteArrayInputStream, if one exists.
*
* @param is the ByteArrayInputStream used
* @throws IOException if can not be read from is
* @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a>
*
public static int guessBOMSize(ByteArrayInputStream is) throws IOException {
byte[] pad = new byte[4];
is.read(pad);
is.reset();
int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF);
int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF);
int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF);
//
if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) {
// UCS-4: BOM takes 4 bytes
return 4;
} else if (test3 == 0xEFBBFF) {
// UTF-8: BOM takes 3 bytes
return 3;
} else if (test2 == 0xFEFF || test2 == 0xFFFE) {
// UTF-16: BOM takes 2 bytes
return 2;
}
return 0;
}*/
/***
* removes utf8 byte order marks from byte arrays, in case one is there
* @param zugferdRaw the CII XML
* @return the byte array without bom
*/
public static byte[] removeBOM(byte[] zugferdRaw) {
byte[] zugferdData;
if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
zugferdData = new byte[zugferdRaw.length - 3];
System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3);
} else {
zugferdData = zugferdRaw;
}
return zugferdData;
}
}

View File

@@ -21,8 +21,8 @@ package org.mustangproject.ZUGFeRD;
* Mustangproject's ZUGFeRD implementation
* Neccessary interface for ZUGFeRD exporter
* Licensed under the APLv2
* @date 2014-05-10 to 2014-06-25
* @version 1.2.0
* @date 2014-05-10 to 2020-11-12
* @version 2.0.0
* @author jstaerk
* */
@@ -36,7 +36,7 @@ import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
/***
* the interface of an transaction, e.g. an invoice, you want to create xml (potentially to be added to a PDF)
* for
* @see Invoice if you want to use an object rather than an interface
* @see org.mustangproject.Invoice
*/
public interface IExportableTransaction {
@@ -431,33 +431,26 @@ public interface IExportableTransaction {
return null;
}
/***
* specify delivery date
* @return the delivery date
*/
default Date getOccurrenceDate() {
return null;
}
/***
* specify delivery period
* specifies the document level delivery period, will be included in a BillingSpecifiedPeriod element
* @return the beginning of the delivery period
*/
default Date getOccurrencePeriodFrom() {
default Date getDetailedDeliveryPeriodFrom() {
return null;
}
/***
* specify delivery period
* specifies the document level delivery period, will be included in a BillingSpecifiedPeriod element
* @return the end of the delivery period
*/
default Date getOccurrencePeriodTo() {
default Date getDetailedDeliveryPeriodTo() {
return null;
}
/**
* get additional referenced documents acccording to BG-24 XRechnung (Rechnungsbegruendende Unterlagen),
* i.e. <ram:AdditionalReferencedDocument> in <ram:ApplicableHeaderTradeAgreement> (only supported for zf2)
* i.e. ram:ApplicableHeaderTradeAgreement/ram:AdditionalReferencedDocument
*
* @return a array of objects from class FileAttachment
*/

View File

@@ -26,8 +26,6 @@ package org.mustangproject.ZUGFeRD;
* @author jstaerk
* */
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
import java.math.BigDecimal;
public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
@@ -74,10 +72,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
return BigDecimal.ONE.setScale(4);
}
default String getCategoryCode() {
return TaxCategoryCodeTypeConstants.STANDARDRATE;
}
/***
* the ID of an additionally referenced document for this item
* @return the id as string

View File

@@ -106,11 +106,12 @@ public interface IZUGFeRDExportableProduct {
}
default String getTaxCategoryCode() {
if (isIntraCommunitySupply()) {
if (getVATPercent().equals(new BigDecimal(0))) {
return "Z"; // zero rated goods
} else if (isIntraCommunitySupply()) {
return "K";
} else {
return "S";
return "S"; // one of the "standard" rates (not neccessarily a default rate, even a deducted VAT is standard calculation)
}
}

View File

@@ -33,7 +33,7 @@ public class Profiles {
{"BASIC", new Profile("BASIC", "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic")},
{"EN16931", new Profile("EN16931", "urn:cen.eu:en16931:2017")},
{"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")},
{"XRECHNUNG", new Profile("XRECHNUNG", "XRECHNUNG")}
{"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0")}
}).collect(Collectors.toMap(data -> (String) data[0], data -> (Profile) data[1]));
static Map<String, Profile> zf1Map = Stream.of(new Object[][]{

View File

@@ -1,6 +1,7 @@
package org.mustangproject.ZUGFeRD;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
/***
@@ -30,7 +31,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
res = res.add(amount.getCalculated());
}
return res;
return res.setScale(2, RoundingMode.HALF_UP);
}
/***
@@ -146,7 +147,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
BigDecimal percent = currentItem.getProduct().getVATPercent();
LineCalculator lc = new LineCalculator(currentItem);
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
trans.getDocumentCode());
currentItem.getProduct().getTaxCategoryCode());
VATAmount current = hm.get(percent.stripTrailingZeros());
if (current == null) {
hm.put(percent.stripTrailingZeros(), itemVATAmount);

View File

@@ -27,11 +27,11 @@ public class XMLUpgrader {
/***
* Takes a filename of a ZF1 XML file and returns the string of ZF2 XML
* @param xmlFilename
* @param xmlFilename the filename of the source
* @return String the updated XML
* @throws FileNotFoundException
* @throws TransformerException
* @throws UnsupportedEncodingException
* @throws FileNotFoundException if the source could not be found
* @throws TransformerException if the source could not be transformed
* @throws UnsupportedEncodingException if the source was not utf8
*/
public String migrateFromV1ToV2(String xmlFilename) throws FileNotFoundException, TransformerException, UnsupportedEncodingException {
/**

View File

@@ -1,21 +1,23 @@
/** **********************************************************************
*
/**
* *********************************************************************
* <p>
* Copyright 2018 Jochen Staerk
*
* <p>
* Use is subject to license terms.
*
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
*
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* <p>
* See the License for the specific language governing permissions and
* limitations under the License.
*
*********************************************************************** */
* <p>
* **********************************************************************
*/
package org.mustangproject.ZUGFeRD;
import org.dom4j.Document;
@@ -43,7 +45,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
protected byte[] zugferdData;
private String paymentTermsDescription;
SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd");
protected Profile profile=Profiles.getByName("COMFORT",1);
protected Profile profile = Profiles.getByName("COMFORT", 1);
/**
@@ -52,6 +54,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
@Override
public void setTest() {
}
private String vatFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
@@ -104,20 +107,19 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
@Override
public void generateXML(IExportableTransaction trans) {
this.trans = trans;
this.calc=new TransactionCalculator(trans);
this.calc = new TransactionCalculator(trans);
boolean hasDueDate=false;
String taxCategoryCode="";
boolean hasDueDate = false;
SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
String exemptionReason="";
String exemptionReason = "";
if (trans.getPaymentTermDescription()!=null) {
paymentTermsDescription=trans.getPaymentTermDescription();
if (trans.getPaymentTermDescription() != null) {
paymentTermsDescription = trans.getPaymentTermDescription();
}
if (paymentTermsDescription==null) {
paymentTermsDescription= "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
if (paymentTermsDescription == null) {
paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
}
String senderReg = "";
@@ -136,14 +138,14 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
}
String subjectNote = "";
if (trans.getSubjectNote()!=null) {
if (trans.getSubjectNote() != null) {
subjectNote = "<ram:IncludedNote>\n" + " <ram:Content>"
+ XMLTools.encodeXML(trans.getSubjectNote())+ "</ram:Content>\n"
+ XMLTools.encodeXML(trans.getSubjectNote()) + "</ram:Content>\n"
+ "</ram:IncludedNote>\n";
}
String typecode="380";
if (trans.getDocumentCode()!=null) {
typecode=trans.getDocumentCode();
String typecode = "380";
if (trans.getDocumentCode() != null) {
typecode = trans.getDocumentCode();
}
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@@ -164,12 +166,12 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " <rsm:HeaderExchangedDocument>\n"
+ " <ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>\n" //$NON-NLS-2$
+ " <ram:Name>RECHNUNG</ram:Name>\n"
+ " <ram:TypeCode>"+typecode+"</ram:TypeCode>\n"
+ " <ram:TypeCode>" + typecode + "</ram:TypeCode>\n"
+ " <ram:IssueDateTime><udt:DateTimeString format=\"102\">"
+ zugferdDateFormat.format(trans.getIssueDate()) + "</udt:DateTimeString></ram:IssueDateTime>\n" // date
// format
// was
// 20130605
// format
// was
// 20130605
+ subjectNote
+ rebateAgreement
+ senderReg
@@ -182,14 +184,14 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
}
xml = xml + " <ram:SellerTradeParty>\n";
xml+= getTradePartyAsXML(trans.getSender(), true);
xml+= " </ram:SellerTradeParty>\n"
xml += getTradePartyAsXML(trans.getSender(), true);
xml += " </ram:SellerTradeParty>\n"
+ " <ram:BuyerTradeParty>\n";
// + " <ID>GE2020211</ID>\n"
// + " <GlobalID schemeID=\"0088\">4000001987658</GlobalID>\n"
xml+= getTradePartyAsXML(trans.getRecipient(), false);
if ((trans.getOwnVATID()!=null)&&(trans.getOwnOrganisationName()!=null)) {
xml += getTradePartyAsXML(trans.getRecipient(), false);
if ((trans.getOwnVATID() != null) && (trans.getOwnOrganisationName() != null)) {
xml = xml + " <ram:SpecifiedTaxRegistration>\n" + " <ram:ID schemeID=\"VA\">"
+ XMLTools.encodeXML(trans.getOwnVATID()) + "</ram:ID>\n"
+ " </ram:SpecifiedTaxRegistration>";
@@ -197,21 +199,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
xml += " </ram:BuyerTradeParty>\n";
if (trans.getBuyerOrderReferencedDocumentID()!=null) {
if (trans.getBuyerOrderReferencedDocumentID() != null) {
xml = xml + " <ram:BuyerOrderReferencedDocument>\n"
+ " <ram:IssuerAssignedID>"
+ XMLTools.encodeXML(trans.getBuyerOrderReferencedDocumentID()) + "</ram:IssuerAssignedID>\n"
+ " </ram:BuyerOrderReferencedDocument>\n";
}
xml = xml + " </ram:ApplicableSupplyChainTradeAgreement>\n"
+ " <ram:ApplicableSupplyChainTradeDelivery>\n" ;
if (this.trans.getDeliveryAddress()!=null) {
xml += "<ram:ShipToTradeParty>"+
getTradePartyAsXML(this.trans.getDeliveryAddress(), false)+
xml = xml + " </ram:ApplicableSupplyChainTradeAgreement>\n"
+ " <ram:ApplicableSupplyChainTradeDelivery>\n";
if (this.trans.getDeliveryAddress() != null) {
xml += "<ram:ShipToTradeParty>" +
getTradePartyAsXML(this.trans.getDeliveryAddress(), false) +
"</ram:ShipToTradeParty>";
}
xml+= " <ram:ActualDeliverySupplyChainEvent>\n"
xml += " <ram:ActualDeliverySupplyChainEvent>\n"
+ " <ram:OccurrenceDateTime>";
if (trans.getDeliveryDate() != null) {
@@ -232,21 +234,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " <ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>\n" //$NON-NLS-2$
+ " <ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>\n";
if (trans.getTradeSettlementPayment()!=null) {
if (trans.getTradeSettlementPayment() != null) {
for (IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
if(payment!=null) {
hasDueDate=true;
xml+=payment.getSettlementXML();
if (payment != null) {
hasDueDate = true;
xml += payment.getSettlementXML();
}
}
}
if (trans.getTradeSettlement()!=null) {
if (trans.getTradeSettlement() != null) {
for (IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
if(payment!=null) {
if (payment != null) {
if (payment instanceof IZUGFeRDTradeSettlementPayment) {
hasDueDate=true;
hasDueDate = true;
}
xml+=payment.getSettlementXML();
xml += payment.getSettlementXML();
}
}
}
@@ -261,7 +263,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " <ram:TypeCode>VAT</ram:TypeCode>\n"
+ exemptionReason
+ " <ram:BasisAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>\n" // currencyID=\"EUR\"
+ " <ram:CategoryCode>"+taxCategoryCode+"</ram:CategoryCode>\n"
+ " <ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>\n"
+ " <ram:ApplicablePercent>" + vatFormat(currentTaxPercent)
+ "</ram:ApplicablePercent>\n" + " </ram:ApplicableTradeTax>\n"; //$NON-NLS-2$
@@ -280,7 +282,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
}
}
if (hasDueDate && (trans.getDueDate()!=null)) {
if (hasDueDate && (trans.getDueDate() != null)) {
xml = xml + " <ram:DueDateDateTime><udt:DateTimeString format=\"102\">" // $NON-NLS-2$
+ zugferdDateFormat.format(trans.getDueDate())
+ "</udt:DateTimeString></ram:DueDateDateTime>\n";// 20130704
@@ -292,7 +294,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
}
xml = xml + " <ram:SpecifiedTradeSettlementMonetarySummation>\n"
+ " <ram:LineTotalAmount currencyID=\""+trans.getCurrency()+"\">" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>\n" //$NON-NLS-2$
+ " <ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>\n" //$NON-NLS-2$
// currencyID=\"EUR\"
+ " <ram:ChargeTotalAmount currencyID=\"" + trans.getCurrency() + "\">0.00</ram:ChargeTotalAmount>\n" // currencyID=\"EUR\"
+ " <ram:AllowanceTotalAmount currencyID=\"" + trans.getCurrency() + "\">0.00</ram:AllowanceTotalAmount>\n" //
@@ -318,9 +320,8 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
int lineID = 0;
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
lineID++;
taxCategoryCode=currentItem.getProduct().getTaxCategoryCode();
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason="<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
@@ -331,10 +332,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " </ram:AssociatedDocumentLineDocument>\n"
+ " <ram:SpecifiedSupplyChainTradeAgreement>\n"
+ " <ram:GrossPriceProductTradePrice>\n"
+ " <ram:ChargeAmount currencyID=\""+trans.getCurrency()+"\">" + priceFormat(lc.getPriceGross())
+ " <ram:ChargeAmount currencyID=\"" + trans.getCurrency() + "\">" + priceFormat(lc.getPriceGross())
+ "</ram:ChargeAmount>\n"
+ " <ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) +"</ram:BasisQuantity>\n"
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>\n"
// + " <AppliedTradeAllowanceCharge>\n"
// + " <ChargeIndicator>false</ChargeIndicator>\n"
// + " <ActualAmount currencyID=\"EUR\">0.6667</ActualAmount>\n"
@@ -342,10 +343,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
// + " </AppliedTradeAllowanceCharge>\n"
+ " </ram:GrossPriceProductTradePrice>\n"
+ " <ram:NetPriceProductTradePrice>\n"
+ " <ram:ChargeAmount currencyID=\""+trans.getCurrency()+"\">" + priceFormat(currentItem.getPrice())
+ " <ram:ChargeAmount currencyID=\"" + trans.getCurrency() + "\">" + priceFormat(currentItem.getPrice())
+ "</ram:ChargeAmount>\n"
+ " <ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) +"</ram:BasisQuantity>\n"
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>\n"
+ " </ram:NetPriceProductTradePrice>\n"
+ " </ram:SpecifiedSupplyChainTradeAgreement>\n"
@@ -358,21 +359,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ " <ram:ApplicableTradeTax>\n"
+ " <ram:TypeCode>VAT</ram:TypeCode>\n"
+ exemptionReason
+ " <ram:CategoryCode>"+currentItem.getProduct().getTaxCategoryCode()+"</ram:CategoryCode>\n"
+ " <ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>\n"
+ " <ram:ApplicablePercent>"
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:ApplicablePercent>\n"
+ " </ram:ApplicableTradeTax>\n"
+ " <ram:SpecifiedTradeSettlementMonetarySummation>\n"
+ " <ram:LineTotalAmount currencyID=\""+trans.getCurrency()+"\">" + currencyFormat(lc.getItemTotalNetAmount())
+ " <ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount())
+ "</ram:LineTotalAmount>\n"
+ " </ram:SpecifiedTradeSettlementMonetarySummation>\n";
if (currentItem.getAdditionalReferencedDocumentID()!=null) {
xml=xml + " <ram:AdditionalReferencedDocument><ram:ID>"+currentItem.getAdditionalReferencedDocumentID()+"</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>\n";
if (currentItem.getAdditionalReferencedDocumentID() != null) {
xml = xml + " <ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>\n";
}
xml=xml + " </ram:SpecifiedSupplyChainTradeSettlement>\n"
+ " <ram:SpecifiedTradeProduct>\n";
}
xml = xml + " </ram:SpecifiedSupplyChainTradeSettlement>\n"
+ " <ram:SpecifiedTradeProduct>\n";
// + " <GlobalID schemeID=\"0160\">4012345001235</GlobalID>\n"
if (currentItem.getProduct().getSellerAssignedID() != null) {
xml = xml + " <ram:SellerAssignedID>"
@@ -406,14 +407,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
byte[] zugferdRaw;
try {
zugferdRaw = xml.getBytes("UTF-8");
if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
zugferdData = new byte[zugferdRaw.length - 3];
System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3);
} else {
zugferdData = zugferdRaw;
}
zugferdData = XMLTools.removeBOM(zugferdRaw);
} catch (UnsupportedEncodingException e) {
Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e);
}
@@ -421,7 +415,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
@Override
public void setProfile(Profile p) {
profile=p;
profile = p;
}
private String buildPaymentTermsXml() {
@@ -455,7 +449,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
paymentTermsXml += "<ram:BasisDateTime>";
paymentTermsXml += "<udt:DateTimeString format=\"102\">" + zugferdDateFormat.format(baseDate) + "</udt:DateTimeString>";
paymentTermsXml += "</ram:BasisDateTime>";
paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
}

View File

@@ -218,7 +218,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
this.calc = new TransactionCalculator(trans);
boolean hasDueDate = false;
String taxCategoryCode = "";
SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
String exemptionReason = "";
@@ -298,7 +297,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
int lineID = 0;
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
lineID++;
taxCategoryCode = currentItem.getProduct().getTaxCategoryCode();
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
@@ -492,19 +490,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ " <ram:TypeCode>VAT</ram:TypeCode>\n"
+ exemptionReason
+ " <ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>\n" // currencyID=\"EUR\"
+ " <ram:CategoryCode>" + taxCategoryCode + "</ram:CategoryCode>\n"
+ " <ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>\n"
+ " <ram:RateApplicablePercent>" + vatFormat(currentTaxPercent)
+ "</ram:RateApplicablePercent>\n" + " </ram:ApplicableTradeTax>\n"; //$NON-NLS-2$
}
}
if ((trans.getOccurrencePeriodFrom() != null) || (trans.getOccurrencePeriodTo() != null)) {
if ((trans.getDetailedDeliveryPeriodFrom() != null) || (trans.getDetailedDeliveryPeriodTo() != null)) {
xml = xml + "<ram:BillingSpecifiedPeriod>";
if (trans.getOccurrencePeriodFrom() != null) {
xml = xml + "<ram:StartDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(trans.getOccurrencePeriodFrom()) + "</udt:DateTimeString></ram:StartDateTime>";
if (trans.getDetailedDeliveryPeriodFrom() != null) {
xml = xml + "<ram:StartDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(trans.getDetailedDeliveryPeriodFrom()) + "</udt:DateTimeString></ram:StartDateTime>";
}
if (trans.getOccurrencePeriodTo() != null) {
xml = xml + "<ram:EndDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(trans.getOccurrencePeriodTo()) + "</udt:DateTimeString></ram:EndDateTime>";
if (trans.getDetailedDeliveryPeriodTo() != null) {
xml = xml + "<ram:EndDateTime><udt:DateTimeString format='102'>" + zugferdDateFormat.format(trans.getDetailedDeliveryPeriodTo()) + "</udt:DateTimeString></ram:EndDateTime>";
}
xml = xml + "</ram:BillingSpecifiedPeriod>";
@@ -617,13 +615,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
try {
zugferdRaw = xml.getBytes("UTF-8");
if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
zugferdData = new byte[zugferdRaw.length - 3];
System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3);
} else {
zugferdData = zugferdRaw;
}
zugferdData=XMLTools.removeBOM(zugferdRaw);
} catch (UnsupportedEncodingException e) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
}

View File

@@ -84,7 +84,6 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
}
public ZUGFeRDExporterFromA1() {
ensurePDFisUpgraded = true;
setZUGFeRDVersion(ZUGFeRDExporterFromA3.DefaultZUGFeRDVersion);
}

View File

@@ -26,16 +26,19 @@ import org.apache.pdfbox.pdmodel.*;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.preflight.PreflightDocument;
import org.apache.pdfbox.preflight.exception.ValidationException;
import org.apache.pdfbox.preflight.parser.PreflightParser;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema;
import org.apache.xmpbox.schema.DublinCoreSchema;
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.schema.XMPBasicSchema;
import org.apache.xmpbox.type.ArrayProperty;
import org.apache.xmpbox.type.BadFieldValueException;
import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpParsingException;
import org.apache.xmpbox.xml.XmpSerializer;
import org.mustangproject.FileAttachment;
@@ -44,6 +47,7 @@ import javax.activation.FileDataSource;
import javax.xml.transform.TransformerException;
import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
@@ -56,8 +60,11 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<FileAttachment>();
protected boolean ensurePDFisUpgraded = true;
/**
* This flag controls whether or not the metadata is overwritten, or kind of merged.
* The merging probably needs to be overhauled, but for my purpose it was good enough.
*/
protected boolean overwrite = true;
private boolean disableAutoClose;
private boolean fileAttached = false;
@@ -71,7 +78,6 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* IZUGFeRDExportableTransaction for the XML to be populated.
*/
protected PDMetadata metadata = null;
protected PDFAIdentificationSchema pdfaid = null;
protected XMPMetadata xmp = null;
/**
* Producer attribute for PDF
@@ -86,6 +92,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
*/
protected String creatorTool = "mustangproject";
/** @deprecated author is never set yet */
@Deprecated
protected String author;
/** @deprecated title is never set yet */
@Deprecated
protected String title;
/** @deprecated subject is never set yet */
@Deprecated
protected String subject;
private PDDocument doc;
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
@@ -223,7 +239,6 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
public ZUGFeRDExporterFromA3() {
super();
ensurePDFisUpgraded = false;
}
public void attachFile(FileAttachment file) {
@@ -460,46 +475,23 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
}
protected void prepareDocument() throws IOException {
String fullProducer = producer + " (via mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + ")";
PDDocumentCatalog cat = doc.getDocumentCatalog();
metadata = new PDMetadata(doc);
cat.setMetadata(metadata);
xmp = XMPMetadata.createXMPMetadata();
xmp = getXmpMetadata();
writeAdobePDFSchema(xmp);
writePDFAIdentificationSchema(xmp);
writeDublinCoreSchema(xmp);
writeXMLBasicSchema(xmp);
writeDocumentInformation();
pdfaid = new PDFAIdentificationSchema(xmp);
// the following three lines are intended to make the pdf more PDF/A conformant if it isn't already
addSRGBOutputIntend();
setMarked();
addStructureTreeRoot();
xmp.addSchema(pdfaid);
DublinCoreSchema dc = xmp.createAndAddDublinCoreSchema();
dc.addCreator(creator);
XMPBasicSchema xsb = xmp.createAndAddXMPBasicSchema();
xsb.setCreatorTool(creatorTool);
xsb.setCreateDate(GregorianCalendar.getInstance());
// PDDocumentInformation pdi=doc.getDocumentInformation();
PDDocumentInformation pdi = new PDDocumentInformation();
pdi.setProducer(fullProducer);
pdi.setAuthor(creator);
doc.setDocumentInformation(pdi);
AdobePDFSchema pdf = xmp.createAndAddAdobePDFSchema();
pdf.setProducer(fullProducer);
if (ensurePDFisUpgraded) {
try {
pdfaid.setConformance(conformanceLevel.getLetter());
} catch (BadFieldValueException ex) {
// This should be impossible, because it would occur only if an illegal
// conformance level is supplied,
// however the enum enforces that the conformance level is valid.
throw new Error(ex);
}
pdfaid.setPart(3);
}
addXMP(xmp); /*
* this is the only line where we do something Zugferd-specific, i.e. add PDF
* metadata specifically for Zugferd, not generically for a embedded file
@@ -544,23 +536,189 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
return this;
}
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
XmpSerializer serializer = new XmpSerializer();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
/**
* Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata.
*/
protected XMPMetadata getXmpMetadata() {
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if (meta != null) {
try {
DomXmpParser xmpParser = new DomXmpParser();
return xmpParser.parse(meta.toByteArray());
} catch (XmpParsingException | IOException e) {
// TODO use logging or handle the error somehow
}
}
return XMPMetadata.createXMPMetadata();
}
String prefix = "<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>";
String suffix = "<?xpacket end=\"w\"?>";
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
return buffer.toByteArray();
}
/**
* Sets the producer if the overwrite flag is set or the producer is not already set.
* Sets the PDFVersion to 1.4 if the field is empty.
*/
protected void writeAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = getAdobePDFSchema(xmp);
if (overwrite || isEmpty(pdf.getProducer()))
pdf.setProducer(producer);
}
/**
* Returns the AdobePDFSchema from the XMPMetadata if it exists.
* If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
*/
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = xmp.getAdobePDFSchema();
if (pdf != null)
if (overwrite)
xmp.removeSchema(pdf);
else
return pdf;
return xmp.createAndAddAdobePDFSchema();
}
protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
if (overwrite || isEmpty(pdfaid.getConformance())) {
try {
pdfaid.setConformance(conformanceLevel.getLetter());
} catch (BadFieldValueException ex) {
// This should be impossible, because it would occur only if an illegal
// conformance level is supplied,
// however the enum enforces that the conformance level is valid.
throw new Error(ex);
}
}
pdfaid.setPart(3);
}
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
if (pdfaid != null)
if (overwrite)
xmp.removeSchema(pdfaid);
else
return pdfaid;
return xmp.createAndAddPFAIdentificationSchema();
}
protected void writeDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = getDublinCoreSchema(xmp);
if (dc.getFormat() == null)
dc.setFormat("application/pdf");
if ((overwrite || dc.getCreators() == null || dc.getCreators().isEmpty()) && creator != null)
dc.addCreator(creator);
if ((overwrite || dc.getDates() == null || dc.getDates().isEmpty()) && creator != null)
dc.addDate(Calendar.getInstance());
ArrayProperty titleProperty = dc.getTitleProperty();
if (titleProperty != null) {
if (overwrite && !isEmpty(title)) {
dc.removeProperty(titleProperty);
dc.setTitle(title);
} else if (titleProperty.getElementsAsString().stream().anyMatch("Untitled"::equalsIgnoreCase)) {
// remove unfitting ghostscript default
dc.removeProperty(titleProperty);
}
} else if (!isEmpty(title)) {
dc.setTitle(title);
}
}
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = xmp.getDublinCoreSchema();
if (dc != null)
if (overwrite)
xmp.removeSchema(dc);
else
return dc;
return xmp.createAndAddDublinCoreSchema();
}
protected void writeXMLBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = getXmpBasicSchema(xmp);
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
xsb.setCreatorTool(creatorTool);
if (overwrite || xsb.getCreateDate() == null)
xsb.setCreateDate(GregorianCalendar.getInstance());
}
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = xmp.getXMPBasicSchema();
if (xsb != null)
if (overwrite)
xmp.removeSchema(xsb);
else
return xsb;
return xmp.createAndAddXMPBasicSchema();
}
protected void writeDocumentInformation() {
String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
PDDocumentInformation info = doc.getDocumentInformation();
if (overwrite || info.getCreationDate() == null)
info.setCreationDate(Calendar.getInstance());
if (overwrite || info.getModificationDate() == null)
info.setModificationDate(Calendar.getInstance());
if (overwrite || (isEmpty(info.getAuthor()) && !isEmpty(author)))
info.setAuthor(author);
if (overwrite || (isEmpty(info.getProducer()) && !isEmpty(fullProducer)))
info.setProducer(fullProducer);
if (overwrite || (isEmpty(info.getCreator()) && !isEmpty(creator)))
info.setCreator(creator);
if (overwrite || (isEmpty(info.getTitle()) && !isEmpty(title)))
info.setTitle(title);
if (overwrite || (isEmpty(info.getSubject()) && !isEmpty(subject)))
info.setSubject(subject);
}
/**
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
*/
protected void addSRGBOutputIntend() {
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return;
}
try {
buffer.write(prefix.getBytes("UTF-8")); // see https://github.com/ZUGFeRD/mustangproject/issues/44
serializer.serialize(xmpMetadata, buffer, false);
buffer.write(suffix.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new TransformerException(e);
InputStream colorProfile = Thread.currentThread().getContextClassLoader().getResourceAsStream("sRGB.icc");
if (colorProfile != null) {
PDOutputIntent intent = new PDOutputIntent(doc, colorProfile);
intent.setInfo("sRGB IEC61966-2.1");
intent.setOutputCondition("sRGB IEC61966-2.1");
intent.setOutputConditionIdentifier("sRGB IEC61966-2.1");
intent.setRegistryName("http://www.color.org");
doc.getDocumentCatalog().addOutputIntent(intent);
}
} catch (IOException e) {
throw new TransformerException(e);
// TODO use logging or handle the error somehow
}
}
/**
* Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
*/
protected void setMarked() {
PDDocumentCatalog catalog = doc.getDocumentCatalog();
if (catalog.getMarkInfo() == null) {
catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject()));
}
catalog.getMarkInfo().setMarked(true);
}
/**
* Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
*/
protected void addStructureTreeRoot() {
if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
}
return buffer.toByteArray();
}
@@ -601,4 +759,14 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
return this;
}
/**
* Utility method inspired by apache commons-lang3 StringUtils.
*
* @param string the string to test
* @return true if the string is null or empty
*/
private boolean isEmpty(String string) {
return string == null || string.isEmpty();
}
}

View File

@@ -171,7 +171,7 @@ public class ZUGFeRDImporter {
xmlFact.setNamespaceAware(false);
DocumentBuilder builder = xmlFact.newDocumentBuilder();
ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
is.skip(guessBOMSize(is));
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
}
@@ -187,33 +187,6 @@ public class ZUGFeRDImporter {
}
/**
* Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists.
*
* @param is the ByteArrayInputStream used
* @throws IOException if can not be read from is
* @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a>
*/
private int guessBOMSize(ByteArrayInputStream is) throws IOException {
byte[] pad = new byte[4];
is.read(pad);
is.reset();
int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF);
int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF);
int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF);
//
if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) {
// UCS-4: BOM takes 4 bytes
return 4;
} else if (test3 == 0xEFBBFF) {
// UTF-8: BOM takes 3 bytes
return 3;
} else if (test2 == 0xFEFF || test2 == 0xFFFE) {
// UTF-16: BOM takes 2 bytes
return 2;
}
return 0;
}
protected String extractString(String xpathStr) {

View File

@@ -40,6 +40,13 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
"//*[local-name()=\"ExchangedDocument\"]");
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
xpr = xpath.compile(
"//*[local-name()=\"GrandTotalAmount\"]");
BigDecimal expectedGrandTotal = null;
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (totalNodes.getLength() > 0) {
expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent());
}
Date issueDate = null;
Date dueDate = null;
@@ -192,6 +199,70 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
}
// item level charges+allowances are not yet handled but a lower item price will be read,
// so the invoice remains arithmetically correct
// -> parse document level charges+allowances
xpr = xpath.compile(
"//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]");
NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < chargeNodes.getLength(); i++) {
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
boolean isCharge = true;
String chargeAmount = null;
String reason = null;
String taxPercent = null;
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:ChargeIndicator")) {
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
if (indicatorChilds.item(indicatorChildIndex).getNodeName().equals("udt:Indicator")) {
isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true");
}
}
} else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:ActualAmount")) {
chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent();
} else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:Reason")) {
reason = chargeNodeChilds.item(chargeChildIndex).getTextContent();
} else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:CategoryTradeTax")) {
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
if (taxChilds.item(taxChildIndex).getNodeName().equals("ram:RateApplicablePercent")) {
taxPercent = taxChilds.item(taxChildIndex).getTextContent();
}
}
}
}
if (isCharge) {
Charge c = new Charge(new BigDecimal(chargeAmount));
if (reason != null) {
c.setReason(reason);
}
if (taxPercent != null) {
c.setTaxPercent(new BigDecimal(taxPercent));
}
zpp.addCharge(c);
} else {
Allowance a = new Allowance(new BigDecimal(chargeAmount));
if (reason != null) {
a.setReason(reason);
}
if (taxPercent != null) {
a.setTaxPercent(new BigDecimal(taxPercent));
}
zpp.addAllowance(a);
}
}
TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getTotalGross().toPlainString();
if (!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) {
throw new ParseException("Could not reproduce the invoice, this could mean that it could not be read properly", 0);
}
return zpp;
}

Binary file not shown.

View File

@@ -20,13 +20,21 @@ package org.mustangproject.ZUGFeRD;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.xml.DomXmpParser;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -199,24 +207,10 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
public void testEdgeExport() {
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505newEdge.pdf";
// the writing part
final String SOURCE_PDF = "/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf";
try (InputStream SOURCE_PDF =
this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
) {
ZUGFeRDExporterFromA3 ze = new ZUGFeRDExporterFromA3()
.setProducer("My Application")
.setCreator(System.getProperty("user.name"))
.setZUGFeRDVersion(1)
.load(SOURCE_PDF);
ze.setTransaction(this);
String theXML = new String(ze.getProvider().getXML());
assertTrue(theXML.contains("<rsm:CrossIndustryDocument"));
ze.export(TARGET_PDF);
} catch (IOException e) {
e.printStackTrace();
fail("IOException should not happen in testEdgeExport ");
}
// the writing part
setupPdfUnderTest(SOURCE_PDF, TARGET_PDF);
// now check the contents (like MustangReaderTest)
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF);
@@ -236,4 +230,53 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
}
/**
* This test is based on #testEdgeExport but test only that the PDFIdentificationSchema is present and identifies
* the pdf as PDF/A-3 conformant.
*/
public void testEdgeExportIsPDFA3() throws Exception {
final String SOURCE_PDF = "/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf";
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505newEdge.pdf";
// the writing part
setupPdfUnderTest(SOURCE_PDF, TARGET_PDF);
// now check the contents
PDDocument doc = PDDocument.load(new File(TARGET_PDF));
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
assertNotNull("The pdf must contain XMPMetadata", meta);
byte[] xmpBytes = meta.toByteArray();
assertNotNull("The xmp metadata stream must not be null", xmpBytes);
DomXmpParser xmpParser = new DomXmpParser();
XMPMetadata xmp = xmpParser.parse(xmpBytes);
PDFAIdentificationSchema pdfai = xmp.getPDFIdentificationSchema();
assertNotNull("The pdf must contain a PDFIdentificationSchema", pdfai);
assertTrue("The PDF/A conformance must be A, B or U",
Arrays.asList("A", "B", "U", "a", "b", "u").contains(pdfai.getConformance()));
assertEquals("The PDF/A must be 3", (Integer) 3, pdfai.getPart());
}
private void setupPdfUnderTest(String sourcePath, String targetPath) {
try (InputStream inputStream = this.getClass().getResourceAsStream(sourcePath)) {
ZUGFeRDExporterFromA3 ze = new ZUGFeRDExporterFromA3()
.setProducer("My Application")
.setCreator(System.getProperty("user.name"))
.setZUGFeRDVersion(1)
.load(inputStream);
ze.setTransaction(this);
String theXML = new String(ze.getProvider().getXML());
assertTrue(theXML.contains("<rsm:CrossIndustryDocument"));
ze.export(targetPath);
} catch (IOException e) {
e.printStackTrace();
fail("IOException should not happen in testEdgeExport ");
}
}
}

View File

@@ -25,9 +25,11 @@ import junit.framework.TestCase;
import org.mustangproject.*;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
@@ -36,8 +38,8 @@ import static org.xmlunit.assertj.XmlAssert.assertThat;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class XRTest extends TestCase {
public void testPushExport() {
final String TARGET_XML = "./target/testout-XR.xml";
public void testXRExport() {
// the writing part
@@ -45,10 +47,15 @@ public class XRTest extends TestCase {
String number = "123";
String amountStr = "1.00";
BigDecimal amount = new BigDecimal(amountStr);
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE")).setOwnTaxID("4711").setOwnVATID("0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO), amount, new BigDecimal(1.0)));
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
.setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test","+49123456789","test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890","COBADEFXXX")))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE"))
.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)));
ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
zf2p.setProfile(Profiles.getByName("XRechnung"));
zf2p.generateXML(i);
String theXML = new String(zf2p.getXML());
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
@@ -60,7 +67,13 @@ public class XRTest extends TestCase {
assertThat(theXML).valueByXPath("//*[local-name()='DuePayableAmount']")
.asDouble()
.isEqualTo(1);
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(TARGET_XML));
writer.write(theXML);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}

View File

@@ -65,7 +65,7 @@ public class ZF2PushTest extends TestCase {
.load(SOURCE_PDF)) {
try {
ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setOccurrencePeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))));
ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))));
} catch (ParseException ex) {
throw new RuntimeException("Parse exception");
}
@@ -209,7 +209,7 @@ public class ZF2PushTest extends TestCase {
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("en16931")).ignorePDFAErrors()
.load(SOURCE_PDF)) {
ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setOccurrenceDate(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)
ze.setTransaction(new Invoice().setCurrency("CHF").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)))
.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)))
@@ -258,12 +258,14 @@ public class ZF2PushTest extends TestCase {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
try {
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()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID)).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(number)
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())
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setID("0009845"))
.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setID("0008734").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE")))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)), price, new BigDecimal(1.0)).addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16))))
.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)))
.setOccurrenceDate(sdf.parse("2020-01-01"))
.setDeliveryDate(sdf.parse("2020-02-02"))
.setDetailedDeliveryPeriod(sdf.parse("2020-01-01"),sdf.parse("2020-01-31"))
.setDeliveryDate(sdf.parse("2020-02-02")).setOwnVATID("DE0815").setNumber(number)
);
} catch (ParseException e) {
e.printStackTrace();
@@ -279,6 +281,8 @@ public class ZF2PushTest extends TestCase {
// now check the contents (like MustangReaderTest)
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PUSHEDGE);
assertTrue(zi.getUTF8().contains("0009845"));
assertTrue(zi.getUTF8().contains("0008734"));
assertTrue(zi.getUTF8().contains("item level 1/1"));
assertTrue(zi.getUTF8().contains("document level 2/2"));
@@ -296,7 +300,7 @@ public class ZF2PushTest extends TestCase {
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("en16931")).ignorePDFAErrors()
.load(SOURCE_PDF)) {
ze.setTransaction(new Invoice().setDueDate(new Date()).setOccurrenceDate(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)
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)), 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)))
@@ -339,7 +343,7 @@ public class ZF2PushTest extends TestCase {
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("extended")).ignorePDFAErrors()
.load(SOURCE_PDF)) {
ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setOccurrenceDate(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)
ze.setTransaction(new Invoice().setCurrency("CHF").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)), 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))))
@@ -386,7 +390,7 @@ public class ZF2PushTest extends TestCase {
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors()
.load(SOURCE_PDF)) {
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setOccurrencePeriod(new Date(), new Date()).setDeliveryDate(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").addVATID("DE0815")).setNumber(number)
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDetailedDeliveryPeriod(new Date(), new Date()).setDeliveryDate(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").addVATID("DE0815")).setNumber(number)
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty)).setCorrection("0815");

View File

@@ -41,11 +41,10 @@ import java.util.GregorianCalendar;
* used for this import, testout-ZF2New.pdf
*/
public class ZF2ZInvoiceImporterTest extends TestCase {
final String TARGET_PDF = "./target/testout-ZF2new.pdf";
public void testInvoiceImport() {
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF);
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2new.pdf");
boolean hasExceptions=false;
Invoice invoice=null;
@@ -82,7 +81,44 @@ public class ZF2ZInvoiceImporterTest extends TestCase {
assertEquals("Stadthausen", invoice.getSender().getLocation());
TransactionCalculator tc=new TransactionCalculator(invoice);
assertEquals(new BigDecimal("571.040000"),tc.getTotalGross());
assertEquals(new BigDecimal("571.04"),tc.getTotalGross());
// name street location zip country, contact name phone email, total amount
}
public void testItemAllowancesChargesImport() {
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf");
boolean hasExceptions=false;
Invoice invoice=null;
try {
invoice=zii.extractInvoice();
} catch (XPathExpressionException | ParseException e) {
hasExceptions=true;
}
assertFalse(hasExceptions);
TransactionCalculator tc=new TransactionCalculator(invoice);
assertEquals(new BigDecimal("18.33"),tc.getTotalGross());
}
public void testAllowancesChargesImport() {
ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushChargesAllowances.pdf");
boolean hasExceptions=false;
Invoice invoice=null;
try {
invoice=zii.extractInvoice();
} catch (XPathExpressionException | ParseException e) {
hasExceptions=true;
}
assertFalse(hasExceptions);
TransactionCalculator tc=new TransactionCalculator(invoice);
assertEquals(new BigDecimal("11.07"),tc.getTotalGross());
// name street location zip country, contact name phone email, total amount
@@ -91,4 +127,5 @@ public class ZF2ZInvoiceImporterTest extends TestCase {
}
}

View File

@@ -161,8 +161,8 @@
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
mvn clean compile assembly:single -->
<!-- or whatever version you use -->
<source>1.7</source>
<target>1.7</target>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<!-- /ZUV -->
@@ -171,6 +171,7 @@
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<minimizeJar>false</minimizeJar>
<filters>
<filter>

View File

@@ -1,5 +1,6 @@
package org.mustangproject.validator;
import org.mustangproject.XMLTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,14 +0,0 @@
package org.mustangproject.validator;
import org.dom4j.io.XMLWriter;
public class XMLTools extends XMLWriter {
public String escapeAttributeEntities(String s) {
return super.escapeAttributeEntities(s);
}
public String escapeElementEntities(String s) {
return super.escapeElementEntities(s);
}
}

View File

@@ -18,6 +18,7 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.mustangproject.XMLTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -61,7 +62,7 @@ public class XMLValidator extends Validator {
// file existence must have been checked before
try {
zfXML = new String(Files.readAllBytes(Paths.get(name)));
zfXML = new String(XMLTools.removeBOM(Files.readAllBytes(Paths.get(name))));
} catch (IOException e) {
ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(9)
@@ -112,8 +113,6 @@ public class XMLValidator extends Validator {
failedRules = 0;
ByteArrayInputStream xmlByteInputStream = new ByteArrayInputStream(zfXML.getBytes(StandardCharsets.UTF_8));
if (zfXML.isEmpty()) {
ValidationResultItem res = new ValidationResultItem(ESeverity.exception,
"XML data not found in " + filename
@@ -145,8 +144,8 @@ public class XMLValidator extends Validator {
// document.getElementsByTagNameNS("*",...
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(xmlByteInputStream);
InputSource is = new InputSource(new StringReader(zfXML));
Document doc = db.parse(is);
Element root = doc.getDocumentElement();

View File

@@ -1,11 +1,7 @@
package org.mustangproject.validator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
@@ -22,11 +18,13 @@ import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.mustangproject.XMLTools;
import org.riversun.bigdoc.bin.BigFileSearcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
//abstract class
@@ -165,8 +163,12 @@ public class ZUGFeRDValidator {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
byte[] content=Files.readAllBytes(file.toPath());
content= XMLTools.removeBOM(content);
String s=new String(content);
InputSource is = new InputSource(new StringReader(s));
Document doc = db.parse(is);
Element root = doc.getDocumentElement();
isXML=true;//no exception so far
@@ -176,6 +178,8 @@ public class ZUGFeRDValidator {
// probably no xml file, sth like SAXParseException content not allowed in prolog
// ignore isXML is already false
// in the tests, this may error-out anyway
//ex.printStackTrace();
}
if (isXML) {
pdfValidity = true;

View File

@@ -135,4 +135,28 @@ public class LibraryTest extends ResourceCase {
assertThat(res).valueByXPath("/validation/summary/@status")
.isEqualTo("valid");
}
/**
* automatically test the xrechnung
*/
public void testXRValidation() {
File tempFile = new File("../library/target/testout-XR.xml");
ZUGFeRDValidator zfv = new ZUGFeRDValidator();
String res = zfv.validate(tempFile.getAbsolutePath());
assertThat(res).valueByXPath("count(//error)")
.asInt()
.isEqualTo(0);
assertThat(res).valueByXPath("count(//notice)")
.asInt()
.isEqualTo(0);
assertThat(res).valueByXPath("/validation/summary/@status")
.asString()
.isEqualTo("valid");// expect to be valid because XR notices are, well, only notices
assertThat(res).valueByXPath("/validation/xml/summary/@status")
.asString()
.isEqualTo("valid");
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?><!-- the document starts with an invisible utf8 byte order mark, which is valid-->
<rsm:CrossIndustryInvoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>