Added the possibility to set ZUGFeRD conformance level

This commit is contained in:
Ivan Vaklinov
2014-09-23 14:18:06 +03:00
committed by Thomas Chojecki
parent 1a16ac296a
commit 28d6d3ca31
2 changed files with 71 additions and 47 deletions

View File

@@ -12,15 +12,24 @@ import org.apache.jempbox.xmp.XMPSchemaBasic;
import org.w3c.dom.Element;
public class XMPSchemaZugferd extends XMPSchemaBasic {
public class XMPSchemaZugferd extends XMPSchemaBasic
{
private String conformanceLevel = "COMFORT";
/**
* This is what needs to be added to the RDF metadata - basically the name of the embedded
* Zugferd file
* */
public XMPSchemaZugferd(org.apache.jempbox.xmp.XMPMetadata parent) {
public XMPSchemaZugferd(org.apache.jempbox.xmp.XMPMetadata parent, String conformanceLevel)
{
super(parent);
if (conformanceLevel != null)
{
this.conformanceLevel = conformanceLevel;
}
schema.setAttributeNS(NS_NAMESPACE, "xmlns:zf", //$NON-NLS-1$
"urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#"); //$NON-NLS-1$
// the superclass includes this two namespaces we don't need
@@ -42,7 +51,7 @@ public class XMPSchemaZugferd extends XMPSchemaBasic {
textNode = schema.getOwnerDocument().createElement(
"zf:ConformanceLevel"); //$NON-NLS-1$
XMLUtil.setStringValue(textNode, "COMFORT"); //$NON-NLS-1$
XMLUtil.setStringValue(textNode, this.conformanceLevel); //$NON-NLS-1$
schema.appendChild(textNode);
}

View File

@@ -38,8 +38,8 @@ import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
public class ZUGFeRDExporter {
/***
* You will need Apache PDFBox. To use the ZUGFeRD exporter,
implement IZUGFeRDExportableTransaction in yourTransaction
* You will need Apache PDFBox. To use the ZUGFeRD exporter,
implement IZUGFeRDExportableTransaction in yourTransaction
(which will require you to implement Product, Item and Contact)
then call
doc = PDDocument.load(PDFfilename);
@@ -55,22 +55,26 @@ public class ZUGFeRDExporter {
*
*/
//// MAIN CLASS
private String conformanceLevel="U";
private String versionStr="1.0";
// BASIC, COMFORT etc - may be set from outside.
private String ZUGFeRDConformanceLevel = null;
/**
* Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set, in which case passing a
* IZUGFeRDExportableTransaction is not necessary. By default it is null meaning the caller needs to
* pass a IZUGFeRDExportableTransaction for the XML to be populated.
*/
byte[] zugferdData = null;
private String currencyFormat(BigDecimal value, char decimalDelimiter) {
/*
* I needed 123,45, locale independent.I tried
@@ -81,12 +85,12 @@ public class ZUGFeRDExporter {
* DecimalFormatSymbols(); symbols.setDecimalSeparator(',');
* symbols.setGroupingSeparator(' ');
* df.setDecimalFormatSymbols(symbols);
*
*
* but that would not switch off grouping. Although I liked very much
* the (incomplete) "BNF diagram" in
* http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
* in the end I decided to calculate myself and take eur+sparator+cents
*
*
* This function will cut off, i.e. floor() subcent values Tests:
* System.err.println(utils.currencyFormat(new BigDecimal(0),
* ".")+"\n"+utils.currencyFormat(new BigDecimal("-1.10"),
@@ -95,10 +99,10 @@ public class ZUGFeRDExporter {
* ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3489"),
* ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3419"),
* ",")+"\n"+utils.currencyFormat(new BigDecimal("12"), ","));
*
*
* results 0.00 -1,10 -1,10 -1,01 20000123,34 20000123,34 12,00
*/
value=value.setScale( 2, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19
value=value.setScale( 2, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19
long totalCent = value.multiply(new BigDecimal(100)).intValue(); //now get the cents
long eurOnly = value.longValue();
long centOnly = Math.abs(totalCent % 100);
@@ -114,28 +118,28 @@ public class ZUGFeRDExporter {
/**
All files are PDF/A-3, setConformance refers to the level conformance.
PDF/A-3 has three coformance levels, called "A", "U" and "B".
PDF/A-3 has three coformance levels, called "A", "U" and "B".
PDF/A-3-B where B means only visually
preservable, U -standard for Mustang- means visually and unicode
preservable and A means full compliance, i.e. visually,
unicode and structurally preservable and tagged PDF, i.e. useful metainformation for blind people.
Feel free to pass "A" as new level if you know what you are doing :-)
*/
public void setConformanceLevel(String newLevel) {
conformanceLevel=newLevel;
}
/**
* Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on
* the metadata level, this will not e.g. convert graphics to JPG-2000)
* */
public PDDocumentCatalog PDFmakeA3compliant(PDDocument doc, String producer, String creator,
public PDDocumentCatalog PDFmakeA3compliant(PDDocument doc, String producer, String creator,
boolean attachZugferdHeaders) throws IOException,
TransformerException {
String fullProducer=producer + " (via mustangproject.org " + versionStr + ")";
@@ -177,13 +181,13 @@ public class ZUGFeRDExporter {
doc.getDocumentCatalog().setMarkInfo(markinfo);
*/
/*
*
To be on the safe side, we use level B without Markinfo because we can not
guarantee that the user correctly tagged the templates for the PDF.
*
To be on the safe side, we use level B without Markinfo because we can not
guarantee that the user correctly tagged the templates for the PDF.
* */
pdfaid.setConformance(conformanceLevel);//$NON-NLS-1$ //$NON-NLS-1$
pdfaid.setPart(3);
if (attachZugferdHeaders) {
@@ -198,11 +202,11 @@ public class ZUGFeRDExporter {
metadata.importXMPMetadata(xmp);
return cat;
}
private String getZugferdXMLForTransaction(IZUGFeRDExportableTransaction trans) {
SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$
String xml= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" //$NON-NLS-1$
+ "<rsm:CrossIndustryDocument xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:rsm=\"urn:ferd:CrossIndustryDocument:invoice:1p0\""
+ " xsi:schemaLocation=\"urn:ferd:CrossIndustryDocument:invoice:1p0 ../Schema/ZUGFeRD_1p0.xsd\""
+ " xmlns:ram=\"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12\""
@@ -296,8 +300,8 @@ public class ZUGFeRDExporter {
+ " <ram:Name>"+trans.getOwnBankName()+"</ram:Name>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " </ram:PayeeSpecifiedCreditorFinancialInstitution>\n" //$NON-NLS-1$
+ " </ram:SpecifiedTradeSettlementPaymentMeans>\n"; //$NON-NLS-1$
/*
HashMap<BigDecimal, BigDecimal> VATPercentAmountMap=trans.getVATPercentAmountMap();
for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
@@ -312,7 +316,7 @@ public class ZUGFeRDExporter {
+ " </ApplicableTradeTax>\n"; //$NON-NLS-1$
}
}*/
/* xml+= "
@@ -347,7 +351,7 @@ public class ZUGFeRDExporter {
+ " <ApplicablePercent>7</ApplicablePercent>\n"
+ " </AppliedTradeTax>\n"
+ " </SpecifiedLogisticsServiceCharge>\n"*/
xml=xml+ " <ram:SpecifiedTradePaymentTerms>\n" //$NON-NLS-1$
// + " <Description>Zahlbar innerhalb 30 Tagen netto bis 04.07.2013, 3% Skonto innerhalb 10 Tagen bis 15.06.2013</Description>\n"
+ " <ram:DueDateDateTime><udt:DateTimeString format=\"102\">"+zugferdDateFormat.format(trans.getDueDate())+"</udt:DateTimeString></ram:DueDateDateTime>\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$
@@ -370,8 +374,8 @@ public class ZUGFeRDExporter {
// + " </IncludedNote>\n"
// + " </AssociatedDocumentLineDocument>\n"
// + " </IncludedSupplyChainTradeLineItem>\n";
int lineID=0;
for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
lineID++;
@@ -379,7 +383,7 @@ public class ZUGFeRDExporter {
" <ram:AssociatedDocumentLineDocument>\n" //$NON-NLS-1$
+ " <ram:LineID>"+lineID+"</ram:LineID>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " </ram:AssociatedDocumentLineDocument>\n" //$NON-NLS-1$
+ " <ram:SpecifiedSupplyChainTradeAgreement>\n" //$NON-NLS-1$
+ " <ram:GrossPriceProductTradePrice>\n" //$NON-NLS-1$
+ " <ram:ChargeAmount currencyID=\"EUR\">"+currencyFormat(currentItem.getPriceGross(),'.')+"</ram:ChargeAmount>\n" //$NON-NLS-1$ //$NON-NLS-2$
@@ -395,7 +399,7 @@ public class ZUGFeRDExporter {
+ " <ram:BasisQuantity unitCode=\""+currentItem.getProduct().getUnit()+"\">1</ram:BasisQuantity>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " </ram:NetPriceProductTradePrice>\n" //$NON-NLS-1$
+ " </ram:SpecifiedSupplyChainTradeAgreement>\n" //$NON-NLS-1$
+ " <ram:SpecifiedSupplyChainTradeDelivery>\n" //$NON-NLS-1$
+ " <ram:BilledQuantity unitCode=\""+currentItem.getProduct().getUnit()+"\">"+currentItem.getQuantity()+"</ram:BilledQuantity>\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ " </ram:SpecifiedSupplyChainTradeDelivery>\n" //$NON-NLS-1$
@@ -417,12 +421,12 @@ public class ZUGFeRDExporter {
+ " <ram:Description>"+currentItem.getProduct().getDescription()+"</ram:Description>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " </ram:SpecifiedTradeProduct>\n" //$NON-NLS-1$
+ " </ram:IncludedSupplyChainTradeLineItem>\n"; //$NON-NLS-1$
}
xml=xml + " </rsm:SpecifiedSupplyChainTradeTransaction>\n" //$NON-NLS-1$
+ "</rsm:CrossIndustryDocument>"; //$NON-NLS-1$
return xml;
@@ -450,12 +454,12 @@ public class ZUGFeRDExporter {
dict.setName("AFRelationship", "Alternative"); // as defined in Zugferd standard //$NON-NLS-1$ //$NON-NLS-2$
dict.setString("UF", filename); //$NON-NLS-1$
if (zugferdData == null) // XML ZUGFeRD data not set externally, needs to be built
{
// create a dummy file stream, this would probably normally be a
// FileInputStream
byte[] zugferdRaw = getZugferdXMLForTransaction(trans).getBytes("UTF-8"); //$NON-NLS-1$
if ((zugferdRaw[0]==(byte)0xEF)&&(zugferdRaw[1]==(byte)0xBB)&&(zugferdRaw[2]==(byte)0xBF)) {
@@ -463,10 +467,10 @@ public class ZUGFeRDExporter {
zugferdData=new byte[zugferdRaw.length-3];
System.arraycopy(zugferdRaw,3,zugferdData,0,zugferdRaw.length-3);
} else {
zugferdData=zugferdRaw;
zugferdData=zugferdRaw;
}
}
ByteArrayInputStream fakeFile = new ByteArrayInputStream(zugferdData);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
// now lets some of the optional parameters
@@ -494,7 +498,7 @@ public class ZUGFeRDExporter {
/**
* Sets the ZUGFeRD XML data to be attached as a single byte array. This is useful for
* use-cases where the XML has already been produced by some external API or component.
*
*
* @param zugferdData XML data to be set as a byte array (XML file in raw form).
*/
public void setZUGFeRDXMLData(byte[] zugferdData)
@@ -502,14 +506,25 @@ public class ZUGFeRDExporter {
this.zugferdData = zugferdData;
}
/**
* Sets the ZUGFeRD conformance level (override).
*
* @param ZUGFeRDConformanceLevel the new conformance level
*/
public void setZUGFeRDConformanceLevel(String ZUGFeRDConformanceLevel)
{
this.ZUGFeRDConformanceLevel = ZUGFeRDConformanceLevel;
}
/***
* This will add both the RDF-indication which embedded file is Zugferd and the
* neccessary PDF/A schema extension description to be able to add this information to RDF
* This will add both the RDF-indication which embedded file is Zugferd and the
* neccessary PDF/A schema extension description to be able to add this information to RDF
* @param metadata
*/
private void addZugferdXMP(XMPMetadata metadata) {
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata);
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, this.ZUGFeRDConformanceLevel);
zf.setAbout(""); //$NON-NLS-1$
metadata.addSchema(zf);