AssignmentData#

Books one resource on one task at a utilization rate. Offset and Duration can narrow the booking to a slice of the task's work. Assignments go in ProjectData.Assignments or in a SchedulingChanges.Assignments.Add list. Build one with an object initializer; Task, Resource and Value are required. To change an assignment that a project already holds, send an AssignmentUpdate.

Declaration#

namespace Plen;

public sealed class AssignmentData : IDeepCloneable<AssignmentData>

AssignmentData has no constructor that sets the required properties, so new AssignmentData() without Task, Resource and Value doesn't compile.

Properties#

Property Type Default Description
Id Id? null Assignment id, unique among the project's assignments. null generates one on import.
Task Id required Id of the task.
Resource Id required Id of the resource.
Value double required Units booked, in the scale of the resource's capacity. 1 is 100 %.
Offset double? null Start of the slice: worked time from the task start, in the task's duration unit. null means 0.
Duration double? null Length of the slice in worked time. null books up to the task end.

Id#

public Id? Id { get; set; }

The key that results and change sets use to refer to this assignment, for example in Note.DroppedAssignment and in AssignmentUpdate.Id. A null id gets a generated string id on import that never collides with an explicit id in the same snapshot. Give each assignment an explicit id when later edits must address it.

Task, Resource#

public required Id Task { get; set; }
public required Id Resource { get; set; }

The booked task and resource. Both must name a record in the same snapshot. An assignment on an unscheduled task stays in the project but books no load, and the result reports it as a Note.DroppedAssignment. One resource can have several assignments on one task; each books its own slice at its own Value, and overlapping slices add up.

Value#

public required double Value { get; set; }

The share of the resource the assignment books while it works, in the same units as ResourceData.Capacity. 0.5 is half-time. Must be finite and greater than 0.

Offset, Duration#

public double? Offset { get; set; }
public double? Duration { get; set; }

Narrow the booking to a slice of the task's work. The slice runs from worked time Offset to Offset + Duration, clipped to the task's work. Both null books the whole task, and an Offset at or past the task's work books nothing.

Both values are worked time in the task's DurationUnit: non-working time and pause time don't count, so the slice moves with the work when leveling delays or splits the task. Clipping happens when load is booked, so a slice follows a task that later gets shorter.

A slice changes resource load only. Task dates never depend on Offset or Duration, and editing them never moves a task. See Assignment slices for how slices are measured and booked.

Offset must be finite and 0 or greater. Duration must be finite and greater than 0.

Methods#

DeepClone#

public AssignmentData DeepClone()

Returns a copy with the same property values.

ToString#

public override string ToString()

Returns a readable rendering of every property, for logging.

AssignmentData doesn't override Equals; two instances compare by reference.

Errors#

The snapshot is validated when it's passed to Engine.Schedule or Project.Solve(data, …). An assignment makes the call throw InputException with Kind = InputErrorKind.Validation when:

  • its Id repeats the id of an earlier assignment: Path = "assignments[i].id"
  • its Value isn't finite or is 0 or less: Path = "assignments[i].value"
  • its Offset isn't finite or is less than 0: Path = "assignments[i].offset"
  • its Duration isn't finite or is 0 or less: Path = "assignments[i].duration"
  • its Resource names no resource: Path = "assignments[i].resource"
  • its Task names no task: Path = "assignments[i].task"
  • in CalendarMode.Resource, the task's calendar and the resource's calendar share no weekly working time: Path = "assignments[i]"

These checks apply to assignments on unscheduled tasks too. Validation.Repair fixes two of these cases instead of throwing. A repeated id replaces the earlier assignment at the earlier position. An invalid Offset or Duration is cleared to null. Each repair adds a Note.Repaired to the result.

Assignments added or edited after the first solve go through SchedulingChanges.Assignments. An invalid one there is a rejected operation in the ApplyReport, not an exception.

Example#

Ann works full-time on task 1. Ben reviews it half-time, on the last of its five days only:

using Plen;

var start = Moment.FromYmdHm(2026, 4, 6, 8, 0)
    ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        new TaskData(1) { StartDate = start, Duration = 5.0 },
    ],
    Resources =
    [
        new ResourceData { Id = "ann" },
        new ResourceData { Id = "ben" },
    ],
    Assignments =
    [
        new AssignmentData { Id = "a1", Task = 1, Resource = "ann", Value = 1.0 },
        new AssignmentData
        {
            Id = "a2",
            Task = 1,
            Resource = "ben",
            Value = 0.5,
            Offset = 4.0,
            Duration = 1.0,
        },
    ],
};

var result = Engine.Schedule(data, new SolveOptions());
Console.WriteLine(result.Overallocations?.Count ?? 0);