Factur-X demo
Genero Report Writer allows you to produce electronic invoice documents conforming to the Factur-X standard.
A Factur-X invoice is a PDF document that contains an embedded XML document. This XML document contains the visual invoice data in a structured way suitable for electronic processing.
The demo FacturX has been added to the OrderReport demonstration project and demonstrates a project producing electronic invoice documents conforming to the Factur-X standard. This Factur-X demo uses the EN 16931 profile.
Semantic model API
- Minimum
- Basic Without Lines
- Basic
- EN 16931 aka. XRECHNUNG
- Extended
- The produced XML always matches the profile specific XML schema provided with the specification (.xsd). It may however fail to pass the schematron validation for reasons such as the rule BR-CO-25.
- Computed fields are automatically computed according to the specification rules. The computed fields are accessible via the API and can be printed.
- All identifiers in the API correspond to the business terms used in the specification and not to the naming of the elements in the schema (.xsd).
- The API is typed. All types in the API correspond to one of the 10 base types listed in the specification. In the case of the type Code, a list (enum) is provided for each type so invalid values are not specified.
- The API is fully documented. The documentation of any class or method contains the business group number, the business term number, the description from the specification and the XPath to its location in the XML file.
- The semantic models allow lower profiles to have less methods and 'depth' compared to the
schemas. The 'depth' of the API is oriented on the semantic model, which can have up to 3 levels,
not on the schema which has up to 7 levels. The ratio per profile is:
- Minimal and Basic Without Lines: 5/2
- Basic and EN 16931: 5/3
- Extended:7/3
- Besides omitting methods that are not available in a particular profile, methods for properties that can occur only once in one profile and multiple times in another are different in the simple profile compared to the more complex one. For example, the method setVATCurrency() is a method of the Invoice class in the Minimal profile while it is a method of the InvoiceTotalVATAmount class in all other profiles where an Invoice can have multiple currencies.
fgl_report_setPDFFacturXAttachment
The API call fgl_report_setPDFFacturXAttachment()
has been added to embed the
XML document in the PDF. For more information on the
fgl_report_setPDFFacturXAttachment()
API call, go to fgl_report_setPDFFacturXAttachment().
Migration from other software
Previously, when attempting to migrate source code which uses another API, it could be difficult to find which method in the previous API maps to which method in the Genero semantic API.
The new semantic model API contains a call and a command line option to create source code from a Factur-X XML file created by the previous software. When run, the source code produces the same Factur-X XML file.
$java -jar $(PATH_TO_JAR)/gre.jar com.fourjs.report.facturx.profiles.en16931.Invoice
-xmlInput files-from-other-tool/factur-x.xml
-packageName myproject.unittests.facturx
-className MigationTest -referenceFilesDirectory files-from-other-tool
-JUnitJavaOutput
Factur-X validation
One tool endorsed by the Genero Report Writer development team used for validation is the Mustang project. It provides a simple command line tool for the validation of Factur-X PDF or XML files.
Extracting a FacturX XML attachment from a PDF and navigating its semantic model
The previously mentioned command line utility from the Mustang project can be used to extract a Factur-X XML document from a PDF as follows:
$ls incoming-invoices
Invoice4711from-supplier-xyz.pdf
$ java -jar $(PATH_TO_JAR)/Mustang-CLI-2.4.1.jar --action extract --source incoming-invoices/ Invoice4711from-supplier-xyz.pdf --out factur-x.xml
$ls factur-x.xml
factur-x.xml
Invoice.fromXML(
String filename,
boolean recomputeComputedFields)
Demo example
IMPORT util
IMPORT FGL greruntime
IMPORT JAVA java.time.LocalDate
IMPORT JAVA java.time.format.DateTimeFormatter
IMPORT JAVA java.util.Collection
IMPORT JAVA java.util.Iterator
IMPORT JAVA com.fourjs.report.facturx.Codes
IMPORT JAVA com.fourjs.report.facturx.Percentage
IMPORT JAVA com.fourjs.report.facturx.UnitPriceAmount
IMPORT JAVA com.fourjs.report.facturx.Quantity
IMPORT JAVA com.fourjs.report.facturx.Amount
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.Invoice
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.Identifier
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.InvoiceLine
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.InvoiceTotalVATAmount
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.VATBreakdown
IMPORT JAVA com.fourjs.report.facturx.profiles.en16931.SellerVATIdentifier
SCHEMA officestore
TYPE OrderType RECORD
orders RECORD LIKE orders.*,
account RECORD LIKE account.*,
country RECORD LIKE country.*,
lineitem RECORD LIKE lineitem.*,
product RECORD LIKE product.*,
category RECORD LIKE category.*,
item RECORD LIKE item.*
END RECORD
CONSTANT
companyname = "FourJ's",
companypostcode = "67800",
companycity = "Schiltigheim",
companyadressline1 = "123 avenue V. Hugo",
companycountry = "FR",
companyvatnumber = "FR38440422236",
paymentterms = "Payment seven days after invoice date"
MAIN
DEFINE
handler om.SaxDocumentHandler
-- load the 4rp file
IF NOT fgl_report_loadCurrentSettings("Invoice.4rp") THEN
EXIT PROGRAM
END IF
-- change some parameters
CALL fgl_report_selectDevice("PDF")
-- use the report
LET handler = fgl_report_commitCurrentSettings()
DISPLAY "Running report from database"
CALL runReportFromDatabase(5, handler)
END MAIN
FUNCTION runReportFromDatabase(orderid, handler)
DEFINE
orderline OrderType,
handler om.SaxDocumentHandler,
orderid INTEGER
DATABASE officestore
DECLARE c_order CURSOR FOR
SELECT orders.*,
account.*,
country.*,
lineitem.*,
product.*,
category.*,
item.*
FROM orders, account, lineitem, product, category, item, country
WHERE orders.orderid = orderid
AND orders.orderid = lineitem.orderid
AND orders.userid = account.userid
AND lineitem.itemid = item.itemid
AND item.productid = product.productid
AND product.catid = category.catid
AND country.code = orders.billcountry
ORDER BY lineitem.linenum
START REPORT invoice TO XML HANDLER handler
FOREACH c_order INTO orderline.*
OUTPUT TO REPORT invoice(orderline.*,handler)
IF fgl_report_getErrorStatus() THEN
DISPLAY "FGL: STOPPING REPORT, msg=\"",
fgl_report_getErrorString(),
"\""
EXIT FOREACH
END IF
END FOREACH
FINISH REPORT invoice
DISPLAY "Produced " || fgl_report_getTotalNumberOfPages() || " page(s)"
CLOSE c_order
END FUNCTION
FUNCTION getVATRate(category) RETURNS FLOAT
DEFINE
category STRING
-- for the demo we set arbitrary VAT rate
CASE category
WHEN "FURNITURE"
RETURN 10
WHEN "ART"
RETURN 5.5
WHEN "TRAVELLING"
RETURN 2.1
OTHERWISE
RETURN 20
END CASE
END FUNCTION
REPORT invoice(orderline,handler)
DEFINE
orderline OrderType,
handler om.SaxDocumentHandler,
dateString STRING,
lineamount DECIMAL(10,2),
totalamountwithoutVAT DECIMAL(10,2),
totalamountwithVAT DECIMAL(10,2),
invoiceInstance Invoice,
lineInstance InvoiceLine,
VATBreakDownList Collection,
VATBase DECIMAL(10,2),
VATRate DECIMAL(4,2),
VATDetail DECIMAL(10,2),
VATBreakdownInstance VATBreakdown,
sellerVATIdentifierInstance SellerVATIdentifier,
it Iterator
FORMAT
FIRST PAGE HEADER
LET dateString = orderline.orders.orderdate USING "yyyy-mm-dd"
LET invoiceInstance =
Invoice.create(Identifier.valueOf(util.JSON.stringify(orderline.orders.orderid)),
Codes.InvoiceTypeCode.No380, --Business Invoice
LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE),
Codes.Currency.EUR)
CALL invoiceInstance.setSellerName(companyname)
.setSellerPostCode(companypostcode)
.setSellerAddressLine1(companyadressline1)
.setSellerCity(companycity)
.setSellerCountryCode(Codes.CountryCodeList.fromString(companycountry))
.setPaymentTerms(paymentterms) -- this or the payment due date are needed if amount due for payment is positive
.setBuyerName(trim(orderline.account.firstname)||" "||trim(orderline.account.lastname))
.setBuyerPostCode(trim(orderline.account.zip))
.setBuyerAddressLine1(trim(orderline.account.addr1))
.setBuyerAddressLine2(trim(orderline.account.addr2))
.setBuyerCity(trim(orderline.account.city))
.setBuyerCountryCode(Codes.CountryCodeList.fromString(orderline.account.country))
LET sellerVATIdentifierInstance = invoiceInstance.appendNewSellerVATIdentifier() -- this is needed if any standard VAT is computed in the invoice
CALL sellerVATIdentifierInstance.setSellerVATIdentifierSchemeIdentifier(Codes.SchemeIdentifierAttribute.VA)
.setSellerVATIdentifier(Identifier.fromString(companyvatnumber))
-- French standard VAT
LET VATBreakdownInstance = invoiceInstance.appendNewVATBreakdown(Codes.VATCategoryCode.S)
CALL VATBreakdownInstance.setVATCategoryRate(Percentage.valueOf(20)) --it is mandatory for computation
-- French reduced VAT
LET VATBreakdownInstance = invoiceInstance.appendNewVATBreakdown(Codes.VATCategoryCode.S)
CALL VATBreakdownInstance.setVATCategoryRate(Percentage.valueOf(10)) --it is mandatory for computation
LET VATBreakdownInstance = invoiceInstance.appendNewVATBreakdown(Codes.VATCategoryCode.S)
CALL VATBreakdownInstance.setVATCategoryRate(Percentage.valueOf(5.5)) --it is mandatory for computation
LET VATBreakdownInstance = invoiceInstance.appendNewVATBreakdown(Codes.VATCategoryCode.S)
CALL VATBreakdownInstance.setVATCategoryRate(Percentage.valueOf(2.1)) --it is mandatory for computation
PRINT companyname,
companyadressline1,
companypostcode,
companycity,
companycountry,
paymentterms
ON EVERY ROW
LET lineInstance = invoiceInstance.appendNewInvoiceLine(
Identifier.fromString(util.JSON.stringify(orderline.lineitem.linenum)),
trim(orderline.product.proddesc),
Codes.UnitOfMeasure.C62, -- Unit is per Item
Quantity.valueOf(orderline.lineitem.quantity),
Codes.VATCategoryCode.S) -- standard VAT
.setInvoicedItemVATRate(Percentage.valueOf(getVATRate(orderline.category.catid)))
.setItemGrossPrice(UnitPriceAmount.valueOf(orderline.lineitem.unitprice))
DISPLAY " EVERY ROW " || orderline.lineitem.linenum
LET lineamount = " "||lineInstance.getInvoiceLineNetAmount().getDisplayValue() -- compute the line amount
LET totalamountwithoutVAT = " "||invoiceInstance.getInvoiceTotalAmountWithoutVAT().getDisplayValue() -- compute the running totals
LET totalamountwithVAT = " "||invoiceInstance.getInvoiceTotalAmountWithVAT().getDisplayValue()
PRINT orderline.*, lineamount, totalamountwithoutVAT, totalamountwithVAT
ON LAST ROW
LET VATBreakDownList = invoiceInstance.getVATBreakdowns()
LET it = VATBreakDownList.iterator()
WHILE it.hasNext()
LET VATBreakdownInstance = CAST(it.next() AS VATBreakdown)
LET VATBase = " "||VATBreakdownInstance.getVATCategoryTaxableAmount().getDisplayValue()
LET VATRate = " "||VATBreakdownInstance.getVATCategoryRate().getDisplayValue()
LET VATDetail = " "||VATBreakdownInstance.getVATCategoryTaxAmount().getDisplayValue()
PRINT VATBase,VATRate,VATDetail
END WHILE
CALL fgl_report_setPDFFacturXAttachment(handler,invoiceInstance.getXMLAsDataURL()) -- embed in PDF
END REPORT
FUNCTION trim(s) RETURNS STRING
DEFINE s STRING
RETURN s CLIPPED
END FUNCTION