pdfa/facturx_invoice_pdfa3b
Create a PDF/A-3b Factur-X (=ZUGFeRD 2.1) invoice and attach a delivery receipt as additional explanatory document.
Download Java Code Show Output Show Input (Lieferschein.pdf)
/*
* Create a PDF/A-3b Factur-X (=ZUGFeRD 2.1) invoice and attach a
* delivery receipt as additional explanatory document
*
* The XML invoice uses the Factur-X profile "EN 16931 (COMFORT)". This must
* match the Factur-X profile that is stored in the XMP input file.
*
* 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, additional reference document as 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.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.pdflib.pdflib;
import com.pdflib.PDFlibException;
/*
* Enable these import statements for validating the generated XML against the
* Factur-X XML schema (see comment in the code for details)
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 facturx_invoice_pdfa3b {
private static final String COMMENT = "Created by PDFlib Cookbook program "
+ "facturx_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_A =
"urn:un:unece:uncefact:data:standard:QualifiedDataType:100";
final static String NAMESPACE_QDT =
"urn:un:unece:uncefact:data:standard:QualifiedDataType:10";
final static String NAMESPACE_RAM =
"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100";
final static String NAMESPACE_RSM =
"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100";
final static String NAMESPACE_UDT =
"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100";
/**
* For simplicity only Euro is used as the currency.
*/
final private static String CURRENCY = "EUR";
/**
* The German VAT percentage.
*/
final private static double VAT_PERCENT = 19;
/**
* Payment due date in days from now.
*/
final private static int DUE_DATE_DAYS = 30;
/**
* 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);
/**
* Encapsulation of information about an additional referenced document
*/
static class additional_referenced_document {
// The following are used in the XML element <ram:AdditionalReferencedDocument>:
String IssuerAssignedID;
String URIID;
String TypeCode;
// The following are used to describe the PDF attachment:
String FileName;
String MimeType;
String Description;
additional_referenced_document(
String IssuerAssignedID, String URIID, String TypeCode,
String FileName, String MimeType, String Description) {
this.IssuerAssignedID = IssuerAssignedID;
this.URIID = URIID;
this.TypeCode = TypeCode;
this.FileName = FileName;
this.MimeType = MimeType;
this.Description = Description;
}
}
/**
* Information about the additional referenced document
* Type code 916 means "Referenzpapier"/"Additional supporting document"
*/
static final additional_referenced_document refdoc =
new additional_referenced_document(
"L 123 456-78", "#ef=Lieferschein.pdf", "916",
"Lieferschein.pdf", "application/pdf", "Delivery receipt");
/**
* 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 " + DUE_DATE_DAYS + " 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 = "facturx_invoice_pdfa3b.pdf";
String title = "Factur-X Invoice";
String xmpfile = "Factur-X_extension_schema.xmp";
String xml_invoice_file = "factur-x.xml";
String invoice_number = calendar.get(Calendar.YEAR) + "-03";
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 Factur-X XML invoice.
*/
Document dom = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element root = xml_create_root(dom);
root.appendChild(dom.createComment(COMMENT));
Element document_context = xml_create_document_context(dom);
root.appendChild(document_context);
Element document_header = xml_create_exchanged_document(dom,
invoice_number, calendar);
root.appendChild(document_header);
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);
}
Element supply_chain_trade_transaction = dom.createElementNS(
NAMESPACE_RSM, "rsm:SupplyChainTradeTransaction");
root.appendChild(supply_chain_trade_transaction);
/*
* 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++;
double total = 0;
/* Data rows: one for each article */
for (int 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++;
total += data[i].price * data[i].quantity;
}
Element applicable_header_trade_agreement =
xml_create_applicable_header_trade_agreement(
dom, seller, buyer, refdoc);
supply_chain_trade_transaction
.appendChild(applicable_header_trade_agreement);
Element delivery =
xml_create_applicable_header_trade_delivery(dom, calendar);
supply_chain_trade_transaction.appendChild(delivery);
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 applicable_header_trade_settlement =
xml_create_applicable_header_trade_settlement(
dom, invoice_number,
bd_netto, bd_vat, calendar, DUE_DATE_DAYS);
supply_chain_trade_transaction
.appendChild(applicable_header_trade_settlement);
/* 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 Factur-X
* ApplicableSupplyChainTradeSettlement element.
*/
Element monetary_summation = xml_create_monetary_summation(dom,
bd_netto, bd_vat, bd_brutto);
applicable_header_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/FACTUR-X_EN16931.xsd" with the actual
* pathname to the Factur-X XML schema for validation according to
* EN 16931, 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/FACTUR-X_EN16931.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".
* Both attachments are marked as "document attachments" to
* make them visible in Acrobat's attachment pane.
*/
int xml_invoice = p.load_asset("Attachment", pvf_name,
"mimetype=text/xml " +
"description={Factur-X invoice data in XML format} " +
"relationship=Alternative documentattachment=true");
/*
* Load the explanatory document (delivery receipt) and associate
* it with the document with relationship "Supplement".
*/
int delivery_receipt = p.load_asset("Attachment", refdoc.FileName,
"mimetype=" + refdoc.MimeType + " " +
"description={" + refdoc.Description + "} " +
"relationship=Supplement documentattachment=true");
p.end_document("associatedfiles={" + xml_invoice + " " + delivery_receipt + "}");
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 a "ram: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 "ram: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");
Element line_document = dom
.createElementNS(NAMESPACE_RAM, "ram:AssociatedDocumentLineDocument");
append_text_element(dom, line_document, NAMESPACE_RAM, "ram:LineID", position);
line_item.appendChild(line_document);
Element product = dom.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTradeProduct");
append_text_element(dom, product, NAMESPACE_RAM, "ram:Name", name);
line_item.appendChild(product);
Element agreement = dom.createElementNS(NAMESPACE_RAM, "ram:SpecifiedLineTradeAgreement");
Element trade_price = dom.createElementNS(NAMESPACE_RAM, "ram:NetPriceProductTradePrice");
append_currency_element(dom, trade_price,
NAMESPACE_RAM, "ram:ChargeAmount", price);
agreement.appendChild(trade_price);
line_item.appendChild(agreement);
Element delivery = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedLineTradeDelivery");
Element billed_quantity = append_text_element(dom, delivery,
NAMESPACE_RAM, "ram:BilledQuantity",
new BigDecimal(quantity).setScale(4).toString());
/*
* Unit code C62 means "one item".
*/
billed_quantity.setAttribute("unitCode", "C62");
line_item.appendChild(delivery);
Element trade_settlement = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedLineTradeSettlement");
Element trade_tax = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableTradeTax");
append_text_element(dom, trade_tax, NAMESPACE_RAM, "ram:TypeCode", "VAT");
append_text_element(dom, trade_tax, NAMESPACE_RAM, "ram:CategoryCode", "S");
append_text_element(dom, trade_tax, NAMESPACE_RAM, "ram:RateApplicablePercent",
new BigDecimal(VAT_PERCENT).setScale(2).toString());
trade_settlement.appendChild(trade_tax);
Element monetary_summation = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTradeSettlementLineMonetarySummation");
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:LineTotalAmount", sum);
trade_settlement.appendChild(monetary_summation);
line_item.appendChild(trade_settlement);
return line_item;
}
/**
* Create a "ram:SpecifiedTradeSettlementHeaderMonetarySummation" XML element.
*
* @param dom
* XML document
* @param netto
* Netto price
* @param vat
* Calculated VAT sum
* @param brutto
* Brutto price (netto + vat)
*
* @return New "ram:SpecifiedTradeSettlementHeaderMonetarySummation" 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:SpecifiedTradeSettlementHeaderMonetarySummation");
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:LineTotalAmount", netto);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:TaxBasisTotalAmount", netto);
append_currency_element_with_id(dom, monetary_summation,
NAMESPACE_RAM, "ram:TaxTotalAmount", vat);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:GrandTotalAmount", brutto);
append_currency_element(dom, monetary_summation,
NAMESPACE_RAM, "ram:DuePayableAmount", brutto);
return monetary_summation;
}
/**
* Create "ram:ApplicableHeaderTradeSettlement" XML element.
*
* @param dom
* XML document
* @param invoice_number
* Invoice number
* @param netto
* Net sum
* @param vat
* Calculated VAT
* @param now
* Current date
* @param due_date_days
* Days until due date
*
* @return New "ram:ApplicableHeaderTradeSettlement" XML element
*/
private static Element xml_create_applicable_header_trade_settlement(
Document dom, String invoice_number,
BigDecimal netto, BigDecimal vat, Calendar now, int due_date_days) {
Element settlement = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableHeaderTradeSettlement");
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:CategoryCode", "S");
append_text_element(dom, applicable_trade_tax, NAMESPACE_RAM, "ram:RateApplicablePercent",
new BigDecimal(VAT_PERCENT).setScale(2).toString());
settlement.appendChild(applicable_trade_tax);
Element payment_terms = dom
.createElementNS(NAMESPACE_RAM, "ram:SpecifiedTradePaymentTerms");
Calendar due_date = (Calendar) now.clone();
due_date.add(Calendar.DATE, due_date_days);
append_date_time_string_element(dom,
payment_terms, NAMESPACE_RAM, "ram:DueDateDateTime", due_date);
settlement.appendChild(payment_terms);
return settlement;
}
/**
* Create "rsm:ExchangedDocumentContext" XML element.
*
* @param dom
* XML document
*
* @return New "rsm:ExchangedDocumentContext" XML element
*
* @throws DOMException
*/
private static Element xml_create_document_context(Document dom)
throws DOMException {
Element document_context = dom.createElementNS(NAMESPACE_RSM,
"rsm:ExchangedDocumentContext");
Element context_param = dom
.createElementNS(NAMESPACE_RAM,
"ram:GuidelineSpecifiedDocumentContextParameter");
document_context.appendChild(context_param);
// Identifier for EN 16931 (COMFORT) profile:
append_text_element(dom, context_param, NAMESPACE_RAM, "ram:ID",
"urn:cen.eu:en16931:2017");
return document_context;
}
/**
* Create "rsm:ExchangedDocument" XML element.
*
* @param dom
* XML document
* @param invoice_number
* Invoice number
* @param now
* Timestamp for invoice creation
*
* @return New "rsm:ExchangedDocument" XML element
*
* @throws DOMException
*/
private static Element xml_create_exchanged_document(Document dom,
String invoice_number, Calendar now)
throws DOMException {
Element header = dom.createElementNS(NAMESPACE_RSM,
"rsm:ExchangedDocument");
append_text_element(dom, header, NAMESPACE_RAM, "ram:ID", invoice_number);
/*
* Type code 380: "Handelsrechnung"/"Commercial invoice"
*/
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_with_id(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, with two decimals after the decimal separator.
*/
Element new_element = append_currency_element(dom, parent, namespace_uri,
element_name, value);
new_element.setAttribute("currencyID", CURRENCY);
return new_element;
}
/**
* Convenience function for inserting an element that is a currency value
* into a parent XML element, without "currencyID" attribute
*
* @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, with two decimals after the decimal separator.
*/
Element new_element = append_text_element(dom, parent, namespace_uri,
element_name, value.setScale(2).toPlainString());
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 Factur-X XML schema.
*/
Element root = dom.createElementNS(NAMESPACE_RSM, "rsm:CrossIndustryInvoice");
root.setAttribute("xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance");
root.setAttribute("xmlns:a", NAMESPACE_A);
root.setAttribute("xmlns:qdt", NAMESPACE_QDT);
root.setAttribute("xmlns:ram", NAMESPACE_RAM);
root.setAttribute("xmlns:rsm", NAMESPACE_RSM);
root.setAttribute("xmlns:udt", NAMESPACE_UDT);
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 AdditionalReferencedDocument element.
*
* @param dom
* XML document
* @param namespace_uri
* Namespace for the new XML element
* @param element_name
* Name for the new XML element
* @param refdoc
* Information for creating the element
*
* @return The new XML element
*/
private static Element xml_create_additional_referenced_document(Document dom,
String namespace_uri, String element_name, additional_referenced_document refdoc) {
Element referenced_document =
dom.createElementNS(namespace_uri, element_name);
append_text_element(dom, referenced_document,
NAMESPACE_RAM, "ram:IssuerAssignedID", refdoc.IssuerAssignedID);
append_text_element(dom, referenced_document,
NAMESPACE_RAM, "ram:URIID", refdoc.URIID);
append_text_element(dom, referenced_document,
NAMESPACE_RAM, "ram:TypeCode", refdoc.TypeCode);
return referenced_document;
}
/**
* Create an "ram:ApplicableHeaderTradeAgreement" XML element.
*
* @param dom
* XML document
* @param seller
* Seller information
* @param buyer
* Buyer information
*
* @return The new "ram:ApplicableHeaderTradeAgreement" XML element
*
* @throws DOMException
*/
private static Element xml_create_applicable_header_trade_agreement(
Document dom, trade_party seller, trade_party buyer,
additional_referenced_document refdoc)
throws DOMException {
Element agreement = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableHeaderTradeAgreement");
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);
Element additional_referenced_document = xml_create_additional_referenced_document(dom,
NAMESPACE_RAM, "ram:AdditionalReferencedDocument", refdoc);
agreement.appendChild(additional_referenced_document);
return agreement;
}
/**
* Create an "ram:ApplicableHeaderTradeDelivery" XML element.
*
* @param dom
* XML document
* @param seller
* Seller information
* @param buyer
* Buyer information
*
* @return The new "ram:ApplicableHeaderTradeDelivery" XML element
*
* @throws DOMException
*/
private static Element xml_create_applicable_header_trade_delivery(
Document dom, Calendar now)
throws DOMException {
Element delivery = dom
.createElementNS(NAMESPACE_RAM, "ram:ApplicableHeaderTradeDelivery");
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;
}
}