Generate Multiple PDF Receipts from a Single Submission

   Journey Manager (JM) The transaction engine for the platform.  |    System Manager / DevOps  |  All versions This feature is related to all versions.

Sometimes it is necessary to generate multiple different PDF receipts from a single submission. There are several use cases for this, for example:

  • You may want a different PDF receipt depending on some data in the form. For example, a slightly different variation of the receipt depending on which state the applicant is from.
  • You may want to generate multiple separate documents, and concatenate them together. For example, a single application form may result in both a credit card and a check account PDF to be generated. You may want to concatenate these two documents together, or perhaps just provide the two separate documents to downstream business processes.

For this particular example, we are going to generate a certificate for a course attendee. When the user fills in the form, they will be able to choose between different PDF receipts, and green receipt, and a receipt with a picture of flowers on it.

In this case, we are going to generate “pixel perfect” or AcroForm receipts. Please refer to this article for more information about AcroForm Receipts: How to import an Acrobat PDF form as a Transact receipt template (AcroForms).

The code is slightly different if you want to generate other types of receipts, but the principles are similar.

Each of the Acroforms has a single Acroform field placed on it – this field is named “FullName”. For consistency, both forms have the identical field name – but in a real scenario there can be a combination of common fields and form-specific fields. You just need to make sure that the data entry form provides all the necessary fields.

The data entry form has two fields, a field for the person’s name, and a dropdown containing the values “Green” and “Flowers”.

Each of these fields has a data extract name. We will use the data extract values for binding to the Acroform, as well as for the logic in our script.

The techniques for creating and generating an AcroForm receipt is described in the AcroForm article. But for the multiple receipt example, we have to solve two additional problems.

  • We need somewhere to store the two (or more) PDF receipt template files. By default, Transact Manager only stores one receipt template per form.
  • We need to create a custom receipt rendering service. By default, the various PDF rendering receipt services that Transact Manager provides only generate a single receipt.

Storing multiple PDF receipts

In order to store multiple receipts, we will create a “dummy” form in Transact Manager for each receipt. This form is not intended for data entry, and it will never be visible externally. It’s simply used as a place to store the receipt.

There are two ways to do this. Either:

  • Deploy your data entry form twice, under two different form codes. Or
  • Deploy your data entry form once. Then go into Transact Manager, and manually create a new form. You will need to upload a data entry form when you create this form, because Transact will not allow a form to be created without a data entry template. However, this can be any form, since the data entry form will never be visible.

Once you have defined your data entry form plus your additional “dummy” forms, you can upload the AcroForm PDF receipts to each of these form codes as shown below:

Custom Rendering Service

The Groovy code is shown below. This is an example of how you might write this script, and of course your needs may vary. The code is hopefully reasonably self-evident for Groovy programmers. You may want to refer to http://itextpdf.com/ for more details about the iText PDF library which does the work of merging the field data into the AcroForm.

/* Render Receipt Groovy script.
   Script parameters include:
       form : com.avoka.fc.core.entity.Form
       request : javax.servlet.http.HttpServletRequest
       submission : com.avoka.fc.core.entity.Submission
       serviceDefinition : com.avoka.fc.core.entity.ServiceDefinition
       serviceParameters : Map<String, String>
   Script return:
       a PDF receipt document of type com.avoka.fc.form.service.DataDocument
*/
import com.avoka.core.groovy.GroovyLogger as logger
import com.avoka.fc.core.dao.DaoFactory
import com.avoka.fc.form.service.DataDocument
import com.itextpdf.text.pdf.AcroFields
import com.itextpdf.text.pdf.AcroFields.Item
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfStamper
import org.apache.commons.lang.Validate
logger.info "Start render"
 
final DATA_EXTRACT_CERTIFICATE_TYPE = "Certificate Type"
final FLOWER_FORM = "certificate-flowers"
final GREEN_FORM = "certificate-green"
 
// Get the Data Extract
Map<String, String> dataExtractMap = submission.dataExtractMap
logger.info "dataExtractMap=" + dataExtractMap
 
// Using the Certificate Type field data to determine which form code to use
def certificateType = dataExtractMap.get(DATA_EXTRACT_CERTIFICATE_TYPE)
def formCode = ""
if (certificateType == "Green") {
  formCode = GREEN_FORM
} else {
  formCode = FLOWER_FORM
}
logger.info "formCode=" + formCode
 
// Get the appropriate form object
def form = DaoFactory.getFormDao().getFormByFormCode(formCode.toLowerCase())
 
// Call the function to render the AcroForm and return the result
def receiptDocument = renderAcroForm(form, dataExtractMap)
logger.info "End render"
return receiptDocument
 
// This function uses iText to inject the fields from the data extract map into the AcroForm receipt
def renderAcroForm(form, dataExtractMap) {
    final CONTENT_TYPE_PDF = "application/pdf"
    Validate.notNull(form, "form parameter is null")
    Validate.notEmpty(dataExtractMap, "dataExtractMap parameter is null")
 
    def templateVersion = form.getCurrentVersion()
    Validate.notNull(templateVersion, "form.currentVersion parameter is null")
 
    byte [] templateData = templateVersion.getTemplateVersionData().getReceiptFileData()
    Validate.notNull(templateData, "form.currentVersion.templateVersionDate.receiptFileData parameter is null")
 
    // Create the output stream
    ByteArrayOutputStream stream = new ByteArrayOutputStream(256 * 1024)
 
    // Setup the PDF receipt template
    PdfReader templatePdfReader = new PdfReader(templateData)
    PdfStamper pdfStamper = new PdfStamper(templatePdfReader, stream)
 
    // Bind the AcroForm field values
    AcroFields acroFields = pdfStamper.getAcroFields()
    Map<String, Item> fieldMap = acroFields.getFields()
    for (String fieldName : fieldMap.keySet()) {
        if (dataExtractMap.containsKey(fieldName)) {
            acroFields.setField(fieldName, dataExtractMap.get(fieldName))
        }
    }
 
    // "Flatten" the AcroForm with the bound field data - the fields will no longer be editable
    pdfStamper.setFormFlattening(true)
    pdfStamper.close()
    return new DataDocument(stream.toByteArray(), CONTENT_TYPE_PDF)

Next, learn how to configure receipts server node.