Auto-scheduling: moves tasks so that dependencies and constraints hold.
Tasks are processed in dependency order (see GanttEx.Dependency). Each
task starts at the earliest date its predecessors allow, adjusted by its
constraint:
| Constraint | Meaning | Effect |
|---|---|---|
:asap | as soon as possible (default) | dependencies only |
:alap | as late as possible | as late as successors and the project allow |
:snet | start no earlier than the date | lower bound |
:fnet | finish no earlier than the date | lower bound |
:snlt | start no later than the date | upper bound (wins over dependencies) |
:fnlt | finish no later than the date | upper bound (wins over dependencies) |
:mso | must start on the date | fixed start |
:mfo | must finish on the date | fixed end |
By default only tasks that violate a dependency are moved (later). With
strict: true every constrained task moves to its earliest possible date,
so tasks also move earlier when their predecessors do.
iex> store = GanttEx.Store.new!(
...> [
...> %{id: 1, text: "Design", start_date: ~D[2025-01-06], duration: 3},
...> %{id: 2, text: "Build", start_date: ~D[2025-01-07], duration: 2},
...> %{id: 3, text: "Launch", start_date: ~D[2025-01-08], type: :milestone}
...> ],
...> [%{id: 1, source: 1, target: 2}, %{id: 2, source: 2, target: 3}]
...> )
iex> {:ok, store, changes} = GanttEx.Scheduler.schedule(store)
iex> Enum.map(changes, fn {type, task, _} -> {type, task.id} end)
[task_updated: 2, task_updated: 3]
iex> {GanttEx.Store.get_task(store, 2).start_date, GanttEx.Store.get_task(store, 3).start_date}
{~N[2025-01-09 00:00:00], ~N[2025-01-11 00:00:00]}
Summary
Functions
Merges repeated :task_updated changes of the same task (a project can be
recomputed several times while its children move), keeping the first
position and the last value.
Schedules every task of the store.
Returns the start date task should have, or nil to keep it.
Functions
@spec compact([GanttEx.Store.change()]) :: [GanttEx.Store.change()]
Merges repeated :task_updated changes of the same task (a project can be
recomputed several times while its children move), keeping the first
position and the last value.
iex> a = GanttEx.Task.new!(id: 1, text: "A")
iex> b = %{a | text: "B"}
iex> GanttEx.Scheduler.compact([{:task_updated, a, %{changes: %{start_date: 1}}}, {:task_updated, b, %{changes: %{end_date: 2}}}])
[{:task_updated, b, %{changes: %{start_date: 1, end_date: 2}}}]
@spec schedule(GanttEx.Store.t(), keyword()) :: {:ok, GanttEx.Store.t(), [GanttEx.Store.change()]}
Schedules every task of the store.
Options
:strict- move tasks to their earliest possible date, even earlier. Defaults tofalse.
Returns {:ok, store, changes} like the mutations of GanttEx.Store.
@spec target_start(GanttEx.Store.t(), GanttEx.Task.t(), boolean()) :: NaiveDateTime.t() | nil
Returns the start date task should have, or nil to keep it.