Renames the rover-location status enum and every label that referenced it. Existing rows are migrated in place. Also touches the consumers: - Rover.Location enum + default - RoverLocationsLive index, status filter, form, show, badges - Rover.Compute and rover_live (only_ideal_locations → only_good_locations) - RoverPlanning context candidate filter (status == :good) - RoverPlanning.Show / Form copy - All rover-related tests
71 lines
2.4 KiB
Elixir
71 lines
2.4 KiB
Elixir
defmodule Microwaveprop.RoverPlanning.Mission do
|
|
@moduledoc """
|
|
A planned rover mission. Owned by a user but globally visible. Holds
|
|
the band + height parameters that drive path-profile computation, the
|
|
list of stationary endpoints (`stations`), and the matrix of computed
|
|
paths from each rover-location to each station (`paths`).
|
|
|
|
`only_known_good` toggles whether the rover endpoint set is the
|
|
`:good` rover-locations (default) or every rover-location.
|
|
"""
|
|
use Ecto.Schema
|
|
|
|
import Ecto.Changeset
|
|
|
|
alias Microwaveprop.Accounts.User
|
|
alias Microwaveprop.RoverPlanning.Path
|
|
alias Microwaveprop.RoverPlanning.Station
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
schema "rover_missions" do
|
|
field :name, :string
|
|
field :band_mhz, :integer, default: 10_000
|
|
field :only_known_good, :boolean, default: true
|
|
field :rover_height_ft, :float, default: 8.0
|
|
field :station_height_ft, :float, default: 30.0
|
|
field :notes, :string
|
|
|
|
belongs_to :user, User
|
|
has_many :stations, Station, foreign_key: :mission_id, on_replace: :delete
|
|
has_many :paths, Path, foreign_key: :mission_id
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
@type t :: %__MODULE__{}
|
|
|
|
@castable_fields [
|
|
:name,
|
|
:band_mhz,
|
|
:only_known_good,
|
|
:rover_height_ft,
|
|
:station_height_ft,
|
|
:notes
|
|
]
|
|
|
|
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
|
|
def changeset(mission, attrs) do
|
|
mission
|
|
|> cast(attrs, @castable_fields)
|
|
|> cast_assoc(:stations, with: &Station.changeset/2, required: true)
|
|
|> validate_required([:name, :band_mhz])
|
|
|> validate_length(:name, min: 1, max: 100)
|
|
|> validate_number(:band_mhz, greater_than: 0)
|
|
|> validate_number(:rover_height_ft, greater_than_or_equal_to: 0.0)
|
|
|> validate_number(:station_height_ft, greater_than_or_equal_to: 0.0)
|
|
|> validate_length(:notes, max: 4000)
|
|
|> validate_at_least_one_station()
|
|
end
|
|
|
|
defp validate_at_least_one_station(changeset) do
|
|
case fetch_field(changeset, :stations) do
|
|
{_source, list} when is_list(list) and list != [] ->
|
|
kept = Enum.reject(list, fn s -> match?(%Ecto.Changeset{action: :replace}, s) end)
|
|
if kept == [], do: add_error(changeset, :stations, "must include at least one station"), else: changeset
|
|
|
|
_ ->
|
|
add_error(changeset, :stations, "must include at least one station")
|
|
end
|
|
end
|
|
end
|