lib/balance/transformers/add_structure.ex

# SPDX-FileCopyrightText: 2023 ash_double_entry contributors <https://github.com/ash-project/ash_double_entry/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
  # Adds all the structure required for the resource. See the getting started guide for more.
  @moduledoc false
  use Spark.Dsl.Transformer
  import Spark.Dsl.Builder
  import Ash.Expr

  def before?(Ash.Resource.Transformers.SetRelationshipSource), do: true
  def before?(Ash.Resource.Transformers.CachePrimaryKey), do: true
  def before?(Ash.Resource.Transformers.BelongsToAttribute), do: true
  def before?(_), do: false

  def transform(dsl) do
    storage_type =
      if AshDoubleEntry.Balance.Info.balance_money_composite_type?(dsl) do
        :money_with_currency
      else
        :map
      end

    dsl
    |> Ash.Resource.Builder.add_new_attribute(:id, :uuid,
      primary_key?: true,
      writable?: false,
      generated?: true,
      allow_nil?: false,
      default: &Ash.UUID.generate/0
    )
    |> Ash.Resource.Builder.add_new_attribute(:balance, AshMoney.Types.Money,
      allow_nil?: false,
      constraints: [
        storage_type: storage_type
      ]
    )
    |> Ash.Resource.Builder.add_new_relationship(
      :belongs_to,
      :transfer,
      AshDoubleEntry.Balance.Info.balance_transfer_resource!(dsl),
      attribute_type: AshDoubleEntry.ULID,
      allow_nil?: false,
      attribute_writable?: true
    )
    |> Ash.Resource.Builder.add_new_relationship(
      :belongs_to,
      :account,
      AshDoubleEntry.Balance.Info.balance_account_resource!(dsl),
      allow_nil?: false,
      attribute_writable?: true
    )
    |> add_primary_read_action()
    |> Ash.Resource.Builder.add_new_action(:create, :upsert_balance,
      accept: [:balance, :account_id, :transfer_id],
      upsert?: true,
      upsert_identity: :unique_references
    )
    |> Ash.Resource.Builder.add_new_action(:update, :adjust_balance,
      changes: [
        Ash.Resource.Builder.build_action_change(
          {Ash.Resource.Change.Filter,
           filter:
             expr(
               account_id in [^arg(:from_account_id), ^arg(:to_account_id)] and
                 transfer_id > ^arg(:transfer_id)
             )}
        ),
        Ash.Resource.Builder.build_action_change(
          {AshDoubleEntry.Balance.Changes.AdjustBalance,
           can_add_money?: AshDoubleEntry.Balance.Info.balance_data_layer_can_add_money?(dsl)}
        )
      ],
      arguments: [
        Ash.Resource.Builder.build_action_argument(:from_account_id, :uuid, allow_nil?: false),
        Ash.Resource.Builder.build_action_argument(:to_account_id, :uuid, allow_nil?: false),
        Ash.Resource.Builder.build_action_argument(:delta, AshMoney.Types.Money,
          allow_nil?: false
        ),
        Ash.Resource.Builder.build_action_argument(:transfer_id, AshDoubleEntry.ULID,
          allow_nil?: false
        )
      ]
    )
    |> Ash.Resource.Builder.add_new_identity(:unique_references, [:account_id, :transfer_id],
      pre_check_with: pre_check_with(dsl)
    )
  end

  defbuilder add_primary_read_action(dsl) do
    if Ash.Resource.Info.primary_action(dsl, :read) do
      {:ok, dsl}
    else
      Ash.Resource.Builder.add_action(dsl, :read, :_autogenerated_primary_read,
        primary?: true,
        pagination: Ash.Resource.Builder.build_pagination(keyset?: true)
      )
    end
  end

  defp pre_check_with(dsl) do
    case AshDoubleEntry.Balance.Info.balance_pre_check_identities_with(dsl) do
      :error ->
        nil

      {:ok, value} ->
        value
    end
  end
end