Files
arcadia-cloud/lib/arcadia_cloud/catalog/plan_item.ex
Giuliano Silvestro c10f87b6e0 Phase 3: pricing catalog
Five catalog tables:
- plans            — plan identity (code, name); the stable thing.
- plan_versions    — versioned pricing (base_price_cents, currency,
                     status draft/active/retired). A subscription binds
                     to a version; raising prices = publish a new
                     version, existing subs unaffected until migrated.
- plan_items       — what a version includes per resource_kind, plus
                     overage terms (overage_unit, overage_price_cents,
                     hard_cap_qty).
- addons           — a la carte upgrades (code, resource_kind, qty,
                     price_cents).
- resource_prices  — effective-dated fallback per-unit pricing for
                     ad-hoc items not covered by a plan.

ArcadiaCloud.Catalog context: plan + version CRUD, active_version/1
(what a new signup gets), publish_version/1 (retires the prior active
version transactionally then activates the new one),
current_resource_price/2 (effective-dated lookup).

Seed (priv/repo/seeds/catalog_seed.exs, idempotent) creates three AUD
plans — Starter $20, Studio $50, Pro $120/mo — with included
droplet_hours / spaces_gb_month / snapshot_gb_month / bandwidth_gb /
dns_zones (and LLM token allowances on Studio + Pro), plus three storage
/ LLM addons. Prices are placeholders to tune against real DO COGS once
the cost-vs-revenue dashboard lands.

API (authenticated tenants — the catalog is what they pick from):
- GET /api/v1/catalog/plans   — plans with active version + items
- GET /api/v1/catalog/addons

Smoke verified: seed creates 3 plans + 3 addons; endpoints return the
shaped catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:29:52 +10:00

34 lines
857 B
Elixir

defmodule ArcadiaCloud.Catalog.PlanItem do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "plan_items" do
field :resource_kind, :string
field :included_qty, :decimal, default: Decimal.new(0)
field :overage_unit, :string
field :overage_price_cents, :integer
field :hard_cap_qty, :decimal
belongs_to :plan_version, ArcadiaCloud.Catalog.PlanVersion
timestamps(type: :utc_datetime)
end
def changeset(item, attrs) do
item
|> cast(attrs, [
:plan_version_id,
:resource_kind,
:included_qty,
:overage_unit,
:overage_price_cents,
:hard_cap_qty
])
|> validate_required([:plan_version_id, :resource_kind, :included_qty])
|> unique_constraint([:plan_version_id, :resource_kind])
end
end