# `PhoenixKitProjects.Schemas.Assignment`
[🔗](https://github.com/BeamLabEU/phoenix_kit_projects/blob/v0.14.0/lib/phoenix_kit_projects/schemas/assignment.ex#L1)

A task instance within a project. Copies description and duration from
the task template at creation time — editable independently.

Tracks who completed the task via `completed_by_uuid` + `completed_at`.

# `t`

```elixir
@type t() :: %PhoenixKitProjects.Schemas.Assignment{
  __meta__: term(),
  assigned_department:
    PhoenixKitStaff.Schemas.Department.t()
    | Ecto.Association.NotLoaded.t()
    | nil,
  assigned_department_uuid: UUIDv7.t() | nil,
  assigned_person:
    PhoenixKitStaff.Schemas.Person.t() | Ecto.Association.NotLoaded.t() | nil,
  assigned_person_uuid: UUIDv7.t() | nil,
  assigned_team:
    PhoenixKitStaff.Schemas.Team.t() | Ecto.Association.NotLoaded.t() | nil,
  assigned_team_uuid: UUIDv7.t() | nil,
  child_project:
    PhoenixKitProjects.Schemas.Project.t()
    | Ecto.Association.NotLoaded.t()
    | nil,
  child_project_uuid: UUIDv7.t() | nil,
  completed_at: DateTime.t() | nil,
  completed_by:
    PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil,
  completed_by_uuid: UUIDv7.t() | nil,
  counts_weekends: boolean() | nil,
  dependencies:
    [PhoenixKitProjects.Schemas.Dependency.t()] | Ecto.Association.NotLoaded.t(),
  dependents: term(),
  description: String.t() | nil,
  estimated_duration: integer() | nil,
  estimated_duration_unit: String.t() | nil,
  inserted_at: DateTime.t() | nil,
  position: integer() | nil,
  progress_pct: integer() | nil,
  project:
    PhoenixKitProjects.Schemas.Project.t()
    | Ecto.Association.NotLoaded.t()
    | nil,
  project_uuid: UUIDv7.t() | nil,
  status: String.t() | nil,
  task:
    PhoenixKitProjects.Schemas.Task.t() | Ecto.Association.NotLoaded.t() | nil,
  task_uuid: UUIDv7.t() | nil,
  track_progress: boolean() | nil,
  translations: translations_map(),
  updated_at: DateTime.t() | nil,
  uuid: UUIDv7.t() | nil
}
```

# `translations_map`

```elixir
@type translations_map() :: %{
  optional(String.t()) =&gt; %{optional(String.t()) =&gt; String.t()}
}
```

JSONB map of secondary-language overrides. Same shape as
`Project.translations_map`/`Task.translations_map`.

# `changeset`

```elixir
@spec changeset(t(), map()) :: Ecto.Changeset.t()
```

Form-facing changeset. Does NOT allow setting `completed_by_uuid` or
`completed_at` — those are server-owned fields (use `status_changeset/2`).

# `label`

```elixir
@spec label(t(), String.t() | nil) :: String.t() | nil
```

The display label for this assignment, locale-aware: the child project's
name for a sub-project, otherwise the task template's title. Requires the
relevant association (`:child_project` or `:task`) to be preloaded — both are
in `Projects`' `@assignment_preloads`. `lang` may be `nil` (primary value).

Single source of truth so every render site (timeline title, comment header,
dependency badge, remove-confirm, activity metadata) handles the sub-project
case identically instead of dereferencing a `nil` task.

# `localized_description`

```elixir
@spec localized_description(t(), String.t() | nil) :: String.t() | nil
```

Returns the assignment's description in the requested language, with
primary-fallback semantics: empty/missing override → the primary
`description` column, which itself may be `nil` (in which case the
caller's typical pattern is to fall further back to the parent task's
`localized_description/2`). The double-fallback chain keeps existing
call sites like `a.description || a.task.description` working
locale-aware: `Assignment.localized_description(a, lang) ||
Task.localized_description(a.task, lang)`.

# `status_changeset`

```elixir
@spec status_changeset(t(), map()) :: Ecto.Changeset.t()
```

Server-trusted changeset that also allows setting completion fields.
Use from context functions that own the completion transition, e.g.
progress updates, explicit `complete_assignment/2`, or `reopen_assignment/1`.

# `statuses`

```elixir
@spec statuses() :: [String.t()]
```

# `subproject?`

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

True when this assignment embeds a child project (a sub-project row).

# `subproject_changeset`

```elixir
@spec subproject_changeset(t(), map()) :: Ecto.Changeset.t()
```

Changeset for the parent-side linking assignment of a **sub-project**.

Used both to create the linking row (`create_subproject/2`) and to sync the
denormalized rollup fields whenever the child project changes
(`sync_project_rollup/1`). Unlike `changeset/2` it:

  * casts `child_project_uuid` and the server-owned rollup fields
    (`status` / `progress_pct` / `estimated_duration` / `completed_at` /
    `completed_by_uuid`) but never `task_uuid` — so a sub-project row can't
    be flipped into a task-backed one via this path;
  * does NOT require `estimated_duration > 0` — a child with no tasks rolls
    up to 0 planned hours, which is legitimate.

# `translatable_fields`

```elixir
@spec translatable_fields() :: [String.t()]
```

DB-column field names that participate in the `translations` JSONB.

---

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