How to Check Whether Any Dataverse Record Is in a Particular Business-Process-Flow (BPF) Stage

Knowing the current stage of a Business Process Flow (BPF) lets you lock fields, hide buttons, or trigger downstream logic. Below are two generic patterns that work in every online environment—no proprietary entity names, no deprecated APIs, and no plug-ins.


1  |  When You’re Already on the Form

Fastest – zero Web API calls.

/**
 * Returns true if the form’s active stage matches any item
 * in stageNames (case-insensitive).
 *
 * @param {Xrm.ExecutionContext} executionContext
 * @param {string[]} stageNames  e.g. ["expense authority","decision made"]
 */
function isCurrentStage(executionContext, stageNames)
{
    const formContext = executionContext.getFormContext();
    const activeStage = formContext.data.process.getActiveStage();
    if (!activeStage) { return false; }

    const name = (activeStage.getName() || "").toLowerCase();
    return stageNames.map(s => s.toLowerCase()).includes(name);
}

/* Usage */
if (isCurrentStage(executionContext, ["stage a","stage b"])) {
    // lock / hide / validate
}

2  |  When the Record Is Not Open
   (Ribbon, Sub-grid, Power Automate …)

Every BPF you create is backed by an auto-generated table (BPF entity). That table stores:

  • a lookup to the primary record (column name starts with _bpf_…)
  • activestageid – the current stage GUID
  • the formatted value of activestageid – the readable stage name

2.1  Find the BPF Table Name

  • Solution → Processes → <Your BPF> → Properties
    “Entity Name” shows something like new_recoveryclaimbpf.
  • Maker Portal → Tables → All tables (filter by Type = Process) – same name ending in bpf.
  • Advanced Find / Modern Advanced Find – look for a table with a puzzle-piece icon.

General naming pattern:
<publisher-prefix>_<first 50 chars of BPF name>bpf

2.2  Generic Helper

/**
 * Fetches the lower-case stage name by querying the record’s BPF row.
 *
 * @param {string} bpfTable      e.g. "new_recoveryclaimbpf"
 * @param {string} primaryLookup e.g. "_bpf_projectclaimid_value"
 * @param {string} recordId      GUID without braces
 * @return {Promise<string|null>}
 */
function fetchStageName(bpfTable, primaryLookup, recordId)
{
    const query =
        `?$select=_activestageid_value` +
        `&$filter=${primaryLookup} eq ${recordId} and statecode eq 0` +
        `&$top=1`;

    return Xrm.WebApi.retrieveMultipleRecords(bpfTable, query)
        .then(r => {
            if (!r.entities.length) { return null; }
            return (r.entities[0]
              ["_activestageid_value@OData.Community.Display.V1.FormattedValue"] || "")
              .toLowerCase();
        });
}

2.3  Check the Stage

fetchStageName(
    "new_recoveryclaimbpf",      // BPF table
    "_bpf_projectclaimid_value", // lookup to parent
    someRecordId                 // target GUID
).then(stage => {
    if (stage && ["expense authority","decision made"].includes(stage)) {
        // record is in one of the target stages
    }
});

3  |  Field-Lock Example

/**
 * Locks / unlocks a decision field based on
 * the parent record’s BPF stage and the user’s role.
 */
function lockDecision(executionContext)
{
    const formContext  = executionContext.getFormContext();
    const decisionAttr = formContext.getAttribute("prefix_decision");
    if (!decisionAttr) { return; }

    /** 1 ▸ privileged role bypass */
    if (myUtility.userHasRole("Data Fix")) { toggle(false); return; }

    /** 2 ▸ parent record ID */
    const parent = formContext.getAttribute("prefix_parentid")?.getValue();
    if (!parent?.length) { toggle(false); return; }
    const parentId = parent[0].id.replace(/[{}]/g, "");

    /** 3 ▸ stage check */
    fetchStageName(
        "prefix_parentbpf",          // BPF table
        "_bpf_parentid_value",       // lookup column
        parentId
    ).then(stage => {
        const lock = ["expense authority","decision made"].includes(stage || "");
        toggle(lock);
    }).catch(() => toggle(false));

    function toggle(disabled) {
        decisionAttr.controls.forEach(c => c.setDisabled(disabled));
    }
}

4  |  Key Take-aways

  • Same formformContext.data.process.getActiveStage()
  • Elsewhere → query the hidden BPF table once and read the formatted stage name.
  • Each BPF always has its own table; locate it in the solution or maker portal.
  • Compare stage names in lower-case to avoid localisation or casing issues.

 These two patterns let you tailor UX or logic to a record’s exact BPF stage in under 20 lines of code.

2/2

No comments:

Post a Comment