Published at: 2025-10-30

Custom Data Printing with HTML Templates and APL Integration


Notes

  1. This feature requires beta access: [Print templates now support APL functions]
  2. This documentation applies to PDF templates only

picture coming soon:

picture coming soon:

1. Use Case Examples

For order or Contract records, when printing line item details, you may need to display additional Product Attributes such as product category or specifications. For multi-variant Products, some attributes may require lookup through the SPU (Standard Product Unit). APL’s query capabilities enable outputting these multi-level related data fields.

Example scenario: Printing energy type (e.g., “Plug-in Hybrid”) for Contract line items by tracing:
Contract line item → SKU (Stock Keeping Unit, e.g., “Destroyer 05 2024 DM-i Glory Edition 1.5L 120km”) → SPU (Standard Product Unit, e.g., “Destroyer 05”).

1.1.1 APL Code

```java /** * @author admin01 * @codeName PrintContractLineItems * @description Prints contract product line items * @createTime 09/04/2024 */

List details = context.details.SaleContractLineObj as List List skuIds = []

details.each { entry -> Map detail = entry as Map String skuId = detail.get(“product_id”) skuIds.add(skuId) }

def fqa = FQLAttribute.builder().columns([“_id”, “name”, “price” , “is_giveaway”, “picture_path”, “spu_id”]).build() def sa = SelectAttribute.builder().build()

List prodctList = Fx.object.findByIds(“ProductObj”, skuIds, fqa, sa).result() as List Map skuId2SpuIdMap = prodctList.collectEntries { [(((Map) it)._id): ((Map) it).spu_id] } Map skuId2Map = prodctList.collectEntries { [(((Map) it)._id): ((Map) it)] } log.info(skuId2Map)

def spuIds = skuId2SpuIdMap.values() as List

def fqa2 = FQLAttribute.builder().columns([“_id”, “name”, “is_spec”]).build() def sa2 = SelectAttribute.builder().build()

List spuList = Fx.object.findByIds(“SPUObj”, spuIds, fqa2, sa2).result() as List Map spuMap = spuList.collectEntries { [(((Map) it)._id): ((Map) it)] } log.info(spuMap)

details.each { entry -> Map detail = entry as Map String skuId = detail.get(“product_id”)

Map skuData = skuId2Map.get(skuId) String isGiveaway = skuData.get(“is_giveaway”) String isGiveawayLabel = “” if (“1” == isGiveaway) { isGiveawayLabel = “Yes” } else if (“0” == isGiveaway) { isGiveawayLabel = “No” } else { isGiveawayLabel = “Unknown” } skuData.put(“isGiveawayLabel”, isGiveawayLabel) detail.put(“SKUObj”, skuData)

String spuId = skuId2SpuIdMap.get(skuId) as String Map spu = spuMap.get(spuId) as Map boolean isSpec = spu.get(“is_spec”) as Boolean Map spuData = [:] if(isSpec){ spuData.put(“is_spec”, “Multi-spec”) } else { spuData.put(“is_spec”, “Single-spec”) } detail.put(“SPUObj”, spuData) }

Map map = [:] map.put(“MySaleContractLineObj”, details)

return map ```

1.1.2 Template Source Code

```html

Contract Line Item ID Multi-spec Product Name Is Gift Sales Price
${item.name} ${item.SPUObj.is_spec} ${item.SKUObj.name} ${item.SKUObj.isGiveawayLabel} ${item.SKUObj.price}
Table rows will auto-populate based on actual data volume


```

1.2 Printing Irregular Approval Data Tables

Current configurable style VS desired implementation style

image

1.2.1 Prerequisites

1.2.1.1 HTML Template Engine Syntax

HTML template engine syntax documentation

1.2.1.2 HTML Learning Resources

Creating irregular tables requires cell merging operations. Familiarity with HTML table tags and cell merging techniques is essential:

HTML Table Colspan and Rowspan
The Table Data Cell element

picture coming soon:

picture coming soon:

1.2.2 Case Explanation

For approval data display:
1. Add an instanceId parameter (String type) in APL to receive the process instance ID from the print template
2. APL provides a Map structure where:
- List data displays in tables
- Each record (objectData) represents a table row
- Each field value represents a cell
3. Query approval instances and calculate cell colspan/rowspan through grouping
4. Merged cells should have rowspan or colspan set to 0

picture coming soon:

1.3 APL-Based Cell Span Calculation

Example case requiring column merging for identical task names

1.3.1 Data JSON Format

json { "instanceList": [ { "task_name": "Single Approval", "reply_user": "lucy", "action_type": "Approved", "opinion": "ok", "span": { "task_name": { "colspan": 1, "rowspan": 2 } } }, { "task_name": "Single Approval", "reply_user": "tom", "action_type": "Approved", "opinion": "Yes", "span": { "task_name": { "colspan": 1, "rowspan": 0 } } }, { "task_name": "Consensus Approval", "reply_user": "scott", "action_type": "Approved", "opinion": "", "span": { "task_name": { "colspan": 1, "rowspan": 1 } } } ] }

Implementation steps:
1. Group by task name

json { "Single Approval": [ { "task_name": "Single Approval", "reply_user": "lucy", "action_type": "Approved", "opinion": "ok" }, { "task_name": "Single Approval", "reply_user": "tom", "action_type": "Approved", "opinion": "Yes" } ], "Consensus Approval": [ { "task_name": "Consensus Approval", "reply_user": "scott", "action_type": "Approved", "opinion": "done" } ] }

  1. Set rowspan for task_name field (group size for first record, 0 for others)

json { "Single Approval": [ { "task_name": "Single Approval", "reply_user": "lucy", "action_type": "Approved", "opinion": "ok", "span": { "task_name": { "rowspan": 2 } } }, { "task_name": "Single Approval", "reply_user": "tom", "action_type": "Approved", "opinion": "Yes", "span": { "task_name": { "rowspan": 0 } } } ], "Consensus Approval": [ { "task_name": "Consensus Approval", "reply_user": "scott", "action_type": "done", "opinion": "", "span": { "task_name": { "rowspan": 1 } } } ] }

  1. Restore complete list under “instanceList”

json { "instanceList": [ { "task_name": "Single Approval", "reply_user": "lucy", "action_type": "Approved", "opinion": "ok", "span": { "task_name": { "rowspan": 2 } } }, { "task_name": "Single Approval", "reply_user": "tom", "action_type": "Approved", "opinion": "Yes", "span": { "task_name": { "rowspan": 0 } } }, { "task_name": "Consensus Approval", "reply_user": "scott", "action_type": "Approved", "opinion": "done", "span": { "task_name": { "rowspan": 1 } } } ] }

1.3.2 APL Code

```java /** * @author admin01 * @codeName PrintApproval * @description Parameter passing example * @createTime 07/08/2024 */ Map map = [:]

def (Boolean err, List instanceList, String errMsg) = Fx.approval.findTasks(instanceId)

List printList = [] instanceList.each { inst -> List opinions = ((Map) inst).opinions as List opinions.eachWithIndex { opin, i -> Map newOpin = [:] // Approval node name newOpin.task_name = ((Map) inst).task_name // Approver String replyUser = ((List) ((Map) opin).reply_user)[0] def (Boolean err1, Map userInfo, String errMsg1) = Fx.org.findUserById(replyUser) if(!err1){ newOpin.reply_user = userInfo.full_name } else { log.info(errMsg1) } // Approval result String type = ((Map) opin).action_type if(type == ‘agree’){ newOpin.action_type = ‘Approved’ } else if(type == ‘reject’){ newOpin.action_type = ‘Rejected’ } else if(type == ‘cancel’){ newOpin.action_type = ‘Canceled’ } // Comments newOpin.opinion = ((Map) opin).opinion // Initialize colspan/rowspan for task_name merging Map spanTaskName = [task_name: [colspan: 1, rowspan: 1]] newOpin.span = spanTaskName printList.add(newOpin) } }

// Group relational data to calculate task_name row merging Map groupedByTaskName = printList.groupBy { ((Map) it).task_name }.collectEntries { [(it.key): (it.value)] } groupedByTaskName.each { entry -> def taskList = entry.value as List<Map> if (taskList.size() <= 1) return

taskList.eachWithIndex { task, i ->
	Map span = ((Map) task).span as Map
	Map taskNameSpan = span.task_name as Map 
	if (i == 0) {
		taskNameSpan.rowspan = taskList.size()
	} else {
		taskNameSpan.rowspan = 0
	}
} }

map.put(‘instanceList’, printList)

return map ```

1.3.3 Template Source Code

Configure print templates with conditional statements:
- Hide td elements where rowspan = 0
- Display and set rowspan attribute for others

picture coming soon:

```html


Task Node Approver Result Comments
${entry.task_name} ${entry.reply_user} ${entry.action_type} ${entry.opinion}


Task Node Approver Result Comments
${entry.task_name} ${entry.reply_user} ${entry.action_type} ${entry.opinion}


```

2. Summary

Through these examples, print templates + APL can theoretically display:
- Data directly/indirectly related to primary objects
- Sub-object records grouped by Record Type or other option fields
- Aggregated field values (sums, averages) with flexible record filtering
- Multi-level lookup fields (Custom Object A → B → … → Z)

3. Practical Exercises

3.1 Implementing Transposed Data Tables

Example: Convert from this orientation (regions as columns, quarters as rows):

picture coming soon:

To this orientation (quarters as columns, regions as rows):

picture coming soon:

3.2 Column Headers

json [ "Sales by Region", "Europe", "Asia", "North America" ]

3.3 Data

```json [

Submit Feedback