🚀 Automatically Moving Business Process Flow (BPF) Stages in Dynamics 365 Using JavaScript


In many real-world Dynamics 365 / Model-Driven App implementations, Business Process Flow (BPF) stages must reflect the actual business state of a record, not just user clicks.

In this blog, I’ll explain how to automatically move a BPF stage on form load based on:

  • Request Status

  • Assigned To field

This ensures the BPF always stays in sync with the record’s lifecycle — even when data is updated by background processes, integrations, or workflows.




🎯 Business Requirement

When a record is opened:

  • The BPF stage should automatically move to the correct stage

  • The stage depends on:

    • he_requeststatus

    • he_assignedto

Example Rules



This removes manual stage movement and improves data accuracy and user experience.


🧠 Key Design Challenges

Microsoft does not provide a direct API to jump to a specific BPF stage by name.
Instead, we only have:

  • moveNext()

  • movePrevious()

So the solution:
✅ Determines the target stage
✅ Figures out whether to move forward or backward
✅ Moves step-by-step until the desired stage is reached
✅ Includes safety limits to prevent infinite loops


⚙️ Step 1: Trigger Logic on Form Load

The function below runs when the form loads and decides which BPF stage should be active:

function moveBpfStage(executionContext) { const formContext = executionContext.getFormContext(); const process = formContext.data.process; setTimeout(function () { const status = formContext.getAttribute("he_requeststatus")?.getValue(); const assignedTo = formContext.getAttribute("he_assignedto")?.getValue(); if (!process) return; let targetStageName = null; if (status === 0) { targetStageName = "Draft"; } else if (status === 1) { targetStageName = "Initiated"; } else if (status === 2 && !assignedTo) { targetStageName = "Waiting For Reviewer Assigned"; } else if (status === 2 && assignedTo) { targetStageName = "Waiting For Approval"; } else if (status === 3 || status === 4) { targetStageName = "Complete"; } if (!targetStageName) return; moveBpfSmart(process, targetStageName); }, 1500); }

✅ Why setTimeout(1500)?

The BPF is not immediately available on form load.
This delay ensures the process control is fully initialized before attempting movement.


⚙️ Step 2: Smart Movement Logic (Forward / Backward)

This function handles the actual movement:

function moveBpfSmart(process, targetStageName) { let safetyCounter = 0; const MAX_STEPS = 10; function step() { const activeStage = process.getActiveStage(); if (!activeStage) return; const currentName = activeStage.getName(); if (currentName === targetStageName) { console.log("✅ BPF reached target stage:", targetStageName); return; } if (safetyCounter++ > MAX_STEPS) { console.error("❌ BPF movement stopped: safety limit reached"); return; } if (!step.direction) { step.direction = guessDirection(process, targetStageName); } if (step.direction === "forward") { process.moveNext(function (result) { if (result === "success") { setTimeout(step, 300); } }); } else { process.movePrevious(function (result) { if (result === "success") { setTimeout(step, 300); } }); } } step(); }

✅ Why loop movement?

The API only allows moving one stage at a time.
So we loop until the correct stage is reached.

✅ Why safety counter?

Prevents infinite loops if:

  • Stage names change

  • BPF structure changes

  • Movement is blocked


⚙️ Step 3: Direction Guessing (Forward vs Backward)

function guessDirection(process, targetStageName) { const activeStageName = process.getActiveStage()?.getName(); const stageOrder = [ "Draft", "Initiated", "Waiting For Reviewer Assigned", "Waiting For Approval", "Complete" ]; const currentIndex = stageOrder.indexOf(activeStageName); const targetIndex = stageOrder.indexOf(targetStageName); if (currentIndex === -1 || targetIndex === -1) { return "forward"; } return targetIndex > currentIndex ? "forward" : "backward"; }

✅ Why heuristic?

There is no official API to read all BPF stages.
So we define the known stage sequence and compare indexes.

This allows:
➡️ Forward movement
⬅️ Backward movement
🎯 Efficient navigation to the target stage


🏗️ Architecture Overview

Trigger:
Form Load → JavaScript

Flow:
Form → Read Status & Assigned To → Decide Target Stage → Smart BPF Navigation


💡 Benefits of This Approach

✔ Keeps BPF always in sync with record state
✔ Works for background updates and integrations
✔ Prevents incorrect stage positioning
✔ Zero manual user interaction
✔ Safe & maintainable logic
✔ Easily extendable for more statuses


⚠️ Best Practices

  • Keep BPF stage names stable

  • Update stageOrder when BPF changes

  • Register script only once

  • Avoid running this logic on every field change

  • Add logs for debugging

  • Test movement permissions for different security roles


🎯 Final Thoughts

This approach gives you deterministic BPF control without unsupported APIs.
It’s production-safe, extensible, and aligned with how Dynamics 365 handles process flows internally.

Comments

Popular posts from this blog

AI-Powered Power Pages Development with Claude Code Plugin

🛠 Power Automate: Handling Cached Runs After Long Downtime