How to Generate msdyn_plannedwork Contours in Dynamics 365 Project Operations (External Scheduling)
In Dynamics 365 Project Operations external scheduling, the platform can store and display time-phased hours — but the planned work contour (the day-by-day distribution) must be produced correctly so grids, estimates, and reporting behave as expected.
This post breaks down the first principles behind work templates → contours → msdyn_plannedwork, then gives a clear algorithm (plus validation steps) to generate the JSON stored on the Resource Assignment.
Key idea
Effort distribution should follow the work template (your working-time model) — not the availability of an individual who may be assigned later.
msdyn_plannedwork JSON1) What problem are we solving?
Project planning needs a time-phased view of hours (day-by-day). In Project Operations, that view comes from the planned work contour stored on the Resource Assignment.
In external scheduling, you should assume that planned work must be calculated explicitly and stored as msdyn_plannedwork. If this field is empty or malformed, time-phased grids and totals won’t reflect the plan correctly.
2) What is a contour (first principles)?
A schedule isn’t “40 hours sometime next week.” A schedule is a distribution of work across working time. That distribution is a contour — the breakdown of hours by working day (or working interval).
In Project Operations, the contour is stored as JSON in:
msdyn_resourceassignment.msdyn_plannedwork
3) Work templates: the source of working time
Contour generation needs one consistent definition of “working time.” That definition comes from a work template (working days, daily windows, breaks, shift patterns).
Important: A work template can be applied at project or project task level. Enabling task-level templates typically requires customization (so the template can be chosen and stored per task). The contour algorithm stays the same — it simply uses the applicable template.
Practical rule
Pick one work template as the source of truth for the assignment, then compute the contour strictly within that template’s working intervals.
4) The contour algorithm
This approach produces a deterministic contour that:
- allocates effort only on working days defined by the work template
- skips non-working days entirely (no JSON entries)
- does not attempt leveling/overbooking checks (that’s a separate layer)
Inputs
- Assignment start and finish (from the task)
- Total effort for the assignment (hours)
- Working time per day from the work template
Steps
- Find all working days between start and finish (inclusive) using the work template.
- For each working day
d, computeWorkingTime_d(hours). - Compute a single scaling factor Units so:
Σ(Units × WorkingTime_d) = TotalEffort - For each working day:
Hours_d = Units × WorkingTime_d - Build the
msdyn_plannedworkJSON array:
one entry per working day (or per working interval if your template has multiple intervals per day). - Save the JSON string onto the Resource Assignment.
5) Worked example (40 hours across 6 working days)
Scenario:
Example inputs
Note: 9.5 hours/day can represent a 10-hour shift with a 30-minute non-working break. The contour should follow productive working time.
Total productive capacity across the window:
6 × 9.5 = 57.0 hours
Units:
Units = 40 / 57 = 0.7017543859
Hours per working day:
Hours_d = 0.7017543859 × 9.5 = 6.6666666667 ≈ 6.67
Result: the contour distributes planned work as 6.67 hours per working day for the 6 days in the template window.
6) Building the msdyn_plannedwork JSON
The stored value is a JSON array. Each entry represents the work allocation for a working day (or working interval). Each entry contains:
- Start (UTC timestamp in “/Date(…)/” milliseconds format)
- End (UTC timestamp in “/Date(…)/” milliseconds format)
- Hours (decimal hours for that day/interval)
[
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 },
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 },
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 },
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 },
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 },
{ "Start": "/Date(START_UTC_MS)/", "End": "/Date(END_UTC_MS)/", "Hours": 6.67 }
]
7) What do “Start” and “End” mean?
For each working day, use the daily window defined by the work template:
- Start = the first working minute of the day (template)
- End = the last working minute of the day (template)
This keeps the contour aligned to the template — including breaks and non-working intervals.
8) Validation checklist (quick, reliable, repeatable)
- Create a view for Resource Assignments that includes: Project, Task, Start, Finish, Total Effort (Hours), Planned Work (
msdyn_plannedwork). - Validate the raw JSON:
Σ(Hours) = Total Effort- No entries on non-working days
- Start/End align with the template day windows
- Validate the UI distribution:
- Open the project → Resource Assignments tab
- Set the time scale to Day
- Confirm daily hours match your contour (e.g., 6.67 across expected days)
9) Common pitfalls (and how to avoid them)
- Allocating on non-working days: skip those days entirely — no JSON entries.
- Using inconsistent working time definitions: always anchor contour generation to the chosen work template.
- Rounding drift: define a deterministic rounding rule (e.g., round to 2 decimals and distribute remainder on the last working day).
- Time zone confusion: validate using stored UTC values to avoid UI display surprises.
FAQ (for people searching)
msdyn_plannedwork in Project Operations? msdyn_plannedwork is the JSON field on the Resource Assignment that stores the time-phased planned work contour (hours distributed over working time).
The contour is driven by the work template (working days, daily windows, breaks/shifts) and the assignment’s start/finish window, scaled to match total effort.
No — contour generation should be deterministic and local to the assignment. Overbooking and leveling is a separate layer with different tradeoffs.
Next in the series: we’ll build the working time engine behind this calculation — calendars, breaks, exceptions, and how to reliably compute “first working minute” and “last working minute” for a work template.