Problem Statement

For any assignment form that is running on older app versions, any use of ID Generator Field/Tool would be picking the next running number from the app version it started on.

App VersionPublishedEnvironment Variable "counter" Value

For example, process instance started on app version 1 will get 11 as the next number. Likewise, the one started on app version 2 will get 11 too.

This does not go well if the number generated is a running number (Ii.e. invoice/receipt number) that must NOT be repeated.

The solution is to intercept at the data storing layer (form or form section) where the ID Generator Field is placed on.


In the form where the ID Generator Field is in, modify the store binder, and set it to Bean Shell Form Binder.

Modify line 17 to 27 to suit your app.

import org.joget.apps.form.model.Element;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.text.DecimalFormat;
import org.joget.commons.util.StringUtil;
import org.joget.apps.form.model.FormStoreBinder;
import org.joget.plugin.base.PluginManager;

//Your App ID here
String appId = "SampleApp";

//Environment Variable ID here
String envId = "PaymentRunningNumber";

//ID generator field ID here
String genFieldId = "field3";

//ID generator value format
String format = "RECEIPT/#date.MMyy#/??????";

public FormRowSet store(Element element, FormRowSet rows, FormData formData) {
    FormRow row = rows.get(0);
    String result = incrementEnvVar(appId,envId);
    row.setProperty(genFieldId, result);
    //Reuse Workflow Form Binder to store data
    PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
    FormStoreBinder binder = (FormStoreBinder) pluginManager.getPlugin("org.joget.apps.form.lib.WorkflowFormBinder");, rows, formData);
    return rows;

public String incrementEnvVar(String appId, String envId) {
    AppService appService = (AppService) AppUtil.getApplicationContext().getBean("appService");

    /* Set null to always get latest version */
    //AppDefinition appDef = appService.getAppDefinition(appId,null);
    /* Set with publishedVersion to always get published version */
    Long publishedVersion = appService.getPublishedVersion(appId);
    AppDefinition appDef = appService.getAppDefinition(appId,publishedVersion.toString());
    EnvironmentVariableDao environmentVariableDao = (EnvironmentVariableDao) AppUtil.getApplicationContext().getBean("environmentVariableDao");
    EnvironmentVariable env = environmentVariableDao.loadById(envId, appDef);
    /* For testing only to check retrieved env-var */
    // System.out.println("This is env-var value --> " + env.getValue());
    Integer count = null;
    if (publishedVersion.compareTo(Long.parseLong("#appDef.version#")) == 0) {
        count = Integer.parseInt(env.getValue());
    } else {
        count = environmentVariableDao.getIncreasedCounter(envId, null, appDef);
    String value = format;
    Matcher m = Pattern.compile("(\\?+)").matcher(format);
    if (m.find()) {
        String pattern =;
        String formatter = pattern.replaceAll("\\?", "0");
        pattern = pattern.replaceAll("\\?", "\\\\?");
        DecimalFormat myFormatter = new DecimalFormat(formatter);
        String runningNumber = myFormatter.format(count);
        value = value.replaceAll(pattern, StringUtil.escapeRegex(runningNumber));
    return value;

return store(element, rows, formData);

This change should be done at every non-published version so that they will be picking up and using the environment variable from the published version.

  • No labels