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.
JSONB map of secondary-language overrides for translatable fields.
Functions
Lifecycle state for this project, in priority order
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
@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.
@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 }
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
@spec derived_status(t(), DateTime.t()) :: derived_state()
Lifecycle state for this project, in priority order:
:archived— soft-hidden (archived_atis set):template—is_template: true:completed—completed_atis set:running—started_atis 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.
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).
lang may be nil (e.g. when multilang is disabled) — in that case
the primary column is returned directly.
@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
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.
@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.