BASKET
Search
PDFlib

pdfa/zugferd_invoice

Download Java Code      Show Output PDF      Show Input File

/*

 * $Id: zugferd_invoice.java,v 1.16 2016/01/13 10:28:30 stm Exp $

 *

 * 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 9.0.1 or later

 * (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.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 zugferd_invoice {

    private static final String COMMENT = "Created by PDFlib Cookbook program "

        + "zugferd_invoice.java $Revision: 1.16 $\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 = "DejaVuSerif";


    final static String basefontoptions = "fontname=" + fontname + " fontsize="

        + fontsize + " encoding=unicode embedding";


    /*

     * 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 = "LuciduxSans-Oblique";


        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";


        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 = "zugferd_invoice.pdf";

        String title = "ZUGFeRD Rechnung";

        String xmpfile = "ZUGFeRD_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";


        /*

         * 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();


            String optlist = "SearchPath=" + searchpath;

            p.set_option(optlist);


            /*

             * 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", "zugferd_invoice.java");

            p.set_info("Title", title + " $Revision: 1.16 $");


            /* Use sRGB output intent so that we can use RGB color */

            p.load_iccprofile("sRGB", "usage=outputintent");


            /*

             * 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,

                BigDecimal.ROUND_HALF_UP);

            BigDecimal bd_vat_pct = new BigDecimal(VAT_PERCENT / 100);

            BigDecimal bd_vat = bd_netto.multiply(bd_vat_pct).setScale(2,

                BigDecimal.ROUND_HALF_UP);

            BigDecimal bd_brutto = bd_netto.add(bd_vat).setScale(2,

                BigDecimal.ROUND_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, BigDecimal.ROUND_HALF_UP),

                    new BigDecimal(sum).setScale(2, BigDecimal.ROUND_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.print("PDFlib exception occurred:\n");

            System.err.print("[" + e.get_errnum() + "] " + e.get_apiname()

                + ": " + e.get_errmsg() + "\n");

            exitcode = 1;

        }

        catch (Exception e) {

            System.err.println(e.getMessage());

            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", quantity);

        /*

         * 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));

        append_currency_element(dom, monetary_summation,

            NAMESPACE_RAM, "ram:AllowanceTotalAmount", new BigDecimal(0));

        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",

            Double.toString(VAT_PERCENT));

        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;

    }

}