defmodule DaProductApp.Settlements.YspSummary do @moduledoc """ Schema for YSP Summary table that stores settlement summary information including file paths and structured settlement data. Prevents duplicate settlement processing based on merchant_tag, merchant_id, batch_number, and settlement_date. """ use Ecto.Schema import Ecto.Changeset @primary_key {:id, :id, autogenerate: true} @derive {Phoenix.Param, key: :id} schema "ysp_summary" do field :merchant_tag, :string field :merchant_id, :string field :batch_number, :string field :settlement_date, :date field :file_path, :string field :settlement_id, :map timestamps(type: :utc_datetime) end @doc """ Changeset for creating YSP summary records. """ def changeset(ysp_summary, attrs) do ysp_summary |> cast(attrs, [ :merchant_tag, :merchant_id, :batch_number, :settlement_date, :file_path, :settlement_id ]) |> validate_required([ :merchant_tag, :merchant_id, :batch_number, :settlement_date, :file_path, :settlement_id ]) |> validate_length(:merchant_tag, max: 255) |> validate_length(:merchant_id, max: 255) |> validate_length(:batch_number, max: 50) |> unique_constraint([:merchant_tag, :merchant_id, :batch_number, :settlement_date], name: :uk_merchant_batch_date, message: "Settlement already processed for this merchant, batch, and date" ) # Some databases may have a differently named unique index. Map the other possible # constraint name to a changeset error as well to avoid raising Ecto.ConstraintError. |> unique_constraint([:merchant_tag, :merchant_id, :batch_number, :settlement_date], name: :uk_merchant_batch, message: "Settlement already processed for this merchant, batch, and date" ) |> validate_settlement_id_structure() end @doc """ Validates the settlement_id JSON structure contains required fields. """ defp validate_settlement_id_structure(changeset) do case get_field(changeset, :settlement_id) do nil -> add_error(changeset, :settlement_id, "is required") settlement_data when is_map(settlement_data) -> required_keys = ["settlement_reference", "status", "settlement_date"] if Enum.all?(required_keys, &Map.has_key?(settlement_data, &1)) do changeset else add_error( changeset, :settlement_id, "must contain required keys: #{Enum.join(required_keys, ", ")}" ) end _ -> add_error(changeset, :settlement_id, "must be a valid JSON object") end end @doc """ Creates a standardized settlement_id JSON structure for storage. """ def build_settlement_json(settlement_data) do %{ "settlement_reference" => settlement_data[:settlement_reference] || settlement_data["settlement_reference"] || "", "status" => settlement_data[:status] || settlement_data["status"] || "COMPLETED", "transaction_count" => settlement_data[:transaction_count] || settlement_data["transaction_count"] || 0, "gross_amount" => to_string(settlement_data[:gross_amount] || settlement_data["gross_amount"] || "0.00"), "net_amount" => to_string(settlement_data[:net_amount] || settlement_data["net_amount"] || "0.00"), "currency" => settlement_data[:currency] || settlement_data["currency"] || "AED", "mismatch_detected" => settlement_data[:mismatch_detected] || settlement_data["mismatch_detected"] || false, "settlement_date" => to_string(settlement_data[:settlement_date] || settlement_data["settlement_date"]), "generated_timestamp" => DateTime.utc_now() |> DateTime.to_iso8601() } end end