mirror of
https://github.com/ash-project/ash_double_entry.git
synced 2024-09-08 14:01:28 +12:00
improvement: update to support new atomics & bulk actions
This commit is contained in:
parent
1a0819d1c5
commit
c47c548a78
9 changed files with 181 additions and 107 deletions
|
@ -20,6 +20,9 @@ if config_env() == :test do
|
|||
config :ash, :disable_async?, true
|
||||
end
|
||||
|
||||
config :ash, :known_types, [AshMoney.Types.Money]
|
||||
config :logger, level: :error
|
||||
|
||||
config :spark, :formatter,
|
||||
remove_parens?: true,
|
||||
"Ash.Resource": []
|
||||
|
|
|
@ -22,6 +22,7 @@ An extension for creating a double entry ledger balance. See the getting started
|
|||
| [`account_resource`](#balance-account_resource){: #balance-account_resource .spark-required} | `module` | | The resource used for accounts |
|
||||
| [`pre_check_identities_with`](#balance-pre_check_identities_with){: #balance-pre_check_identities_with } | `module` | | A domain to use to precheck generated identities. Required by certain data layers. |
|
||||
| [`money_composite_type?`](#balance-money_composite_type?){: #balance-money_composite_type? } | `boolean` | `true` | Whether the balance is stored as a composite type. |
|
||||
| [`data_layer_can_add_money?`](#balance-data_layer_can_add_money?){: #balance-data_layer_can_add_money? } | `boolean` | `true` | Whether or not the data layer supports adding money. |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@ defmodule AshDoubleEntry.Balance do
|
|||
type: :boolean,
|
||||
doc: "Whether the balance is stored as a composite type.",
|
||||
default: true
|
||||
],
|
||||
data_layer_can_add_money?: [
|
||||
type: :boolean,
|
||||
doc: "Whether or not the data layer supports adding money.",
|
||||
default: true
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
45
lib/balance/changes/adjust_balance.ex
Normal file
45
lib/balance/changes/adjust_balance.ex
Normal file
|
@ -0,0 +1,45 @@
|
|||
defmodule AshDoubleEntry.Balance.Changes.AdjustBalance do
|
||||
@moduledoc false
|
||||
use Ash.Resource.Change
|
||||
|
||||
def change(changeset, _, _) do
|
||||
amount_delta = changeset.arguments.delta
|
||||
|
||||
new_balance =
|
||||
if changeset.data.account_id == changeset.arguments.from_account_id do
|
||||
Money.sub!(changeset.data.balance, amount_delta)
|
||||
else
|
||||
Money.add!(changeset.data.balance, amount_delta)
|
||||
end
|
||||
|
||||
Ash.Changeset.force_change_attribute(changeset, :balance, new_balance)
|
||||
end
|
||||
|
||||
def atomic(changeset, opts, _) do
|
||||
amount_delta = changeset.arguments.delta
|
||||
|
||||
if Ash.Expr.expr?(amount_delta) do
|
||||
raise """
|
||||
Amount delta is dynamic. The balance adjustment logic does not support this.
|
||||
|
||||
Expected a literal money value, got an expression: #{inspect(amount_delta)}
|
||||
"""
|
||||
end
|
||||
|
||||
if opts[:can_add_money?] do
|
||||
{:atomic,
|
||||
%{
|
||||
balance:
|
||||
expr(
|
||||
if account_id == ^changeset.arguments.from_account_id do
|
||||
^atomic_ref(:balance) - ^amount_delta
|
||||
else
|
||||
^atomic_ref(:balance) + ^amount_delta
|
||||
end
|
||||
)
|
||||
}}
|
||||
else
|
||||
{:not_atomic, "Data layer cannot add money, so balance cannot be adjusted atomically"}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@ defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
|
|||
@moduledoc false
|
||||
use Spark.Dsl.Transformer
|
||||
import Spark.Dsl.Builder
|
||||
import Ash.Expr
|
||||
|
||||
def before?(Ash.Resource.Transformers.CachePrimaryKey), do: true
|
||||
def before?(Ash.Resource.Transformers.BelongsToAttribute), do: true
|
||||
|
@ -51,6 +52,32 @@ defmodule AshDoubleEntry.Balance.Transformers.AddStructure do
|
|||
upsert?: true,
|
||||
upsert_identity: :unique_references
|
||||
)
|
||||
|> Ash.Resource.Builder.add_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_identity(:unique_references, [:account_id, :transfer_id],
|
||||
pre_check_with: pre_check_with(dsl)
|
||||
)
|
||||
|
|
|
@ -80,77 +80,55 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
amount_delta
|
||||
)
|
||||
|
||||
unless changeset.action.type == :destroy do
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|> Ash.Changeset.for_create(
|
||||
:upsert_balance,
|
||||
%{
|
||||
account_id: from_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_from_account_balance,
|
||||
account: from_account
|
||||
},
|
||||
Ash.Context.to_opts(context,
|
||||
domain: changeset.domain,
|
||||
skip_unknown_inputs: [:account_id, :transfer_id, :balance, :account]
|
||||
)
|
||||
)
|
||||
|> Ash.create!()
|
||||
balance_resource =
|
||||
AshDoubleEntry.Transfer.Info.transfer_balance_resource!(changeset.resource)
|
||||
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|> Ash.Changeset.for_create(
|
||||
unless changeset.action.type == :destroy do
|
||||
Ash.bulk_create!(
|
||||
[
|
||||
%{
|
||||
account_id: from_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_from_account_balance
|
||||
},
|
||||
%{
|
||||
account_id: to_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_to_account_balance
|
||||
}
|
||||
],
|
||||
balance_resource,
|
||||
:upsert_balance,
|
||||
%{
|
||||
account_id: to_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_to_account_balance
|
||||
},
|
||||
Ash.Context.to_opts(context,
|
||||
domain: changeset.domain,
|
||||
skip_unknown_inputs: [:account_id, :transfer_id, :balance]
|
||||
upsert_fields: [:balance],
|
||||
return_errors?: true,
|
||||
stop_on_error?: true
|
||||
)
|
||||
)
|
||||
|> Ash.create!()
|
||||
end
|
||||
|
||||
# Turn this into a bulk update when we support it in Ash core
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|> Ash.Query.filter(account_id in ^[from_account.id, to_account.id])
|
||||
|> Ash.Query.filter(transfer_id > ^result.id)
|
||||
|> Ash.stream!(Ash.Context.to_opts(context, domain: changeset.domain))
|
||||
|> Stream.map(fn balance ->
|
||||
amount_delta =
|
||||
if changeset.action.type == :destroy do
|
||||
Money.mult!(amount_delta, -1)
|
||||
else
|
||||
amount_delta
|
||||
end
|
||||
|
||||
if balance.account_id == from_account.id do
|
||||
%{
|
||||
account_id: balance.account_id,
|
||||
transfer_id: balance.transfer_id,
|
||||
balance: Money.sub!(balance.balance, amount_delta)
|
||||
}
|
||||
amount_delta =
|
||||
if changeset.action.type == :destroy do
|
||||
Money.mult!(amount_delta, -1)
|
||||
else
|
||||
%{
|
||||
account_id: balance.account_id,
|
||||
transfer_id: balance.transfer_id,
|
||||
balance: Money.add!(balance.balance, amount_delta)
|
||||
}
|
||||
amount_delta
|
||||
end
|
||||
end)
|
||||
|> Ash.bulk_create!(
|
||||
AshDoubleEntry.Transfer.Info.transfer_balance_resource!(changeset.resource),
|
||||
:upsert_balance,
|
||||
|
||||
Ash.bulk_update!(
|
||||
balance_resource,
|
||||
:adjust_balance,
|
||||
%{
|
||||
from_account_id: from_account.id,
|
||||
to_account_id: to_account.id,
|
||||
transfer_id: result.id,
|
||||
delta: amount_delta
|
||||
},
|
||||
Ash.Context.to_opts(context,
|
||||
domain: changeset.domain,
|
||||
strategy: [:atomic, :stream, :atomic_batches],
|
||||
return_errors?: true,
|
||||
stop_on_error?: true,
|
||||
upsert_fields: [:balance]
|
||||
stop_on_error?: true
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -174,16 +152,15 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
Ash.Changeset.before_action(changeset, fn changeset ->
|
||||
balance_resource
|
||||
|> Ash.Query.filter(transfer_id == ^changeset.data.id)
|
||||
|> Ash.stream!(Ash.Context.to_opts(context, authorize?: false, domain: changeset.domain))
|
||||
|> Enum.each(fn balance ->
|
||||
balance
|
||||
|> Ash.Changeset.for_destroy(
|
||||
destroy_action,
|
||||
%{},
|
||||
Ash.Context.to_opts(context, authorize?: false, domain: changeset.domain)
|
||||
|> Ash.bulk_destroy!(
|
||||
destroy_action,
|
||||
%{},
|
||||
Ash.Context.to_opts(context,
|
||||
authorize?: false,
|
||||
domain: changeset.domain,
|
||||
strategy: [:stream, :atomic, :atomic_batches]
|
||||
)
|
||||
|> Ash.destroy!()
|
||||
end)
|
||||
)
|
||||
|
||||
changeset
|
||||
end)
|
||||
|
|
4
mix.exs
4
mix.exs
|
@ -116,8 +116,8 @@ defmodule AshDoubleEntry.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash, ash_version("~> 3.0.0-rc.7")},
|
||||
{:ash_money, "~> 0.1.6-rc.0"},
|
||||
{:ash, ash_version("~> 3.0.0-rc")},
|
||||
{:ash_money, path: "../ash_money"},
|
||||
{:ex_money_sql, "~> 1.10"},
|
||||
# dev/test dependencies
|
||||
{:git_ops, "~> 2.5", only: [:dev, :test]},
|
||||
|
|
10
mix.lock
10
mix.lock
|
@ -15,11 +15,11 @@
|
|||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
|
||||
"ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"},
|
||||
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"},
|
||||
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.4", "5562148dfc631b04712983975093d2aac29df30b3bf2f7257e0c94b85b72e91b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2"},
|
||||
"ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a99e02654fb6910660921df72838a05e44f2411a", []},
|
||||
"ex_money": {:hex, :ex_money, "5.15.4", "9c933cd65e943d9f48cb9c6878d3eaa535baade94ffc8be7630e619c99658d95", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:gringotts, "~> 1.1", [hex: :gringotts, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "196a28df6b9195d566a4ba0860d2f87b45738032b9d6f87d1ca067987a4de5bb"},
|
||||
"ex_cldr": {:hex, :ex_cldr, "2.38.0", "80399ccbfd996ea02f245394db83d7ce6113ed88e1e50e4695238cd9a0c258d5", [:mix], [{:cldr_utils, "~> 2.25", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "8758000c97bdf4b2583c3fedd7cfa35896567a7f8351248b2faa33ba73841cc7"},
|
||||
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.16.1", "29317f533cb5ec046d04523256cca4090291e9157028f28731395149b06ff8b2", [:mix], [{:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "095d5e973bf0ee066dd1153990d10cb6fa6d8ff0e028295bdce7a7821c70a0e4"},
|
||||
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.33.1", "49dc6e77e6d9ad22660aaa2480a7408ad3aedfbe517e4e83e5fe3a7bf5345770", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.16", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "c003bfaa3fdee6bab5195f128b94038c2ce1cf4879a759eef431dd075d9a5dac"},
|
||||
"ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "f8075a37b44b406bc489ee9cb1374f6e3d3bb8ee", []},
|
||||
"ex_money": {:hex, :ex_money, "5.16.1", "99c07552948ab437ce8c7a43c80d59571145ba7c42a466e4c8ce687c71fa0f57", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.33", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:gringotts, "~> 1.1", [hex: :gringotts, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "c4c894a0a1423df31b3f0442692b1cb8d51ba826ff25778382803a059a04ae4f"},
|
||||
"ex_money_sql": {:hex, :ex_money_sql, "1.11.0", "1b9b2f920d5d9220fa6dd4d8aae258daf562deaed2fb037b38b1f7ba4d0a344c", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ex_money, "~> 5.7", [hex: :ex_money, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "629e0541ae9f87122d34650f8c8febbc7349bbc6f881cf7a51b4d0779886107d"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
||||
|
|
|
@ -1,14 +1,47 @@
|
|||
defmodule AshDoubleEntryTest do
|
||||
use ExUnit.Case
|
||||
import ExUnit.CaptureLog
|
||||
require Ash.Query
|
||||
|
||||
defmodule RequiresPositiveBalance do
|
||||
use Ash.Resource.Validation
|
||||
|
||||
def validate(changeset, _, _) do
|
||||
account_id = Ash.Changeset.get_attribute(changeset, :account_id)
|
||||
|
||||
if is_nil(account_id) do
|
||||
:ok
|
||||
else
|
||||
account = Ash.get!(AshDoubleEntryTest.Account, account_id, authorize?: false)
|
||||
|
||||
if account.allow_zero_balance do
|
||||
{:error, "Account must require positive balance"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def atomic(_changeset, _, _) do
|
||||
{:atomic, [:account], expr(account.allow_zero_balance == false),
|
||||
expr(
|
||||
error(Ash.Error.Changes.InvalidRelationship,
|
||||
relationship: :account,
|
||||
message: "Account must require positive balance"
|
||||
)
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Account do
|
||||
use Ash.Resource,
|
||||
domain: AshDoubleEntryTest.Domain,
|
||||
data_layer: Ash.DataLayer.Mnesia,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [AshDoubleEntry.Account]
|
||||
|
||||
ets do
|
||||
private? true
|
||||
end
|
||||
|
||||
account do
|
||||
pre_check_identities_with AshDoubleEntryTest.Domain
|
||||
transfer_resource AshDoubleEntryTest.Transfer
|
||||
|
@ -26,9 +59,13 @@ defmodule AshDoubleEntryTest do
|
|||
defmodule Transfer do
|
||||
use Ash.Resource,
|
||||
domain: AshDoubleEntryTest.Domain,
|
||||
data_layer: Ash.DataLayer.Mnesia,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [AshDoubleEntry.Transfer]
|
||||
|
||||
ets do
|
||||
private? true
|
||||
end
|
||||
|
||||
transfer do
|
||||
account_resource Account
|
||||
balance_resource AshDoubleEntryTest.Balance
|
||||
|
@ -42,9 +79,13 @@ defmodule AshDoubleEntryTest do
|
|||
defmodule Balance do
|
||||
use Ash.Resource,
|
||||
domain: AshDoubleEntryTest.Domain,
|
||||
data_layer: Ash.DataLayer.Mnesia,
|
||||
data_layer: Ash.DataLayer.Ets,
|
||||
extensions: [AshDoubleEntry.Balance]
|
||||
|
||||
ets do
|
||||
private? true
|
||||
end
|
||||
|
||||
balance do
|
||||
pre_check_identities_with AshDoubleEntryTest.Domain
|
||||
transfer_resource Transfer
|
||||
|
@ -55,24 +96,10 @@ defmodule AshDoubleEntryTest do
|
|||
defaults [:destroy]
|
||||
end
|
||||
|
||||
changes do
|
||||
change after_action(&validate_balance/3)
|
||||
end
|
||||
|
||||
defp validate_balance(_changeset, result, _context) do
|
||||
account = result |> Ash.load!(:account) |> Map.get(:account)
|
||||
|
||||
if account.allow_zero_balance == false &&
|
||||
Money.negative?(result.balance) do
|
||||
{:error,
|
||||
Ash.Error.Changes.InvalidAttribute.exception(
|
||||
value: result.balance,
|
||||
field: :balance,
|
||||
message: "balance cannot be negative"
|
||||
)}
|
||||
else
|
||||
{:ok, result}
|
||||
end
|
||||
validations do
|
||||
validate compare(:balance, greater_than_or_equal_to: 0),
|
||||
where: [RequiresPositiveBalance],
|
||||
message: "balance cannot be negative"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -86,17 +113,6 @@ defmodule AshDoubleEntryTest do
|
|||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
Ash.DataLayer.Mnesia.start(Domain)
|
||||
|
||||
on_exit(fn ->
|
||||
capture_log(fn ->
|
||||
:mnesia.stop()
|
||||
:mnesia.delete_schema([node()])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
describe "opening accounts" do
|
||||
test "an account can be opened" do
|
||||
assert %{identifier: "account_one"} =
|
||||
|
|
Loading…
Reference in a new issue