Project#apply#

Writes edits into the accepted state of a Project and marks it unsolved. It doesn't schedule anything: call solve(null, …) or level afterwards to get the new dates.

Signature#

public ApplyReport apply(SchedulingChanges changes)

Synchronous and CPU-only. Changes the project's state. Not synchronized: serialize calls on one instance.

Parameters#

Name Type Description
changes SchedulingChanges The edits to commit. Must not be null

Behavior#

  • Operations run in order and are partially accepted. A rejected operation goes into ApplyReport.rejected and isn't thrown.
  • Any rejection sets needsResync. From then on solve(null, …), apply and level throw NEEDS_RESYNC until a full solve(data, …).
  • changes.baselineId, when set, must name the current baseline. null skips the check.
  • The project is unsolved afterwards. preview, loadProfile, evaluate, suggestAssignments and roster need a solve first.
  • Tasks added or updated by successful operations accumulate across calls. The next acyclic solve(null, …) or level reports each one as a direct update.
  • An explicit start that falls on non-working time is kept unsnapped when the task has no predecessor. The end is computed forward from it on the working calendar.

Returns#

An ApplyReport with the applied and rejected operations, their side effects and the new revision.

Errors#

All exceptions are unchecked.

Error When
ProjectException with code == ProjectError.NEEDS_RESYNC An earlier apply rejected an operation and no solve(data, …) followed. Checked first
ProjectException with code == ProjectError.STALE_BASELINE changes.baselineId doesn't name the current baseline
NullPointerException changes is null

Example#

Commit an edit, resync with the full data if part of it is rejected, and reschedule.

import java.util.List;
import org.dhtmlx.plen.*;

public class ApplyExample {
  public static void main(String[] args) throws Exception {
    Moment monday = Moment.fromYmdHm(2026, 8, 10, 8, 0);
    ProjectData data = new ProjectData()
        .withTasks(List.of(new TaskData(Id.num(1)).withStartDate(monday).withDuration(2.0)));

    SolveOptions options = new SolveOptions();
    Project project = new Project();
    ScheduleResult solved = project.solve(data, options);

    SchedulingChanges changes = new SchedulingChanges().withBaselineId(solved.baselineId);
    changes.tasks.update.add(new TaskUpdate(Id.num(1)).withDuration(Patch.of(3.0)));

    ApplyReport report = project.apply(changes);
    for (RejectedOperation rejected : report.rejected) {
      System.out.println("rejected at " + rejected.path + ": " + rejected.message);
    }

    // After a rejection, only a full solve with data is accepted.
    ScheduleResult result = report.needsResync
        ? project.solve(data, options)
        : project.solve(null, options);
    System.out.println(result.updates.size() + " task(s) moved"); // 1 task(s) moved
  }
}