lib/account/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.Account.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

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

  def transform(dsl) do
    dsl
    |> add_primary_read_action()
    |> 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(:identifier, :string, allow_nil?: false)
    |> Ash.Resource.Builder.add_new_attribute(
      :currency,
      :string,
      allow_nil?: false
    )
    |> Ash.Resource.Builder.add_new_action(:create, :open,
      accept:
        Enum.uniq(
          [:identifier, :currency] ++ AshDoubleEntry.Account.Info.account_open_action_accept!(dsl)
        )
    )
    |> Ash.Resource.Builder.add_new_action(:read, :lock_accounts,
      preparations: [
        Ash.Resource.Builder.build_preparation(
          {AshDoubleEntry.Account.Preparations.LockForUpdate, []}
        )
      ]
    )
    |> Ash.Resource.Builder.add_new_attribute(:inserted_at, :utc_datetime_usec,
      allow_nil?: false,
      default: &DateTime.utc_now/0
    )
    |> Ash.Resource.Builder.add_new_relationship(
      :has_many,
      :balances,
      AshDoubleEntry.Account.Info.account_balance_resource!(dsl),
      destination_attribute: :account_id
    )
    |> add_balance_as_of_ulid_calculation()
    |> add_balance_as_of_calculation()
    |> Ash.Resource.Builder.add_new_identity(:unique_identifier, [:identifier],
      pre_check_with: pre_check_with(dsl)
    )
  end

  defbuilder add_balance_as_of_ulid_calculation(dsl) do
    Ash.Resource.Builder.add_new_calculation(
      dsl,
      :balance_as_of_ulid,
      AshMoney.Types.Money,
      {AshDoubleEntry.Account.Calculations.BalanceAsOfUlid,
       [resource: Spark.Dsl.Transformer.get_persisted(dsl, :module)]},
      arguments: [
        Ash.Resource.Builder.build_calculation_argument(
          :ulid,
          AshDoubleEntry.ULID,
          allow_nil?: false,
          allow_expr?: true
        )
      ]
    )
  end

  defbuilder add_balance_as_of_calculation(dsl) do
    Ash.Resource.Builder.add_new_calculation(
      dsl,
      :balance_as_of,
      AshMoney.Types.Money,
      {AshDoubleEntry.Account.Calculations.BalanceAsOf,
       [resource: Spark.Dsl.Transformer.get_persisted(dsl, :module)]},
      arguments: [
        Ash.Resource.Builder.build_calculation_argument(
          :timestamp,
          :utc_datetime_usec,
          allow_nil?: false,
          default: &DateTime.utc_now/0
        )
      ]
    )
  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, required?: false)
      )
    end
  end

  defp pre_check_with(dsl) do
    case AshDoubleEntry.Account.Info.account_pre_check_identities_with(dsl) do
      :error ->
        nil

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