# `GanttEx.Task`
[🔗](https://github.com/rocket4ce/gantt_ex/blob/v0.1.0/lib/gantt_ex/task.ex#L1)

A task (row) of the Gantt chart.

Tasks mirror the DHTMLX Gantt data model:

  * `:id` - unique identifier (integer, string or atom). Required.
  * `:text` - the task name.
  * `:start_date`, `:end_date` - `NaiveDateTime` boundaries (end is exclusive).
  * `:duration` - integer amount of the chart's duration unit (`:day` by default).
  * `:progress` - completion between `0.0` and `1.0`.
  * `:parent` - id of the parent task, `nil` for root tasks.
  * `:type` - `:task`, `:project` (summary task whose dates are computed from
    its children) or `:milestone` (zero-duration event).
  * `:open` - whether the children are expanded initially.
  * `:readonly` - disables editing for this task.
  * `:color`, `:text_color`, `:progress_color` - optional CSS colors.
  * `:calendar_id` - the working-time calendar of the task (see `GanttEx.Calendar`).
  * `:constraint_type`, `:constraint_date` - scheduling constraint used by
    auto-scheduling (see `GanttEx.Scheduler` and `constraint_types/0`).
  * `:planned_start`, `:planned_end` - the baseline (the original plan).
  * `:deadline` - the date the task must be finished by.
  * `:render` - `:split` draws the children of the task on its own row.
  * `:rollup` - also show the task on the rows of its ancestor projects.
  * `:resources` - resource assignments (see `GanttEx.Resource.cast_assignments/1`).
  * `:has_children` - the task has children that are not loaded yet (dynamic
    loading: expanding it sends a `:branch_requested` event).
  * `:meta` - any additional fields (for custom grid columns, templates, ...).

Only one or two of `:start_date`, `:end_date` and `:duration` are needed: the
missing one is computed by `GanttEx.Store` (see `GanttEx.Store.new/3`).

## Building tasks

`new/1` accepts maps (atom or string keys), keyword lists and structs, such
as Ecto schemas. Unknown fields are kept in `:meta`, and `:parent_id` is
accepted as an alias of `:parent`:

    iex> {:ok, task} = GanttEx.Task.new(%{"id" => 1, "text" => "Design", "start_date" => "2025-01-06", "duration" => 3, "owner" => "Ana"})
    iex> {task.id, task.text, task.start_date, task.duration, task.meta}
    {1, "Design", ~N[2025-01-06 00:00:00], 3, %{"owner" => "Ana"}}

    iex> {:ok, task} = GanttEx.Task.new(id: 2, parent_id: 1, type: "milestone", start_date: ~D[2025-01-10])
    iex> {task.parent, task.type}
    {1, :milestone}

    iex> GanttEx.Task.new(%{text: "No id"})
    {:error, {:missing, :id}}

    iex> GanttEx.Task.new(%{id: 1, progress: 50})
    {:error, {:invalid, :progress}}

# `constraint`

```elixir
@type constraint() :: :asap | :alap | :snet | :snlt | :fnet | :fnlt | :mso | :mfo
```

A scheduling constraint: as soon/late as possible, start/finish no
earlier/later than a date, or must start/finish on a date.

# `error`

```elixir
@type error() :: {:missing, atom()} | {:invalid, atom()}
```

Casting errors returned by `new/1` and `change/2`.

# `id`

```elixir
@type id() :: integer() | String.t() | atom()
```

A task or link identifier.

# `t`

```elixir
@type t() :: %GanttEx.Task{
  calendar_id: term(),
  color: String.t() | nil,
  constraint_date: NaiveDateTime.t() | nil,
  constraint_type: constraint() | nil,
  deadline: NaiveDateTime.t() | nil,
  duration: non_neg_integer() | nil,
  end_date: NaiveDateTime.t() | nil,
  has_children: boolean(),
  id: id(),
  meta: map(),
  open: boolean(),
  parent: id() | nil,
  planned_end: NaiveDateTime.t() | nil,
  planned_start: NaiveDateTime.t() | nil,
  progress: float(),
  progress_color: String.t() | nil,
  readonly: boolean(),
  render: :split | nil,
  resources: [GanttEx.Resource.assignment()],
  rollup: boolean(),
  start_date: NaiveDateTime.t() | nil,
  text: String.t(),
  text_color: String.t() | nil,
  type: type()
}
```

# `type`

```elixir
@type type() :: :task | :project | :milestone
```

The kind of task.

# `cast_constraint`

```elixir
@spec cast_constraint(term()) :: {:ok, constraint() | nil} | :error
```

Casts a constraint type (DHTMLX codes such as `"snet"` or atoms).

    iex> GanttEx.Task.cast_constraint("mso")
    {:ok, :mso}

    iex> GanttEx.Task.cast_constraint("")
    {:ok, nil}

# `cast_type`

```elixir
@spec cast_type(term()) :: {:ok, type()} | :error
```

Casts a task type from an atom or string.

    iex> GanttEx.Task.cast_type("project")
    {:ok, :project}

    iex> GanttEx.Task.cast_type(nil)
    {:ok, :task}

    iex> GanttEx.Task.cast_type("epic")
    :error

# `change`

```elixir
@spec change(t(), map() | keyword()) :: {:ok, t()} | {:error, error()}
```

Applies `attrs` on top of an existing task, casting only the given fields.

The `:id` cannot be changed. Unknown fields are merged into `:meta`.

    iex> task = GanttEx.Task.new!(id: 1, text: "Draft", start_date: ~D[2025-01-01])
    iex> {:ok, task} = GanttEx.Task.change(task, %{"text" => "Final", "progress" => "0.25"})
    iex> {task.text, task.progress}
    {"Final", 0.25}

# `constraint_types`

```elixir
@spec constraint_types() :: [constraint()]
```

Returns the supported constraint types.

    iex> GanttEx.Task.constraint_types()
    [:asap, :alap, :snet, :snlt, :fnet, :fnlt, :mso, :mfo]

# `milestone?`

```elixir
@spec milestone?(t()) :: boolean()
```

Returns `true` for milestones.

    iex> GanttEx.Task.milestone?(GanttEx.Task.new!(id: 1, type: :milestone))
    true

# `new`

```elixir
@spec new(map() | keyword() | struct()) :: {:ok, t()} | {:error, error()}
```

Builds a task from a map, keyword list or struct. See the module docs.

# `new!`

```elixir
@spec new!(map() | keyword() | struct()) :: t()
```

Like `new/1` but raises `ArgumentError` on invalid attributes.

    iex> GanttEx.Task.new!(id: 1, text: "Kickoff", start_date: ~D[2025-01-01]).text
    "Kickoff"

# `project?`

```elixir
@spec project?(t()) :: boolean()
```

Returns `true` for project (summary) tasks.

    iex> GanttEx.Task.project?(GanttEx.Task.new!(id: 1))
    false

# `to_map`

```elixir
@spec to_map(t(), keyword()) :: map()
```

Converts the task into a plain map, handy to persist it.

`:meta` fields are merged at the top level unless `meta: false` is given.

    iex> task = GanttEx.Task.new!(id: 1, text: "A", start_date: ~D[2025-01-01], duration: 2, priority: :high)
    iex> GanttEx.Task.to_map(task) |> Map.take([:id, :text, :duration, :priority])
    %{id: 1, text: "A", duration: 2, priority: :high}

# `types`

```elixir
@spec types() :: [type()]
```

Returns the supported task types.

    iex> GanttEx.Task.types()
    [:task, :project, :milestone]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
