defmodule DaProductApp.RiskManagement.RiskRule do use Ecto.Schema import Ecto.Changeset @derive {Jason.Encoder, only: [ :id, :name, :description, :category, :parameters, :enabled, :rule_type, :execution_order, :mcc_codes, :inserted_at, :updated_at ]} @primary_key {:id, :binary_id, autogenerate: true} schema "risk_rules" do field :name, :string field :description, :string field :category, :string # Cat A, Cat B, Cat C, Cat D field :parameters, :map field :enabled, :boolean, default: true field :rule_type, :string # 'hold' or 'alert' field :execution_order, :integer, default: 0 field :mcc_codes, :map, default: %{} # Stored as JSON map has_many :rule_hits, DaProductApp.RiskManagement.RiskRuleHit timestamps(type: :utc_datetime) end @required_fields [:name, :category, :rule_type] @optional_fields [:description, :parameters, :enabled, :execution_order, :mcc_codes] @all_fields @required_fields ++ @optional_fields @valid_categories ["Cat A", "Cat B", "Cat C", "Cat D"] @valid_rule_types ["hold", "alert"] def changeset(risk_rule, attrs) do risk_rule |> cast(attrs, @all_fields) |> validate_required(@required_fields) |> validate_length(:name, min: 3, max: 100) |> validate_length(:description, max: 1000) |> validate_inclusion(:category, @valid_categories) |> validate_inclusion(:rule_type, @valid_rule_types) |> validate_number(:execution_order, greater_than_or_equal_to: 0) |> unique_constraint([:name, :category]) |> ensure_mcc_codes_is_map() end defp ensure_mcc_codes_is_map(changeset) do case get_field(changeset, :mcc_codes) do nil -> put_change(changeset, :mcc_codes, %{}) %{} = _map -> changeset _string_or_other -> put_change(changeset, :mcc_codes, %{}) end end def valid_categories, do: @valid_categories def valid_rule_types, do: @valid_rule_types # Convert mcc_codes map to comma-separated string for form display def mcc_codes_to_string(%{} = map) when map_size(map) == 0, do: "" def mcc_codes_to_string(nil), do: "" def mcc_codes_to_string(%{} = map) do map |> Map.keys() |> Enum.join(",") end def mcc_codes_to_string(string) when is_binary(string), do: string # Convert comma-separated string to mcc_codes map for storage def string_to_mcc_codes(""), do: %{} def string_to_mcc_codes(nil), do: %{} def string_to_mcc_codes(string) when is_binary(string) do string |> String.split(",") |> Enum.map(&String.trim/1) |> Enum.filter(&(&1 != "")) |> Enum.reduce(%{}, &Map.put(&2, &1, true)) end def string_to_mcc_codes(map) when is_map(map), do: map end