GanttEx.Scheduler (GanttEx v0.1.0)

Copy Markdown View Source

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:

ConstraintMeaningEffect
:asapas soon as possible (default)dependencies only
:alapas late as possibleas late as successors and the project allow
:snetstart no earlier than the datelower bound
:fnetfinish no earlier than the datelower bound
:snltstart no later than the dateupper bound (wins over dependencies)
:fnltfinish no later than the dateupper bound (wins over dependencies)
:msomust start on the datefixed start
:mfomust finish on the datefixed 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

compact(changes)

@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}}}]

schedule(store, opts \\ [])

@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 to false.

Returns {:ok, store, changes} like the mutations of GanttEx.Store.

target_start(store, task, strict \\ false)

@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.