pdfa/zugferd1_invoice_pdfa3b
Create a PDF/A-3b ZUGFeRD 1 invoice from scratch.
Download Java Code Show Output
/*
* Create a PDF/A-3b ZUGFeRD 1.0 invoice from scratch.
*
* This program is based on information released in the "ZUGFeRD-Infopaket"
* by FeRD:
*
* http://www.ferd-net.de/
*
* The XML invoice uses the ZUGFeRD profile "BASIC".
*
* Required software: PDFlib+PDI/PPS 10
* (set run_sample_with_pdi=false below to run the sample code without PDI)
* Required data: two fonts, stationery PDF
*/
package com.pdflib.cookbook.pdflib.pdfa;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.pdflib.pdflib;
import com.pdflib.PDFlibException;
/*
* Enable these import for validating the generated XML against the
* ZUGFeRD XML schema.
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
*/
public class zugferd1_invoice_pdfa3b {
private static final String COMMENT = "Created by PDFlib Cookbook program "
+ "zugferd1_invoice_pdfa3b.java\n"
+ "http://www.pdflib.com/pdflib-cookbook/";
final static boolean run_sample_with_pdi = true;
final static double fontsize = 11;
final static double fontsizesmall = 9;
final static double x_table = 55;
final static double tablewidth = 475;
final static double headerheight = 4 * fontsize;
final static double y_address = 682;
final static double x_salesrep = 455;
final static double y_invoice = 542;
final static double y_invoice_p2 = 800;
final static double footer_bottom = 40;
final static double bottom = footer_bottom + 4 * fontsize;
final static double imagesize = 90;
final static String fontname = "NotoSerif-Regular";
final static String basefontoptions = "fontname=" + fontname + " fontsize="
+ fontsize;
/*
* XML namespaces used in the invoice document.
*/
final static String NAMESPACE_RSM =
"urn:ferd:CrossIndustryDocument:invoice:1p0";
final static String NAMESPACE_RAM =
"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12";
final static String NAMESPACE_UDT =
"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15";
/**
* For simplicity only Euro is used as the currency.
*/
final static String CURRENCY = "EUR";
/**
* The German VAT percentage.
*/
private static double VAT_PERCENT = 19;
/**
* Encapsulation of information for buyer and seller.
*/
static class trade_party {
String name;
String person_name;
String postcode;
String street;
String line_two;
String city;
String country_code;
String country;
String phone;
String fax;
String email;
String url;
String id;
String vat_id;
String director;
String company_registration;
String bank_name;
String iban;
trade_party(String name, String person_name, String postcode,
String street, String line_two, String city, String country_code,
String country, String phone, String fax, String email, String url,
String id, String vat_id, String director,
String company_registration, String bank_name, String iban) {
this.name = name;
this.person_name = person_name;
this.postcode = postcode;
this.street = street;
this.line_two = line_two;
this.city = city;
this.country_code = country_code;
this.country = country;
this.phone = phone;
this.fax = fax;
this.email = email;
this.url = url;
this.id = id;
this.vat_id = vat_id;
this.director = director;
this.company_registration = company_registration;
this.bank_name = bank_name;
this.iban = iban;
}
}
/**
* Information about the sender of the invoice.
*/
static final trade_party seller = new trade_party("Kraxi GmbH", null,
"12345", "Flugzeugallee 17", null, "Papierfeld", "DE", "Deutschland",
"(0123) 4567", "(0123) 4568", "info@kraxi.com", "www.kraxi.com", null,
"DE123456789", "GF Paul Kraxi", "M\u00FCnchen HRB 999999",
"Postbank M\u00FCnchen", "IBAN DE28700100809999999999");
/**
* Information about the recipient of the invoice.
*/
static final trade_party buyer = new trade_party(
"Papierflieger-Vertriebs-GmbH", "Helga Musterfrau", "34567",
"Rabattstr. 25", null, "Osterhausen", "DE", "Deutschland", null, null,
null, null, "987-654", null, null, null, null, null);
/**
* Place company stationery.
*
* @throws Exception
*/
static void create_stationery(pdflib p) throws Exception {
String sender = seller.name + " • " + seller.street
+ " • " + seller.postcode + " " + seller.city + " • "
+ seller.country;
String stationeryfontname = "NotoSerif-Regular";
double y_company_logo = 748;
String senderfull = seller.street + "\n" + seller.postcode + " "
+ seller.city + "\n" + seller.country
+ "<nextline leading=50%><nextparagraph leading=120%>" + "Tel. "
+ seller.phone + "\n" + "Fax " + seller.fax
+ "<nextline leading=50%><nextparagraph leading=120%>"
+ seller.email + "\n" + seller.url;
/*
* Print company logo and sender address
*/
if (run_sample_with_pdi) {
int page, stationery;
String stationeryfilename = "kraxi_logo2_de.pdf";
stationery = p.open_pdi_document(stationeryfilename, "");
page = p.open_pdi_page(stationery, 1, "");
p.fit_pdi_page(page, 0, y_company_logo,
"boxsize={595 85} position={65 center}");
p.close_pdi_page(page);
p.close_pdi_document(stationery);
}
String optlist = basefontoptions + " fontsize=" + fontsizesmall
+ " fontname=" + stationeryfontname + " charref=true";
p.fit_textline(sender, x_table, y_address + fontsize, optlist);
/*
* Print full company contact details
*/
optlist = basefontoptions + " fontname=" + stationeryfontname
+ " leading=125% fillcolor={rgb 0.35 0.36 0.37}";
int tf = p.create_textflow(senderfull, optlist);
optlist = "verticalalign=bottom";
p.fit_textflow(tf, x_salesrep, y_address, x_salesrep + imagesize,
y_address + 150, optlist);
p.delete_textflow(tf);
/*
* Print mandatory information about company in footer.
*/
String footer = seller.name + "\tSitz der Gesellschaft\t"
+ "USt-IdNr\t" + seller.bank_name + "\n" + seller.director + "\t"
+ seller.company_registration + "\t" + seller.vat_id + "\t"
+ seller.iban;
optlist = "ruler={" + (tablewidth * 0.2) + " " + (tablewidth * 0.45)
+ " " + (tablewidth * 0.7) + "} tabalignment={left left left}"
+ " hortabmethod=ruler " + basefontoptions + " fontsize="
+ fontsizesmall + " fontname=" + stationeryfontname;
tf = p.add_textflow(-1, footer, optlist);
p.fit_textflow(tf, x_table, footer_bottom, x_table + tablewidth,
footer_bottom + 4 * fontsizesmall, "");
p.delete_textflow(tf);
}
/**
* Print receiver address.
*
* @throws PDFlibException
*/
static void create_address(pdflib p) throws PDFlibException {
String address = buyer.name + "\n" + buyer.person_name + "\n"
+ buyer.street + "\n" + buyer.postcode + " " + buyer.city + "\n"
+ buyer.country;
String optlist = basefontoptions + " leading=120%";
int tf = p.create_textflow(address, optlist);
p.fit_textflow(tf, x_table, y_address, x_table + tablewidth / 2,
y_address - 100, "");
p.delete_textflow(tf);
}
/**
* Print header and date.
*
* @throws PDFlibException
*/
static void create_table_header(pdflib p, Calendar now,
String invoice_number, String customer_number, String order_number)
throws PDFlibException {
String invoiceheader = "Rechnungsnummer: " + invoice_number;
String date = "Liefer- und Rechnungsdatum: "
+ DateFormat.getDateInstance(DateFormat.LONG, Locale.GERMAN)
.format(now.getTime());
String optlist = "ruler={" + tablewidth + "} tabalignment={right} "
+ "hortabmethod=ruler " + basefontoptions;
String text = invoiceheader + "\t" + date + "\n" + "\tKundennummer: "
+ customer_number + "\n" + "\tIhre Auftragsnummer: " + order_number
+ "\n" + "\tBetr\u00E4ge in " + CURRENCY;
int tf = p.add_textflow(-1, text, optlist);
p.fit_textflow(tf, x_table, y_invoice, x_table + tablewidth, y_invoice
+ headerheight, "");
p.delete_textflow(tf);
}
/**
* Information about the articles referenced in the invoice.
*/
static class articledata {
articledata(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
String name;
double price;
int quantity;
}
public static void main(String argv[]) {
/* This is where font/image/PDF input files live. Adjust as necessary. */
String searchpath = "../input";
/* By default annotations are also imported. In some cases this
* requires the Noto fonts for creating annotation appearance streams.
* We therefore set the searchpath to also point to the font directory.
*/
String fontpath = "../resource/font";
String payment_terms =
"Zahlbar innerhalb von 30 Tagen netto auf unser Konto. "
+ "Bitte geben Sie dabei die Rechnungsnummer an. Skontoabz\u00FCge "
+ "werden nicht akzeptiert.";
articledata[] data = { new articledata("Superdrachen", 20, 2),
new articledata("Turbo Flyer", 40, 5),
new articledata("Sturzflug-Geier", 180, 1),
new articledata("Eisvogel", 50, 3),
new articledata("Storch", 20, 10), new articledata("Adler", 75, 1),
new articledata("Kostenlose Zugabe", 0, 1) };
String[] headers = { "Pos.", "Artikelbeschreibung", "Menge", "Preis",
"Betrag" };
String[] alignments = { "right", "left", "right", "right", "right" };
/* Get the current date */
Calendar calendar = Calendar.getInstance();
String outfile = "zugferd1_invoice_pdfa3b.pdf";
String title = "ZUGFeRD 1 Rechnung";
String xmpfile = "ZUGFeRD1_extension_schema.xmp";
String xml_invoice_file = "ZUGFeRD-invoice.xml";
String invoice_number = calendar.get(Calendar.YEAR) + "-03";
String document_name = "Rechnung";
String customer_number = "987-654";
String order_number = "ABC-123";
String optlist;
/*
* Format the numbers for Germany. Note that the numbers inside
* the XML invoice must be formatted as plain numbers, without
* any thousands separator.
*/
final NumberFormat priceFormatDE = NumberFormat
.getInstance(Locale.GERMAN);
priceFormatDE.setMaximumFractionDigits(2);
priceFormatDE.setMinimumFractionDigits(2);
pdflib p = null;
int exitcode = 0;
try {
/*
* Set up document builder for building the ZUGFeRD XML invoice.
*/
Document dom = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element root = xml_create_root(dom);
Comment comment = dom.createComment(COMMENT);
root.appendChild(comment);
Element document_context = xml_create_document_context(dom);
root.appendChild(document_context);
Element document_header = xml_create_document_header(dom,
document_name, invoice_number, calendar);
root.appendChild(document_header);
Element supply_chain_trade_transaction = dom.createElementNS(
NAMESPACE_RSM, "rsm:SpecifiedSupplyChainTradeTransaction");
root.appendChild(supply_chain_trade_transaction);
Element supply_chain_trade_agreement =
xml_create_applicable_supply_chain_trade_agreement(
dom, seller, buyer);
supply_chain_trade_transaction
.appendChild(supply_chain_trade_agreement);
Element delivery =
xml_create_applicable_supply_chain_trade_delivery(dom, calendar);
supply_chain_trade_transaction
.appendChild(delivery);
p = new pdflib();
p.set_option("SearchPath={" + searchpath + "}");
p.set_option("SearchPath={" + fontpath + "}");
/*
* This means we don't have to check error return values, but will
* get an exception in case of runtime problems.
*/
p.set_option("errorpolicy=exception");
p.begin_document(outfile, "pdfa=PDF/A-3b metadata={filename={"
+ xmpfile + "}}");
p.set_info("Creator", "PDFlib Cookbook");
p.set_info("Title", title);
/* Use sRGB output intent so that we can use RGB color */
if (p.load_iccprofile("sRGB", "usage=outputintent") == -1){
System.err.println("Error: " + p.get_errmsg() );
System.err.println("See www.pdflib.com for output intent ICC profiles.");
p.delete();
System.exit(2);
}
/*
* Need to add the ram:ApplicableSupplyChainTradeSettlement
* element before the ram:IncludedSupplyChainTradeLineItems,
* therefore calculate total up-front.
*/
double total = 0;
int i;
for (i = 0; i < data.length; i++) {
double sum = data[i].price * data[i].quantity;
total += sum;
}
BigDecimal bd_netto = new BigDecimal(total).setScale(2,
RoundingMode.HALF_UP);
BigDecimal bd_vat_pct = new BigDecimal(VAT_PERCENT / 100);
BigDecimal bd_vat = bd_netto.multiply(bd_vat_pct).setScale(2,
RoundingMode.HALF_UP);
BigDecimal bd_brutto = bd_netto.add(bd_vat).setScale(2,
RoundingMode.HALF_UP);
Element supply_chain_trade_settlement =
xml_create_supply_chain_trade_settlement(
dom, invoice_number, payment_terms,
bd_netto, bd_vat);
supply_chain_trade_transaction
.appendChild(supply_chain_trade_settlement);
/*
* Create and place table with article list
*/
/* Header row */
int row = 1;
int tbl = -1;
int col;
for (col = 1; col <= headers.length; col++) {
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} margin=2";
tbl = p
.add_table_cell(tbl, col, row, headers[col - 1], optlist);
}
row++;
/* Data rows: one for each article */
for (i = 0; i < data.length; i++) {
double sum = data[i].price * data[i].quantity;
col = 1;
/* column 1: Position */
int item_nr = i + 1;
String item = Integer.toString(item_nr);
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, col++, row, item, optlist);
/* column 2: Artikelbeschreibung */
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} colwidth=50% margin=2";
tbl = p.add_table_cell(tbl, col++, row, data[i].name, optlist);
/* column 3: Menge */
String quantity = Integer.toString(data[i].quantity);
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, col++, row, quantity, optlist);
/* column 4: Preis */
String price = priceFormatDE.format(data[i].price);
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, col++, row, price, optlist);
/* column 5: Betrag */
String sum_string = priceFormatDE.format(sum);
optlist = "fittextline={position={" + alignments[col - 1]
+ " center} " + basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, col++, row, sum_string, optlist);
Element line_item =
xml_create_included_supply_chain_trade_line_item(
dom, item, data[i].name, quantity, new BigDecimal(
data[i].price).setScale(2, RoundingMode.HALF_UP),
new BigDecimal(sum).setScale(2, RoundingMode.HALF_UP));
supply_chain_trade_transaction.appendChild(line_item);
row++;
}
/* Print totals in the rightmost column */
optlist = "fittextline={position={right center} " + basefontoptions
+ "} colspan=2 margin=2";
tbl = p.add_table_cell(tbl, headers.length - 2, row,
"Rechnungssumme netto", optlist);
optlist = "fittextline={position={"
+ alignments[headers.length - 1] + " center} "
+ basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, headers.length, row++,
priceFormatDE.format(bd_netto), optlist);
optlist = "fittextline={position={right center} " + basefontoptions
+ "} colspan=2 margin=2";
tbl = p.add_table_cell(tbl, headers.length - 2, row,
"zuz\u00FCglich 19% MwSt.", optlist);
optlist = "fittextline={position={"
+ alignments[headers.length - 1] + " center} "
+ basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, headers.length, row++,
priceFormatDE.format(bd_vat), optlist);
optlist = "fittextline={position={right center} " + basefontoptions
+ "} colspan=2 margin=2";
tbl = p.add_table_cell(tbl, headers.length - 2, row,
"Rechnungssumme brutto", optlist);
optlist = "fittextline={position={"
+ alignments[headers.length - 1] + " center} "
+ basefontoptions + "} margin=2";
tbl = p.add_table_cell(tbl, headers.length, row++,
priceFormatDE.format(bd_brutto), optlist);
/*
* Add the total amounts to the ZUGFeRD
* ApplicableSupplyChainTradeSettlement element.
*/
Element monetary_summation = xml_create_monetary_summation(dom,
bd_netto, bd_vat, bd_brutto);
supply_chain_trade_settlement.appendChild(monetary_summation);
/* Footer row with terms of payment */
optlist = basefontoptions + " alignment=justify leading=120%";
int tf = p.create_textflow(payment_terms, optlist);
optlist = "rowheight=1 margin=2 margintop=" + (2 * fontsize)
+ " colspan=" + headers.length + " textflow=" + tf;
tbl = p.add_table_cell(tbl, 1, row++, "", optlist);
/*
* Place the table instance(s), creating pages as required.
*/
String result;
int pagecount = 0;
do {
double top;
p.begin_page_ext(0, 0, "width=a4.width height=a4.height");
if (++pagecount == 1) {
create_stationery(p);
create_address(p);
top = y_invoice - 3 * fontsize;
create_table_header(p, calendar, invoice_number,
customer_number, order_number);
}
else {
top = y_invoice_p2;
}
/* Place the table on the page; Shade every other row. */
optlist = "header=1 fill={{area=rowodd fillcolor={gray 0.9}}}";
result = p.fit_table(tbl, x_table, bottom,
x_table + tablewidth, top, optlist);
if (result.equals("_error")) {
throw new Exception("Couldn't place table: "
+ p.get_errmsg());
}
p.end_page_ext("");
}
while (result.equals("_boxfull"));
p.delete_table(tbl, "");
DOMSource source = new DOMSource(root);
/*
* Optional code to validate the generated XML file against
* the schema. This is useful for development purposes. Replace
* "/your/path/to/the/schema/ZUGFeRD_1p0.xsd" with the actual
* pathname to the ZUGFeRD XML schema, and activate the code
* by removing this comment. Also enable the corresponding
* import statements at the top of the file.
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new File("/your/path/to/the/schema/ZUGFeRD_1p0.xsd"));
Validator validator = schema.newValidator();
validator.validate(source);
*/
/*
* Create XML document and put it into a PVF file.
*/
StringWriter sw = new StringWriter();
StreamResult xml_result = new StreamResult(sw);
Transformer transformer = TransformerFactory.newInstance()
.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(
"{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(source, xml_result);
String pvf_name = "/pvf/" + xml_invoice_file;
byte[] xml_bytes = sw.toString().getBytes("UTF-8");
p.create_pvf(pvf_name, xml_bytes, "");
/*
* Load the XML file for the invoice, and associate it with the
* document with relationship "Alternative". Also mark it as an
* attachment that can be retrieved with Acrobat's normal attachment
* dialog.
*/
int xml_invoice = p.load_asset("Attachment", pvf_name,
"mimetype=text/xml "
+ "description={Rechnungsdaten im ZUGFeRD-XML-Format} "
+ "relationship=Alternative documentattachment=true");
p.end_document("associatedfiles={" + xml_invoice + "}");
p.delete_pvf(pvf_name);
}
catch (PDFlibException e) {
System.err.println("PDFlib exception occurred:");
System.err.println("[" + e.get_errnum() + "] " + e.get_apiname() +
": " + e.get_errmsg());
exitcode = 1;
}
catch (Exception e) {
System.err.println(e);
exitcode = 1;
}
finally {
if (p != null) {
p.delete();
}
System.exit(exitcode);
}
}
/**
* Create an "IncludedSupplyChainTradeLineItem" XML element.
*
* @param dom
* XML document
* @param position
* Position in invoice
* @param name
* Article name
* @param quantity
* Quantity of article
* @param price
* Price per article
* @param sum
* Sum for position
* @return XML element "IncludedSupplyChainTradeLineItem"
*/
private static Element xml_create_included_supply_chain_trade_line_item(
Document dom, String position, String name, String quantity,
BigDecimal price, BigDecimal sum) {
Element line_item = dom
.createElementNS(NAMESPACE_RAM, "ram:IncludedSupplyChainTradeLineItem");
/*
* The Schematron validation wants an empty
* "ram:AssociatedDocumentLineDocument" element without a "ram:LineID"
* child element for a BASIC invoice.
*/
Element line_document = dom
.createElementNS(NAMESPACE_RAM, "ram:AssociatedDocumentLineDocument");
line_item.appendChild(line_document);
Element delivery = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedSupplyChainTradeDelivery");
Element billed_quantity = append_text_element(dom, delivery,
NAMESPACE_RAM, "ram:BilledQuantity",
new BigDecimal(quantity).setScale(4).toString());
/*
* C62 means "one item".
*/
billed_quantity.setAttribute("unitCode", "C62");
delivery.appendChild(billed_quantity);
line_item.appendChild(delivery);
/*
* The Schematron validation wants an empty
* "ram:SpecifiedSupplyChainTradeSettlement" element without a
* "ram:SpecifiedTradeSettlementMonetarySummation" child element for
* a BASIC invoice.
*/
Element trade_settlement = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedSupplyChainTradeSettlement");
line_item.appendChild(trade_settlement);
Element product = dom.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTradeProduct");
append_text_element(dom, product, NAMESPACE_RAM, "ram:Name", name);
line_item.appendChild(product);
return line_item;
}
/**
* Create a "SpecifiedTradeSettlementMonetarySummation" XML element.
*
* @param dom
* XML document
* @param netto
* Netto price
* @param vat
* Calculated VAT sum
* @param brutto
* Brutto price (netto + vat)
*
* @return New "SpecifiedTradeSettlementMonetarySummation" XML element
*/
private static Element xml_create_monetary_summation(Document dom,
BigDecimal netto, BigDecimal vat, BigDecimal brutto) {
Element monetary_summation = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTradeSettlementMonetarySummation");
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:LineTotalAmount", netto);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:ChargeTotalAmount", new BigDecimal(0).setScale(2));
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:AllowanceTotalAmount", new BigDecimal(0).setScale(2));
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:TaxBasisTotalAmount", netto);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:TaxTotalAmount", vat);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:GrandTotalAmount", brutto);
return monetary_summation;
}
/**
* Create "ApplicableSupplyChainTradeSettlement" XML element.
*
* @param dom
* XML document
* @param invoice_number
* Invoice number
* @param payment_terms
* Payment terms (freetext)
* @param netto
* Net sum
* @param vat
* Calculated VAT
*
* @return New "ApplicableSupplyChainTradeSettlement" XML element
*/
private static Element xml_create_supply_chain_trade_settlement(
Document dom, String invoice_number, String payment_terms,
BigDecimal netto, BigDecimal vat) {
Element settlement = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableSupplyChainTradeSettlement");
append_text_element(dom, settlement,
NAMESPACE_RAM, "ram:PaymentReference", invoice_number);
append_text_element(dom, settlement,
NAMESPACE_RAM, "ram:InvoiceCurrencyCode", CURRENCY);
Element applicable_trade_tax = dom.createElementNS(NAMESPACE_RAM, "ram:ApplicableTradeTax");
append_currency_element(dom, applicable_trade_tax,
NAMESPACE_RAM, "ram:CalculatedAmount", vat);
append_text_element(dom, applicable_trade_tax, NAMESPACE_RAM, "ram:TypeCode", "VAT");
append_currency_element(dom, applicable_trade_tax,
NAMESPACE_RAM, "ram:BasisAmount", netto);
append_text_element(dom, applicable_trade_tax, NAMESPACE_RAM, "ram:ApplicablePercent",
new BigDecimal(VAT_PERCENT).setScale(2).toString());
settlement.appendChild(applicable_trade_tax);
return settlement;
}
/**
* Create "rsm:SpecifiedExchangedDocumentContext" XML element.
*
* @param dom
* XML document
*
* @return New "rsm:SpecifiedExchangedDocumentContext" XML element
*
* @throws DOMException
*/
private static Element xml_create_document_context(Document dom)
throws DOMException {
Element document_context = dom.createElementNS(NAMESPACE_RSM,
"rsm:SpecifiedExchangedDocumentContext");
/*
* Do not mistake this for a real invoice...
*/
Element test_indicator_element =
dom.createElementNS(NAMESPACE_RAM, "ram:TestIndicator");
append_text_element(dom, test_indicator_element, NAMESPACE_UDT,
"udt:Indicator", "true");
document_context.appendChild(test_indicator_element);
Element context_param = dom
.createElementNS(NAMESPACE_RAM,
"ram:GuidelineSpecifiedDocumentContextParameter");
document_context.appendChild(context_param);
append_text_element(dom, context_param, NAMESPACE_RAM, "ram:ID",
"urn:ferd:CrossIndustryDocument:invoice:1p0:basic");
return document_context;
}
/**
* Create "rsm:HeaderExchangedDocument" XML element.
*
* @param dom
* XML document
* @param document_name
* Name of document
* @param invoice_number
* Invoice number
* @param now
* Timestamp for invoice creation
*
* @return New "rsm:HeaderExchangedDocument" XML element
*
* @throws DOMException
*/
private static Element xml_create_document_header(Document dom,
String document_name, String invoice_number, Calendar now)
throws DOMException {
Element header = dom.createElementNS(NAMESPACE_RSM,
"rsm:HeaderExchangedDocument");
append_text_element(dom, header, NAMESPACE_RAM, "ram:ID", invoice_number);
append_text_element(dom, header, NAMESPACE_RAM, "ram:Name", document_name);
/*
* UNCL 1001 Document Name Code; in BASIC und COMFORT only 380:
* ("Gutschrift im Sinne von Rechnungskorrekturen mit negativen Werten")
*/
append_text_element(dom, header, NAMESPACE_RAM, "ram:TypeCode", "380");
append_date_time_string_element(dom,
header, NAMESPACE_RAM, "ram:IssueDateTime", now);
Element note_element = dom.createElementNS(NAMESPACE_RAM,
"ram:IncludedNote");
append_text_element(dom, note_element, NAMESPACE_RAM, "ram:Content",
COMMENT);
header.appendChild(note_element);
return header;
}
/**
* Create "udt:DateTimeString" XML element
*
* @param dom
* XML document
* @param parent
* Parent XML element for new XML element
* @param namespace_uri
* Namespace URI of new XML element
* @param element_name
* Name of new XML element
* @param now
* Timestamp
*
* @throws DOMException
*/
private static void append_date_time_string_element(Document dom,
Element parent,
String namespace_uri, String element_name, Calendar now)
throws DOMException {
String date_string = "" + now.get(Calendar.YEAR)
+ String.format("%02d", now.get(Calendar.MONTH) + 1)
+ String.format("%02d", now.get(Calendar.DAY_OF_MONTH));
Element date_time = dom.createElementNS(namespace_uri,
element_name);
Element date_time_string = append_text_element(dom, date_time,
NAMESPACE_UDT, "udt:DateTimeString", date_string);
date_time_string.setAttribute("format", "102");
date_time.appendChild(date_time_string);
parent.appendChild(date_time);
}
/**
* Convenience function for inserting an element with a namespace prefix
* with text contents into a parent XML element.
*
* @param dom
* XML document
* @param parent
* Parent XML element for new XML element
* @param namespace_uri
* Namespace URI of new XML element
* @param element_name
* Name of new XML element
* @param value
* Contents for new XML element
*
* @return The new XML element
*
* @throws DOMException
*/
private static Element append_text_element(Document dom, Element parent,
String namespace_uri, String element_name, String value)
throws DOMException {
Element new_element = dom.createElementNS(namespace_uri, element_name);
new_element.appendChild(dom.createTextNode(value));
parent.appendChild(new_element);
return new_element;
}
/**
* Convenience function for inserting an element that is a currency value
* into a parent XML element. The currency identifier is fixed.
*
* @param dom
* XML document
* @param parent
* Parent XML element for new XML element
* @param namespace_uri
* Namespace uri of new XML element
* @param element_name
* Name of new XML element
* @param value
* Value for the new XML element
*
* @return The new XML element
*
* @throws DOMException
*/
private static Element append_currency_element(Document dom,
Element parent, String namespace_uri, String element_name,
BigDecimal value)
throws DOMException {
/*
* Format the number as a plain number, without any thousands
* separator.
*/
Element new_element = append_text_element(dom, parent, namespace_uri,
element_name, value.toPlainString());
new_element.setAttribute("currencyID", CURRENCY);
return new_element;
}
/**
* Create the root element of the XML invoice.
*
* @param dom
* XML document
*
* @return The new root XML element
*
* @throws DOMException
*/
private static Element xml_create_root(Document dom) throws DOMException {
/*
* Create root node of invoice XML with the necessary attributes for
* declaring namespaces and ZUGFeRD XML schema.
*/
Element root = dom.createElementNS(NAMESPACE_RSM, "rsm:CrossIndustryDocument");
root.setAttribute("xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance");
root.setAttribute("xmlns:udt", NAMESPACE_UDT);
root.setAttribute("xmlns:rsm", NAMESPACE_RSM);
root.setAttribute("xmlns:ram", NAMESPACE_RAM);
return root;
}
/**
* Create a trade party element.
*
* @param dom
* XML document
* @param namespace_uri
* Namespace for the new XML element
* @param element_name
* Name for the new XML element
* @param party
* Information for creating the element
*
* @return The new XML element
*/
private static Element xml_create_trade_party(Document dom,
String namespace_uri, String element_name, trade_party party) {
Element trade_party_element =
dom.createElementNS(namespace_uri, element_name);
append_text_element(dom, trade_party_element,
NAMESPACE_RAM, "ram:Name", party.name);
Element postal_trade_address =
dom.createElementNS(NAMESPACE_RAM, "ram:PostalTradeAddress");
trade_party_element.appendChild(postal_trade_address);
append_text_element(dom, postal_trade_address,
NAMESPACE_RAM, "ram:PostcodeCode", party.postcode);
append_text_element(dom, postal_trade_address,
NAMESPACE_RAM, "ram:LineOne", party.street);
if (party.line_two != null) {
append_text_element(dom, postal_trade_address,
NAMESPACE_RAM, "ram:LineTwo", party.line_two);
}
append_text_element(dom, postal_trade_address,
NAMESPACE_RAM, "ram:CityName", party.city);
append_text_element(dom, postal_trade_address,
NAMESPACE_RAM, "ram:CountryID", party.country_code);
if (party.vat_id != null) {
Element tax_registration = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTaxRegistration");
Element id = append_text_element(dom, tax_registration,
NAMESPACE_RAM, "ram:ID", party.vat_id);
id.setAttribute("schemeID", "VA");
trade_party_element.appendChild(tax_registration);
}
return trade_party_element;
}
/**
* Create an "ApplicableSupplyChainTradeAgreement" XML element.
*
* @param dom
* XML document
* @param seller
* Seller information
* @param buyer
* Buyer information
*
* @return The new "ApplicableSupplyChainTradeAgreement" XML element
*
* @throws DOMException
*/
private static Element xml_create_applicable_supply_chain_trade_agreement(
Document dom, trade_party seller, trade_party buyer)
throws DOMException {
Element agreement = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableSupplyChainTradeAgreement");
Element seller_trade_party = xml_create_trade_party(dom,
NAMESPACE_RAM, "ram:SellerTradeParty", seller);
agreement.appendChild(seller_trade_party);
Element buyer_trade_party = xml_create_trade_party(dom,
NAMESPACE_RAM, "ram:BuyerTradeParty", buyer);
agreement.appendChild(buyer_trade_party);
return agreement;
}
/**
* Create an "ApplicableSupplyChainTradeAgreement" XML element.
*
* @param dom
* XML document
* @param seller
* Seller information
* @param buyer
* Buyer information
*
* @return The new "ApplicableSupplyChainTradeAgreement" XML element
*
* @throws DOMException
*/
private static Element xml_create_applicable_supply_chain_trade_delivery(
Document dom, Calendar now)
throws DOMException {
Element delivery = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableSupplyChainTradeDelivery");
Element actual_delivery_supply_chain_event = dom
.createElementNS(NAMESPACE_RAM, "ram:ActualDeliverySupplyChainEvent");
delivery.appendChild(actual_delivery_supply_chain_event);
append_date_time_string_element(dom, actual_delivery_supply_chain_event,
NAMESPACE_RAM, "ram:OccurrenceDateTime", now);
return delivery;
}
}