PhoenixKitProjects.Schemas.Project (PhoenixKitProjects v0.5.1)

Copy Markdown View Source

A project container. Can start immediately (set up tasks first, then mark as started) or be scheduled for a future date.

Soft-hide / archive

archived_at is the soft-hide flag — null = visible, non-null = archived. Mirrors the workspace's trashed_at convention used by publishing posts and core files.

The legacy status string column (V86 / V94) is kept in the table but no longer read or written by application code. See phoenix_kit_projects/AGENTS.md for the deprecation note.

Summary

Types

Human-meaningful lifecycle state derived from the persisted fields.

t()

JSONB map of secondary-language overrides for translatable fields.

Functions

Lifecycle state for this project, in priority order

Calendar end-time for hours of work starting from from, honoring the project's counts_weekends rule.

Returns the project's description in the requested language, with the same primary-fallback semantics as localized_name/2.

Returns the project's name in the requested language, falling back to the primary name column when the language has no override (or the override is empty).

Calendar DateTime when this project would be done if work consumes total_hours of estimated work, starting from started_at.

The list of fields that participate in translations JSONB storage.

Types

derived_state()

@type derived_state() ::
  :archived | :template | :completed | :running | :overdue | :scheduled | :setup

Human-meaningful lifecycle state derived from the persisted fields.

Combines the archived_at soft-hide flag, completion timestamps, start mode, and the scheduled date into the label that's actually meaningful in the UI.

t()

@type t() :: %PhoenixKitProjects.Schemas.Project{
  __meta__: term(),
  archived_at: DateTime.t() | nil,
  assignments:
    [PhoenixKitProjects.Schemas.Assignment.t()] | Ecto.Association.NotLoaded.t(),
  completed_at: DateTime.t() | nil,
  counts_weekends: boolean() | nil,
  description: String.t() | nil,
  inserted_at: DateTime.t() | nil,
  is_template: boolean() | nil,
  name: String.t() | nil,
  position: integer() | nil,
  scheduled_start_date: DateTime.t() | nil,
  start_mode: String.t() | nil,
  started_at: DateTime.t() | nil,
  translations: translations_map(),
  updated_at: DateTime.t() | nil,
  uuid: UUIDv7.t() | nil
}

translations_map()

@type translations_map() :: %{
  optional(String.t()) => %{optional(String.t()) => String.t()}
}

JSONB map of secondary-language overrides for translatable fields.

Shape: %{"es-ES" => %{"name" => "...", "description" => "..."}}. Primary-language values live in the dedicated name/description columns; this map only carries overrides for non-primary languages. Missing/empty overrides fall back to the primary value at render time.

Functions

changeset(project, attrs, opts \\ [])

derived_status(p, now \\ DateTime.utc_now())

@spec derived_status(t(), DateTime.t()) :: derived_state()

Lifecycle state for this project, in priority order:

  • :archived — soft-hidden (archived_at is set)
  • :templateis_template: true
  • :completedcompleted_at is set
  • :runningstarted_at is set and not yet completed
  • :overdue — scheduled, the scheduled_start_date has passed, not started
  • :scheduled — scheduled, start date still in the future, not started
  • :setup — immediate start mode, not yet started

now is injected so callers can pin "now" for tests. The scheduled-overdue check compares full timestamps, not just dates — a project scheduled for today at 09:00 is :overdue by 17:00 the same day.

eta_from(project, from, hours)

@spec eta_from(t(), DateTime.t(), number()) :: DateTime.t() | nil

Calendar end-time for hours of work starting from from, honoring the project's counts_weekends rule.

Sibling of planned_end_for/2 anchored on an arbitrary datetime instead of started_at. Used for the "if work continues at planned pace from now, ETA is …" projection shown on the project page — eta_from(project, DateTime.utc_now(), remaining_hours).

Returns nil when hours <= 0 (nothing left to schedule).

localized_description(p, lang)

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

Returns the project's description in the requested language, with the same primary-fallback semantics as localized_name/2.

localized_name(p, lang)

@spec localized_name(t(), String.t() | nil) :: String.t() | nil

Returns the project's name in the requested language, falling back to the primary name column when the language has no override (or the override is empty).

lang may be nil (e.g. when multilang is disabled) — in that case the primary column is returned directly.

planned_end_for(project, hours)

@spec planned_end_for(t(), number()) :: DateTime.t() | nil

Calendar DateTime when this project would be done if work consumes total_hours of estimated work, starting from started_at.

For counts_weekends: true projects this is simple calendar add. For weekday-only projects (counts_weekends: false) weekend days contribute zero work hours: the calendar walks forward but only weekday hours count toward the budget, scaled at the convention PhoenixKitProjects.Schemas.Task.to_hours/3 uses (8 work hours per 24-hour weekday). Starting on a weekend skips to Monday before any budget is consumed.

Returns nil when the project hasn't started or has no estimated work.

start_modes()

translatable_fields()

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

The list of fields that participate in translations JSONB storage.

Used by the form layer to drive merge_translatable_params/4 and by reads to know which keys to look up under each language code.