defmodule ComponentsElixir.Inventory.Component do @moduledoc """ Schema for electronic components. Each component belongs to a category and tracks inventory information including count, location, and optional image and datasheet. """ use Ecto.Schema import Ecto.Changeset alias ComponentsElixir.Inventory.{Category, StorageLocation} schema "components" do field :name, :string field :description, :string field :keywords, :string field :position, :string field :legacy_position, :string field :count, :integer, default: 0 field :datasheet_url, :string field :datasheet_filename, :string field :image_filename, :string belongs_to :category, Category belongs_to :storage_location, StorageLocation timestamps(type: :naive_datetime_usec) end @doc false def changeset(component, attrs) do component |> cast(attrs, [:name, :description, :keywords, :position, :count, :datasheet_url, :datasheet_filename, :image_filename, :category_id, :storage_location_id]) |> validate_required([:name, :category_id]) |> validate_length(:name, min: 1, max: 255) |> validate_length(:description, max: 2000) |> validate_length(:keywords, max: 500) |> validate_length(:position, max: 100) |> validate_number(:count, greater_than_or_equal_to: 0) |> validate_url(:datasheet_url) |> foreign_key_constraint(:category_id) |> foreign_key_constraint(:storage_location_id) end @doc """ Changeset for updating component count. """ def count_changeset(component, attrs) do component |> cast(attrs, [:count]) |> validate_required([:count]) |> validate_number(:count, greater_than_or_equal_to: 0) end @doc """ Changeset for updating component image. """ def image_changeset(component, attrs) do component |> cast(attrs, [:image_filename]) end @doc """ Changeset for updating component datasheet. """ def datasheet_changeset(component, attrs) do component |> cast(attrs, [:datasheet_filename]) end defp validate_url(changeset, field) do validate_change(changeset, field, fn ^field, url -> cond do is_nil(url) or url == "" -> [] valid_url?(url) -> [] true -> [{field, "must be a valid URL"}] end end) end defp valid_url?(url) do case URI.parse(url) do %URI{scheme: scheme} when scheme in ["http", "https"] -> true _ -> false end end @doc """ Returns true if the component has an image. """ def has_image?(%__MODULE__{image_filename: filename}) when is_binary(filename), do: true def has_image?(_), do: false @doc """ Returns true if the component has a datasheet file. """ def has_datasheet?(%__MODULE__{datasheet_filename: filename}) when is_binary(filename), do: true def has_datasheet?(_), do: false @doc """ Returns the search text for this component. """ def search_text(%__MODULE__{} = component) do [component.name, component.description, component.keywords] |> Enum.filter(&is_binary/1) |> Enum.join(" ") end end