mirror of
https://github.com/ash-project/ash_double_entry.git
synced 2024-09-19 13:03:19 +12:00
improvement: support destroying transfers
error messaging is sub-par, but that can be improved later
This commit is contained in:
parent
1fcc2abc37
commit
16f8e21fd1
3 changed files with 112 additions and 33 deletions
|
@ -9,6 +9,10 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
require Ash.Query
|
||||
|
||||
def change(changeset, _opts, context) do
|
||||
if changeset.action.type == :update do
|
||||
raise "Cannot update transfers currently"
|
||||
end
|
||||
|
||||
changeset
|
||||
|> Ash.Changeset.before_action(fn changeset ->
|
||||
timestamp = Ash.Changeset.get_attribute(changeset, :timestamp)
|
||||
|
@ -23,6 +27,7 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
|
||||
Ash.Changeset.force_change_attribute(changeset, :id, ulid)
|
||||
end)
|
||||
|> maybe_destroy_balances(context)
|
||||
|> Ash.Changeset.after_action(fn changeset, result ->
|
||||
from_account_id = Ash.Changeset.get_attribute(changeset, :from_account_id)
|
||||
to_account_id = Ash.Changeset.get_attribute(changeset, :to_account_id)
|
||||
|
@ -48,32 +53,34 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
new_to_account_balance =
|
||||
Money.add!(to_account.balance_as_of_ulid || Money.new!(0, to_account.currency), amount)
|
||||
|
||||
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
|
||||
},
|
||||
context_to_opts(context)
|
||||
)
|
||||
|> changeset.api.create!()
|
||||
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
|
||||
},
|
||||
context_to_opts(context)
|
||||
)
|
||||
|> changeset.api.create!()
|
||||
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|> Ash.Changeset.for_create(
|
||||
:upsert_balance,
|
||||
%{
|
||||
account_id: to_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_to_account_balance
|
||||
},
|
||||
context_to_opts(context)
|
||||
)
|
||||
|> changeset.api.create!()
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|> Ash.Changeset.for_create(
|
||||
:upsert_balance,
|
||||
%{
|
||||
account_id: to_account.id,
|
||||
transfer_id: result.id,
|
||||
balance: new_to_account_balance
|
||||
},
|
||||
context_to_opts(context)
|
||||
)
|
||||
|> changeset.api.create!()
|
||||
end
|
||||
|
||||
# Turn this into a bulk update when we support it in Ash core
|
||||
changeset.resource
|
||||
|
@ -82,7 +89,7 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
|> Ash.Query.filter(transfer_id > ^result.id)
|
||||
|> changeset.api.stream!(context_to_opts(context))
|
||||
|> Stream.map(fn balance ->
|
||||
if balance.account_id == from_account.id do
|
||||
if changeset.action.type == :destroy && balance.account_id == from_account.id do
|
||||
%{
|
||||
account_id: balance.account_id,
|
||||
transfer_id: balance.transfer_id,
|
||||
|
@ -110,6 +117,38 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do
|
|||
end)
|
||||
end
|
||||
|
||||
defp maybe_destroy_balances(changeset, context) do
|
||||
if changeset.action.type == :destroy do
|
||||
balance_resource =
|
||||
changeset.resource
|
||||
|> AshDoubleEntry.Transfer.Info.transfer_balance_resource!()
|
||||
|
||||
destroy_action = Ash.Resource.Info.primary_action(balance_resource, :destroy)
|
||||
|
||||
if !destroy_action do
|
||||
raise "Must configure a primary destroy action for #{inspect(balance_resource)} to destroy transactions"
|
||||
end
|
||||
|
||||
Ash.Changeset.before_action(changeset, fn changeset ->
|
||||
balance_resource
|
||||
|> Ash.Query.filter(transfer_id == ^changeset.data.id)
|
||||
|> changeset.api.stream!(context_to_opts(context, authorize?: false))
|
||||
|> Enum.each(fn balance ->
|
||||
balance
|
||||
|> Ash.Changeset.for_destroy(
|
||||
destroy_action,
|
||||
context_to_opts(context, authorize?: false)
|
||||
)
|
||||
|> changeset.api.destroy!()
|
||||
end)
|
||||
|
||||
changeset
|
||||
end)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp context_to_opts(context, opts \\ []) do
|
||||
context
|
||||
|> Map.take([:tenant, :actor, :tracer])
|
||||
|
|
|
@ -43,6 +43,8 @@ defmodule AshDoubleEntry.Transfer.Transformers.AddStructure do
|
|||
|> Ash.Resource.Builder.add_action(:read, :read_transfers,
|
||||
pagination: Ash.Resource.Builder.build_pagination(keyset?: true)
|
||||
)
|
||||
|> Ash.Resource.Builder.add_change({AshDoubleEntry.Transfer.Changes.VerifyTransfer, []})
|
||||
|> Ash.Resource.Builder.add_change({AshDoubleEntry.Transfer.Changes.VerifyTransfer, []},
|
||||
on: [:create, :update, :destroy]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,10 @@ defmodule AshDoubleEntryTest do
|
|||
account_resource Account
|
||||
balance_resource AshDoubleEntryTest.Balance
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:destroy]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Balance do
|
||||
|
@ -44,6 +48,10 @@ defmodule AshDoubleEntryTest do
|
|||
account_resource Account
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:destroy]
|
||||
end
|
||||
|
||||
changes do
|
||||
change after_action(&validate_balance/2)
|
||||
end
|
||||
|
@ -139,17 +147,47 @@ defmodule AshDoubleEntryTest do
|
|||
})
|
||||
|> Api.create!()
|
||||
|
||||
Application.put_env(:foo, :bar, true)
|
||||
|
||||
assert Money.equal?(
|
||||
Api.load!(account_one, :balance_as_of).balance_as_of,
|
||||
Money.new!(:USD, -20)
|
||||
)
|
||||
|
||||
# assert Money.equal?(
|
||||
# Api.load!(account_two, :balance_as_of).balance_as_of,
|
||||
# Money.new!(:USD, 20)
|
||||
# )
|
||||
assert Money.equal?(
|
||||
Api.load!(account_two, :balance_as_of).balance_as_of,
|
||||
Money.new!(:USD, 20)
|
||||
)
|
||||
end
|
||||
|
||||
test "destroying transfers update the balances accordingly" do
|
||||
account_one =
|
||||
Account
|
||||
|> Ash.Changeset.for_create(:open, %{identifier: "account_one", currency: "USD"})
|
||||
|> Api.create!()
|
||||
|
||||
account_two =
|
||||
Account
|
||||
|> Ash.Changeset.for_create(:open, %{identifier: "account_two", currency: "USD"})
|
||||
|> Api.create!()
|
||||
|
||||
Transfer
|
||||
|> Ash.Changeset.for_create(:transfer, %{
|
||||
amount: Money.new!(:USD, 20),
|
||||
from_account_id: account_one.id,
|
||||
to_account_id: account_two.id
|
||||
})
|
||||
|> Api.create!()
|
||||
|> Ash.Changeset.for_destroy(:destroy)
|
||||
|> Api.destroy!()
|
||||
|
||||
assert Money.equal?(
|
||||
Api.load!(account_one, :balance_as_of).balance_as_of,
|
||||
Money.new!(:USD, 0)
|
||||
)
|
||||
|
||||
assert Money.equal?(
|
||||
Api.load!(account_two, :balance_as_of).balance_as_of,
|
||||
Money.new!(:USD, 0)
|
||||
)
|
||||
end
|
||||
|
||||
test "balances can be validated" do
|
||||
|
|
Loading…
Reference in a new issue