PhoenixKit.Migrations.Postgres.V127 (phoenix_kit v1.7.131)

Copy Markdown View Source

V127: Sub-projects as tasks (phoenix_kit_project_assignments.child_project_uuid).

Lets a project be embedded inside another project as one of its task rows. A sub-project is an Assignment that points at a child Project instead of a reusable Task template — so it lives in the parent's task timeline and gets dependencies + drag-reorder for free (both are already assignment-level and project-scoped). The child project is the single source of truth; the parent's linking assignment carries denormalized rollup fields (status / progress_pct / estimated_duration / completed_at) synced by the context layer whenever the child changes, so every existing read site (schedule math, recompute_project_completion, dashboards, sorting) keeps working unchanged.

Changes to phoenix_kit_project_assignments:

  • child_project_uuid UUID → FK phoenix_kit_projects(uuid) ON DELETE RESTRICT. RESTRICT (not CASCADE) so a stray child-project delete fails loudly instead of silently mutating the parent's task list — recursive teardown is orchestrated explicitly in PhoenixKitProjects.Projects inside a transaction so it can log activity and tear the subtree down in order.
  • task_uuid loses its NOT NULL — a sub-project assignment has no template.
  • CHECK ((task_uuid IS NOT NULL) <> (child_project_uuid IS NOT NULL)) — exactly one of the two is set (XOR). Existing rows (task set, child NULL) satisfy it, so the constraint validates against current data without a backfill.
  • Partial UNIQUE index on (child_project_uuid) WHERE child_project_uuid IS NOT NULL — a project is a child of at most one parent assignment. This is also what forces template cloning to deep-clone child subtrees rather than point two parents at the same child. It also serves the "find the linking row for this child" lookups (parent breadcrumb, rollup sync): an equality predicate child_project_uuid = $1 implies IS NOT NULL, so Postgres uses the partial index for it — no separate plain index needed.

Idempotent: re-running is a no-op once the column/constraints/indexes exist.

Summary

Functions

down(opts)

up(opts)