Payslip Template Language

The payslip template is stored in a custom record as HTML , the HTML is written with special tags identified by ${something} that are replaced prior to generating the PDF. Trimpath can run both client side and server side, and was selected as it supported iterative loops and has a small footprint.

Full details on the template markup is available at http://code.google.com/p/trimpath/wiki/JavaScriptTemplateSyntax.


// basic value replacement
Date of payment: ${paydate}

// basic value replacement with modifier |h to encode data
Name of employee: ${employee.firstname|h}

// example if loop
{if employee.paytype == "Salary"}
This is a salaried employee
{/if}

// example iterative loop over an array called detail.lines
{for row in detail.lines}
<tr>
		<td>${row.description|h}</td>
		<td align="right">{if row.quantity != ""}${row.quantity}{/if}</td>
		<td align="right">{if row.quantity != ""}${row.rate}{/if}</td>
		<td align="right">${row.amount}</td>
		<td align="right">{if ytdgrp[row.key].displayindex == row_index}${ytdgrp[row.key].ytd}{else}{/if}</td>
</tr>
{/for}


Payslip Data

Payslip Data is made available into the template through a custom object, generated through a combination of saved search calls across employee, payslip and payslip details. basic structure being the root element represents the payslip which contains objects for:

  • employer
  • employee
  • leave
  • detail (list of payslip details) - Deprecated No Longer Exposed Post release 20.2.00
  • detailgrp (list of payslip details grouped for display purposes)
  • ytdgrp (summary of ytd amounts by group)
Data Object
var data={
	        "id": paySlipId,
            "logopdf": nlapiEscapeXML(logoPdfUrlStr),
            "logohtml": logoHtmlUrlStr,
            "payslipref": sRow.getValue("name"),
			"message": sRow.getValue("custrecord_pr_ps_message"),
            "payfrequency": sRow.getValue("custrecord_ps_pay_freq"),
            "paydate": sRow.getValue("custrecord_pr_ps_pay_date"),
            "fromdate": sRow.getValue("custrecord_pr_ps_from"),
            "todate": sRow.getValue("custrecord_ps_pay_period_end"),
            "status": sRow.getText("custrecord_ps_status"),
            "taxyearid": sRow.getValue("custrecord_ps_year"),
            "taxyear": sRow.getText("custrecord_ps_year"),
            "employer": {
                "name": preferenceArr['cmp_name'],
                "abn": formatABN(preferenceArr['cmp_abn']),
                "supercontribution": 0.00,
                "supercontributionytd": 0.00
            },
            "basesalary": nlapiFormatCurrency(sRow.getValue("custrecord_ps_base_salary")),
            "hourlyrate": nlapiFormatCurrency(sRow.getValue("custrecord_pr_ps_hourly_rate")),
            "nontaxableallowance": nlapiFormatCurrency(sRow.getValue("custrecord_pr_ps_nontaxable_allowance")),
            "taxableincome": nlapiFormatCurrency(sRow.getValue("custrecord_ps_taxable_income")),
            "taxdeductions": nlapiFormatCurrency(sRow.getValue("custrecord_ps_tax_deduct")),
            "taxdeductionsytd": 0.00,
            "taxableallowance": nlapiFormatCurrency(sRow.getValue("custrecord_pr_ps_taxable_allowance")),
            "expenses": nlapiFormatCurrency(sRow.getValue("custrecord_ps_expenses")),
            "netpay": nlapiFormatCurrency(sRow.getValue("custrecord_ps_net_pay")),
            "netpayytd": 0.00,
            "grosswage": nlapiFormatCurrency(sRow.getValue("custrecord_ps_gross_wage")),
            "grosswageytd": 0.00,
            "totaldeductions": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_ps_tax_deduct")) + setFloat(sRow.getValue("custrecord_pr_other_deductions")) + setFloat(sRow.getValue("custrecord_pr_ps_salary_sacrifice"))),
            "totaldeductionsytd": 0.00,
            "superobligation": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_pr_ps_super_obligation"))),
            "baseforsuper": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_pr_ps_base_super"))),
            "salarysacrifice": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_pr_ps_salary_sacrifice"))),
            "salarysacrificesuper": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_pr_ps_ss_super"))),
            "baseforsalarysacrifice": nlapiFormatCurrency(setFloat(sRow.getValue("custrecord_pr_ps_base_pay_period"))),
            "employee": {
                "isinactive" : sRow.getValue("isinactive","custrecord_ps_employee") == "T",
                "id": sRow.getValue("custrecord_ps_employee"),
                "name": sRow.getValue("firstname", "custrecord_ps_employee") + " " + sRow.getValue("lastname", "custrecord_ps_employee"),
                "email": sRow.getValue("email", "custrecord_ps_employee"),
                "employmentstatus": sRow.getText("custrecord_ps_employment_status"),
                "residentstatus": sRow.getText("custrecord_ps_resident_status"),
                "paytype": sRow.getText("custrecord_ps_pay_type"),
                "superfundid": sRow.getValue("custrecord_ps_super_fund"),
                "superfund": sRow.getText("custrecord_ps_super_fund"),
                "superfundvendorid": sRow.getValue("custrecord_ps_super_fund_vendor"),
                "superfundvendor" : sRow.getText("custrecord_ps_super_fund_vendor"),
                "jobtitleid": sRow.getValue("custentity_pr_position_title", "custrecord_ps_employee"),
                "jobtitle": sRow.getText("custentity_pr_position_title", "custrecord_ps_employee"),
                "awardid": sRow.getValue("custentity_pr_award_name", "custrecord_ps_employee"),
                "hoursperday": sRow.getValue("custentity_pr_hours_per_day", "custrecord_ps_employee"),
                "award": sRow.getText("custentity_pr_award_name", "custrecord_ps_employee"),
                "annualleavehours": sRow.getValue("custentity_pr_annual_leave_accrued", "custrecord_ps_employee"),
                "hourlyrate": nlapiFormatCurrency(sRow.getValue("custentity_pr_hourly_rate", "custrecord_ps_employee")),
                "paymethod" : sRow.getText("custentity_pr_payslip_method", "custrecord_ps_employee"),
                "taxcode" : sRow.getText("custentity_employee_tax_scale", "custrecord_ps_employee")
            },
            "leave": {
                "annual": {
                    "entitlement": sRow.getValue("custentity_pr_al_entitlement_days", "custrecord_ps_employee"),
                    "accrued": setFloat(sRow.getValue("custentity_pr_annual_leave_accrued", "custrecord_ps_employee"), 2),
                    "available" : setFloat(sRow.getValue("custentity_pr_annual_leave_available", "custrecord_ps_employee"), 2),
                    "hourlyrate" : 0.00,
                    "periodtakenhour" : 0.00,
                    "periodaccruedhour" : 0.00                    
                },
                "personal": {
                    "entitlement": sRow.getValue("custentity_pr_pl_entitlement_days", "custrecord_ps_employee"),
                    "accrued": setFloat(sRow.getValue("custentity_pr_personal_leave_accrued", "custrecord_ps_employee"), 2),
                    "available" : setFloat(sRow.getValue("custentity_pr_personal_leave_available", "custrecord_ps_employee"), 2),
                    "hourlyrate" : 0.00,
                    "periodtakenhour" : 0.00,
                    "periodaccruedhour" : 0.00                    
                },
                "longservice": {
                    "entitlement": sRow.getValue("custentity_pr_lsl_entitlement_days", "custrecord_ps_employee"),
                    "accrued": setFloat(sRow.getValue("custentity_pr_lsl_accrued", "custrecord_ps_employee"), 2),
                    "available" : setFloat(sRow.getValue("custentity_pr_lsl_available", "custrecord_ps_employee"), 2),
                    "hourlyrate" : 0.00,
                    "periodtakenhour" : 0.00,
                    "periodaccruedhour" : 0.00                    
                },
                "inlieu": {
                    "accrued": sRow.getValue("custentity_pr_time_in_lieu_accrued", "custrecord_ps_employee")
                }
            },
            "detailgrp": {},
            "ytdgrp": {},
            "ytdpc" : pcytdArr
        };

As an example you could display the abn number of the employee by adding ${employer.abn} to the payslip template. Only the data exposed above can be used on a payslip, if you reference a value that doesn't exist you will see an error or undefined.

Adding Custom Fields From Employee Record

Payslips are generated from data collated from a saved search of employee, payslip and payslip detail data. Additional Employee fields (standard and custom) can be added to the search by specifying a comma delimited list of field ids. To do this open the payroll configuration record and locate the print options field group. In the screenshot below the field id "custentity_pr_alt_leave_balance" has been set into the Payslip Custom Employee Field. If you wish to add multiple ensure you delimit the fields by comma without spaces.



Once the additional employee fields have been added to the configuration the data will be exposed to the payslip template in the next print process. The additional fields are exposed by name in the employee.customvalue or employee.customtext objects.


Custom Field Example
// custom field called custentity_jobrole of type list/record set to internal id 4 and text "Professional Services"

${employee.customvalue.custentity_jobrole} will display 4
${employee.customtext.custentity_jobrole} will display Professional services


// display the additional leave balance
${employee.customvalue.custentity_pr_alt_leave_balance}


Adding Code Into Template

Trimpath allows you to run javascript code within your template so templates can be extended using Suitescript calls. This makes it possible to query additional records and fields

// the following example queries a custom field on the employee record to display directly onto the page
// example 1: query the value
${nlapiLookupField("employee",employee.id,"custentity_myfield")}

// example 2: query the text (for list/record)
${nlapiLookupField("employee",employee.id,"custentityhr_baselocation",true)}

// the next example is when you want to obtain multiple fields from the same record
// assign the result to a variable that can then be used further down in the template.

{var custdata = nlapiLookupField("employee",employee.id,['subsidiary','supervisor'],true)}
subsidiary:  ${custdata['subsidiary']}
supervisor: ${custdata['supervisor']}



Making Suitescript API calls within the template uses API governance where possible its recommended not to use this functionality.

Note: The process of generating payslips as part of a payrun consumes a lot of governance for each payslip the following activities occur:

  • create pdf representation of payslip in memory
  • create file cabinet record
  • create custom record to attach file cabinet to payslip record
  • attach custom record to employee and payrun (currently disabled)

As a result its recommended not to perform lookups within the page unless absolutely necesary and only for limit employee payruns.


Stylesheets CSS

Cascading Stylesheets (CSS) are used to define how a particular HTML tag should be displayed, its designed to seperate structure from the way in which the structure is displayed.

<style>
body {font-family: helvetica; font-size: 9pt}
h1 {font-family: helvetica; font-weight: bold; font-size: 13pt}
</style>   

Note: The BFO PDF library supports a strange subset of CSS in a non standard way http://bfo.com/products/report/

PDF Body Attributes

In some instances you may wish to change the margins of the printable page (for example if the payslip is printing outside of printable margins on the printer). To do this you can edit the field PDF Body Attributes remembering the options added in here will be placed in the <body> tag.

size='A4' margin-top='16mm' margin-left='16mm' margin-right='16mm' margin-bottom='24mm' padding='0' 


Debugging Templates

  • The result of the output does not meet XHTML structure - in this scenario the xmlToPDF function cannot convert the template into PDF this can occur when you have special characters that have not been encoded (e.g Sprockets & Bolts Ltd - use the |h modifier to encode)
  • An error appears in the template like this [ERROR: ReferenceError: "$nlapiLookupField" is not defined.; "$nlapiLookupField" is not defined.]  This is the template engine failing to interprete something within {} or ${} and you should review your replacements.

The best approach for designing and debugging is to take regular copies of your template whilst you are developing. If you then subsequently introduce an error which you cannot resolve you can roll back to the original copy and retrace your steps.

Display Template Data

To display an html table containing all of the data and the trimpath markup needed to replace the data into the template, you must add the following into the template:


${debugstr}

Note: Debug is only available through the payslip print preview functionality.

For more information review the Trimpath Documentation