Dynamics 365

Building an External Scheduling Engine for D365 Project Operations | Part 3: Status Date Scheduling

Share
External scheduling engine for Dynamics 365 Project Operations, part 3: status date scheduling

In this series

Building an external scheduling engine for Dynamics 365 Project Operations. Part 1: the WBS data model · Part 2: planned work contours · Part 3: status date scheduling · Part 4: the working-time engine · Part 5: dependencies and the critical path · Part 6: making it fast · Part 7: work that arrives from outside

Status Date Scheduling: Pushing Remaining Work Forward

In the previous topic we covered how a planned work contour distributes effort over time. The next step is harder (and more important in real life): what happens when “today” moves forward and the plan contains work that hasn’t actually been earned yet?

This post explains status date (sometimes called “data date”) from first principles, and gives a practical set of rules you can implement in an external scheduling engine for Dynamics 365 Project Operations to ensure no unearned work remains behind today — while still preserving a stable medium-range plan.

Key idea

The status date is the boundary between history and forecast. Earned work stays where it happened. Unearned work is not allowed to remain behind “today” — it must be pushed forward into valid working time.

Status date (data date)
Earned vs. remaining
No work in the past
Calendar-aware push

1) What is the status date?

The status date is the vertical “today” line you advance as time progresses. Conceptually:

  • Left of the status date is history (actuals / earned work).
  • Right of the status date is forecast (remaining / unearned work).

If your schedule contains unearned work before the status date, the model is lying about reality — and your downstream signals (variance, utilization, float, “what should we do today?”) degrade quickly.

2) The rules that make status-date scheduling work

These rules are intentionally tool-agnostic. You can implement them in any scheduling engine, then map them into Dynamics 365 Project Operations external scheduling.

Principles

  • No unearned work behind the status date.
  • Total effort stays constant. If work isn’t complete, duration may extend — the plan stretches; effort doesn’t magically vanish.
  • Earned work is preserved as history (audit trail).
  • Only deliberate priority changes should reposition work in the medium-range plan.

These principles are the core of status-date management in a stable planning horizon (e.g., a rolling 12-week plan).

3) Planning horizon: stable plan + daily control

A common operating model is a rolling planning horizon where the plan is intentionally stable unless someone makes a deliberate change (re-prioritization). Within that stable plan, the status-date process provides daily execution control: reconcile what was planned vs. what was earned, then push the remainder forward.

One concrete example of this model is a 12-week static plan with:

  • Week 1 = frozen
  • Week 2 = soft
  • Weeks 3–12 = rough-cut planning

The exact windowing is a choice, but the underlying idea is timeless: stability comes from governance, while reality comes from earned vs. planned.

Where the status date lives decides what it moves

The status date is not a standard column. Put it on the program rather than on each project, because that is the level a scheduler actually works at. Then make a deliberate governance choice per program:

  • Managed by the program. One master schedule. The program’s date cascades to every sub-program and project underneath it; their own fields are read-only.
  • Managed by the sub-programs. Each outage or turnaround owns its own status date. The parent program is a cost roll-up container and never pushes anyone’s work.

Routine maintenance usually wants the first. Outages and turnarounds almost always want the second. Build both and let the program record say which applies; do not infer it from the hierarchy.

4) The three task scenarios your engine must handle

When the status date advances, tasks fall into a few predictable buckets. The goal is always the same: ensure remaining work begins on or after the status date (in working time).

Scenario A: Task not started, entirely before the status date

Condition: no earned hours, and both planned start and planned finish are behind the status date.

Rule: shift the task so the new start is the status date (snapped to working time), then recompute finish using calendar-aware duration logic.

Scenario B: Task not started, overlaps the status date

Condition: no earned hours, planned start is behind the status date, and planned finish is ahead of it.

Rule: shift the start to the status date (snapped to working time) and push the full unearned workload forward. Conceptually, you preserve the “working duration” logic — you simply stop pretending the task started earlier.

Scenario C: Task started, but not complete

Condition: some hours are earned, but remaining hours still exist.

Rule: earned work stays where it occurred. The remaining work is pushed forward so it resumes on or after the status date (snapped to working time). This will usually extend the finish date and may visually appear as a “split” in a Gantt view — but you don’t need to create subtasks just to get that visual effect.

Scenario D: the status date moves backwards

Condition: the new status date is earlier than the old one (a correction, or a report being re-run as of an earlier date).

Rule: do not touch any task dates. A backward move is a change of viewpoint, not a change of plan; the only thing to do is recompute float and criticality as of the new date. If a backward move costs the same as a forward move in your implementation, it is doing work it should not be doing.

Constraints move with the task

A pushed task that carries a date constraint cannot be left contradicting it. Adjust the constraint on the same write, not in a second pass:

  • Must start on and start no earlier than: the constraint date becomes the new start.
  • Must finish by: the constraint date becomes the new finish, and the task is now flagged as exceeding its original commitment.

Record that the constraint was moved by the sweep, not by a planner. The planner will want to know which of their commitments the status date quietly rewrote.

Important: calendars are non-negotiable

All shifting must respect working days and working hours. If the status date is on a weekend/non-working window, the “new start” should be snapped to the next working interval, and the finish should be recalculated by adding working time (not elapsed time).

5) Calendar snapping: the one function you can’t skip

Status-date logic breaks if you treat dates as simple timestamps. You need explicit working-time helpers, for example:

nextWorkingInstant(datetime) → datetime
addWorkingMinutes(datetime, minutes) → datetime
workingMinutesBetween(a, b) → minutes

In practice:

  • If the status date lands on a non-working period, set RemainingStart = nextWorkingInstant(statusDate).
  • When computing finish, use addWorkingMinutes (calendar-aware), not “+ N days”.

Part 4 of this series builds these three functions properly: calendar expansion, the fallback chain when a task has no template, and the horizon problem that produces plausible-looking wrong dates.

6) Automation-friendly algorithm (pseudocode)

This pseudocode captures the core behavior most teams currently do manually: lock the past, push the remainder forward, then resolve capacity and dependency impacts.

OnStatusDateAdvance(statusDate):

OnStatusDateAdvance(program, newStatusDate):
  if newStatusDate <= oldStatusDate:
      # Scenario D: viewpoint change only
      for each Project in program: recomputeCriticalPath(Project)
      return

  # ---- Phase 1: decide every move in memory, write nothing yet
  moves = []
  for each Task in program:
      if Task.progress == 100 or Task.remainingHours <= 0: continue
      if Task.scheduledStart >= newStatusDate: continue

      remainingStart = nextWorkingInstant(newStatusDate)
      if Task.earnedHours == 0:
          # Scenarios A & B
          newStart  = remainingStart
          newFinish = addWorkingMinutes(newStart, Task.remainingMinutes)
      else:
          # Scenario C: history stays, remainder resumes
          newStart  = Task.scheduledStart
          newFinish = addWorkingMinutes(remainingStart, Task.remainingMinutes)

      constraint = adjustConstraint(Task, newStart, newFinish)   # see box above
      contour    = regenerateContour(Task, remainingStart, Task.remainingHours)
      moves.append(Task, newStart, newFinish, constraint, contour, movedByStatusDate = true)

  # ---- Phase 2: persist the moves as a batch, tagged as engine writes
  #      Per-task triggers recalculate only their own fields; none of them runs CPM.
  batchWrite(moves, tag = "status-date-sweep")

  # ---- Phase 3: once per affected project, on committed data
  for each Project touched by moves:
      propagateDependencies(Project)        # FS/SS/FF/SF, lag preserved
      recomputeCriticalPath(Project)        # forward + backward pass, float, critical flag
      updateBookingsAndCapacity(Project)

  produce exception list (overloads, negative float, constraints moved, work still in the past)

The three-phase shape is not a stylistic choice. Our first implementation did what the earlier version of this post described: it updated each task in turn, and each task’s own trigger ran the project-wide critical path and wrote every sibling. Two tasks in the same batch then locked each other’s rows and the database picked a victim. The rule that fell out of it: a per-task step may only write that task; anything project-wide runs once, afterwards, on committed data. Part 6 covers the full story, including why even the batched version eventually needs to persist asynchronously.

Two things the naive version gets wrong

It nulls the lag. When a successor is pushed and its predecessor is not, the tempting shortcut is to clear the lag on the edge between them so the dependency stops “pulling” the successor back. That silently rewrites the planner’s schedule logic, and no planning tool they have used before does it. Keep the lag. The status date is a floor on the successor’s start, not a reason to change the relationship.

It destroys the history the KPIs need. Planners coming from Primavera P6 expect the status date to be a “time now” cursor: the plan stays put and variance is measured against a baseline. Rewriting scheduled dates every day is a different paradigm, and without a baseline it leaves you nothing to measure “behind” against. Two things make it work: snapshot a baseline before the first push (and on each weekly approval), and stamp every task the sweep touches with a moved-by-status-date flag so a planner can tell a system move from a deliberate one at a glance.

7) Validation and controls

Status-date automation needs guardrails — not just math.

  • Block planned bookings before the status date for tasks that are not complete.
  • Preserve audit history: stamp every task the sweep moves with a moved-by-status-date flag, and record when a constraint was adjusted by the system rather than by a person.
  • Flag overloads (> 100% utilization) and require a scheduler decision.
  • Track deliberate re-prioritizations separately from automatic status-date shifts.

8) KPIs that tell you whether the system is healthy

Suggested KPI set

Earned vs. planned% daily / weekly
Work left in the pastTarget: 0 tasks
Utilization balance~100% (±5%)
Float at riskNegative float count


A stable plan is only useful if it stays truthful: remaining work must be in the future, and capacity/float signals must reflect reality.

9) Governance cadence (how this stays stable)

  • Daily: advance status date; enforce “no unearned work behind today”; act on exceptions.
  • Weekly: approve the near-term window; baseline and publish; review variance.
  • Monthly: trend KPIs and drive continuous improvements.

Next in the series: Part 4 builds the working-time engine the sweep depends on. Part 5 takes the phase-3 step above apart: dependency propagation, the forward and backward passes, what the due date anchors, and how constraints and dependencies interact.

Keep reading

Building something in the Power Platform?

We design and ship the systems behind these posts. Tell us what you are working on.

Book a meeting All posts